diff --git a/docs/commands-list.md b/docs/commands-list.md index 9dd4de6..392dd79 100644 --- a/docs/commands-list.md +++ b/docs/commands-list.md @@ -8,7 +8,7 @@ | | `add` | `!account add test@ 12345678 test` | Allows you to add a new user account | | | `setpassword` | `!account setpassword test@ 12345678` | Allows you to set a new password for account | | | `setbtag` | `!account setbtag test@ NonTest` | Allows you to change battle tag for account | -| | `setuserlevel` | `!account setuserlevel admin test@` | Allows you to set a new user level for account | +| | `setuserlevel` | `!account setuserlevel test@ admin` | Allows you to set a new user level for account | | Mute Command | `mute` | `!mute test@` | Disable chat functions for user | ## Game Commands diff --git a/src/DiIiS-NA/D3-GameServer/GSSystem/ItemsSystem/Item.cs b/src/DiIiS-NA/D3-GameServer/GSSystem/ItemsSystem/Item.cs index f0ee67d..dff1e85 100644 --- a/src/DiIiS-NA/D3-GameServer/GSSystem/ItemsSystem/Item.cs +++ b/src/DiIiS-NA/D3-GameServer/GSSystem/ItemsSystem/Item.cs @@ -856,7 +856,7 @@ namespace DiIiS_NA.GameServer.GSSystem.ItemsSystem Destroy(); break; - case ActorSno._crafting_looted_reagent_05: //Death's Breath + case ActorSno._crafting_looted_reagent_05: //Death's Breath GBID? 2087837753 playerAcc.CraftItem4++; Destroy(); break; diff --git a/src/DiIiS-NA/D3-GameServer/GSSystem/PlayerSystem/ArtisanTrainHelper.cs b/src/DiIiS-NA/D3-GameServer/GSSystem/PlayerSystem/ArtisanTrainHelper.cs index bc10f43..5415d7f 100644 --- a/src/DiIiS-NA/D3-GameServer/GSSystem/PlayerSystem/ArtisanTrainHelper.cs +++ b/src/DiIiS-NA/D3-GameServer/GSSystem/PlayerSystem/ArtisanTrainHelper.cs @@ -10,24 +10,34 @@ namespace DiIiS_NA.D3_GameServer.GSSystem.PlayerSystem { private const int maxLevel = 12; private static readonly ArtisanType[] canBeTrained = new[] { ArtisanType.Blacksmith, ArtisanType.Jeweler, ArtisanType.Mystic }; + private static readonly Dictionary recipeTemplates = new() { [ArtisanType.Blacksmith] = "BlackSmith_Train_Level{0}", [ArtisanType.Jeweler] = "Jeweler_Train_Level{0}", [ArtisanType.Mystic] = "Mystic_Train_Level{0}" }; + private static readonly Dictionary achievements = new() { [ArtisanType.Blacksmith] = new[] { 74987243307767, 74987243307768, 74987243307769, 74987251817289 }, [ArtisanType.Jeweler] = new[] { 74987243307781, 74987243307782, 74987243307783, 74987257153995 }, [ArtisanType.Mystic] = new[] { 74987253584575, 74987256660015, 74987248802163, 74987251397159 } }; + private static readonly Dictionary criteriaForLevel10 = new() { [ArtisanType.Blacksmith] = 74987249071497, [ArtisanType.Jeweler] = 74987245845978, [ArtisanType.Mystic] = 74987259424359 }; + + // To'do it's necessary to get the correct Animation for Lvl 11 and 12. + // For now I'm just using the Lvl 10 Animation. + // fixme no animation level 11 + // 0x00011600, + // fixme no animation level 12 + // 0x00011610, private static readonly int[] animationTags = new[] { 0x00011500, 0x00011510, @@ -38,12 +48,15 @@ namespace DiIiS_NA.D3_GameServer.GSSystem.PlayerSystem 0x00011560, 0x00011570, 0x00011580, - 0x00011590, - // fixme no animation - 0x00011600, - // fixme no animation - 0x00011610, + 0x00011590 }; + + // To'do it's necessary to get the correct Animation for Lvl 11 and 12. + // For now I'm just using the Lvl 10 Animation. + // fixme no animation level 11 + // 0x00011310, + // fixme no animation level 12 + // 0x00011320 private static readonly int[] idleAnimationTags = new[] { 0x00011210, 0x00011220, @@ -54,22 +67,20 @@ namespace DiIiS_NA.D3_GameServer.GSSystem.PlayerSystem 0x00011270, 0x00011280, 0x00011290, - 0x00011300, - // fixme no animation - 0x00011310, - // fixme no animation - 0x00011320 + 0x00011300 }; - private readonly ArtisanType artisanType; + internal ArtisanTrainHelper(DBCraft dBCraft, ArtisanType type) { if (!canBeTrained.Contains(type)) throw new ArgumentException("Unsupported artisan type", nameof(type)); + DbRef = dBCraft ?? throw new ArgumentNullException(nameof(dBCraft)); artisanType = type; } + internal DBCraft DbRef { get; } internal string TrainRecipeName => string.Format(recipeTemplates[artisanType], Math.Min(DbRef.Level, maxLevel - 1)); @@ -85,9 +96,28 @@ namespace DiIiS_NA.D3_GameServer.GSSystem.PlayerSystem internal ulong? Criteria => DbRef.Level == 10 ? (ulong)criteriaForLevel10[artisanType] : null; - internal int AnimationTag => animationTags[DbRef.Level - 1]; - internal int IdleAnimationTag => idleAnimationTags[DbRef.Level - 1]; + internal int AnimationTag + { + get + { + if (DbRef.Level >= 10) + return animationTags[9]; // Force to use the LVL 10 Animation. + + return animationTags[DbRef.Level - 1]; + } + } + + internal int IdleAnimationTag + { + get + { + if (DbRef.Level >= 10) + return idleAnimationTags[9]; // Force to use the LVL 10 Idle Animation. + + return idleAnimationTags[DbRef.Level - 1]; + } + } internal int Type => Array.IndexOf(canBeTrained, artisanType); } -} +} \ No newline at end of file diff --git a/src/DiIiS-NA/D3-GameServer/GSSystem/PlayerSystem/Inventory.cs b/src/DiIiS-NA/D3-GameServer/GSSystem/PlayerSystem/Inventory.cs index 13a742a..635eba2 100644 --- a/src/DiIiS-NA/D3-GameServer/GSSystem/PlayerSystem/Inventory.cs +++ b/src/DiIiS-NA/D3-GameServer/GSSystem/PlayerSystem/Inventory.cs @@ -1,28 +1,32 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using DiIiS_NA.Core.Logging; +using DiIiS_NA.Core.Extensions; using DiIiS_NA.Core.Helpers.Math; -using DiIiS_NA.Core.Storage.AccountDataBase.Entities; +using DiIiS_NA.Core.Logging; using DiIiS_NA.Core.MPQ; using DiIiS_NA.Core.MPQ.FileFormats; +using DiIiS_NA.Core.Storage.AccountDataBase.Entities; +using DiIiS_NA.GameServer.ClientSystem; +using DiIiS_NA.GameServer.Core; using DiIiS_NA.GameServer.Core.Types.SNO; using DiIiS_NA.GameServer.GSSystem.ActorSystem.Implementations; using DiIiS_NA.GameServer.GSSystem.ItemsSystem; +using DiIiS_NA.GameServer.GSSystem.ObjectsSystem; +using DiIiS_NA.GameServer.GSSystem.PlayerSystem; using DiIiS_NA.GameServer.MessageSystem; -using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Inventory; using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.ACD; using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Artisan; -using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Misc; -using DiIiS_NA.GameServer.GSSystem.ObjectsSystem; -using DiIiS_NA.GameServer.Core; -using DiIiS_NA.GameServer.MessageSystem.Message.Fields; -using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Effect; -using DiIiS_NA.GameServer.ClientSystem; using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Base; +using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Effect; +using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Inventory; +using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Misc; +using DiIiS_NA.GameServer.MessageSystem.Message.Fields; +using DiIiS_NA.LoginServer.AccountsSystem; +using Discord; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; using static DiIiS_NA.Core.MPQ.FileFormats.GameBalance; -using DiIiS_NA.Core.Extensions; namespace DiIiS_NA.GameServer.GSSystem.PlayerSystem { @@ -98,21 +102,29 @@ namespace DiIiS_NA.GameServer.GSSystem.PlayerSystem return _buybackGrid; } - public bool HaveEnough(int GBid, int count) + public bool HaveEnough(int gBid, int count, Player? player = null) { - return (_inventoryGrid.TotalItemCount(GBid) + _stashGrid.TotalItemCount(GBid)) >= count; + // 2087837753 = Death's Breath -> AKA: CraftItem4 _crafting_looted_reagent_05. + if (player != null && gBid == 2087837753) + { + var playerAcc = player.InGameClient.BnetClient.Account.GameAccount; + return playerAcc.CraftItem4 > count; + } + + return (_inventoryGrid.TotalItemCount(gBid) + _stashGrid.TotalItemCount(gBid)) >= count; } - public void GrabSomeItems(int GBid, int count) + public void GrabSomeItems(int gBid, int count) { - if (_inventoryGrid.HaveEnough(GBid, count)) - _inventoryGrid.GrabSomeItems(GBid, count); + + if (_inventoryGrid.HaveEnough(gBid, count)) + _inventoryGrid.GrabSomeItems(gBid, count); else { - int inBag = _inventoryGrid.TotalItemCount(GBid); - _inventoryGrid.GrabSomeItems(GBid, inBag); + int inBag = _inventoryGrid.TotalItemCount(gBid); + _inventoryGrid.GrabSomeItems(gBid, inBag); count -= inBag; - _stashGrid.GrabSomeItems(GBid, count); + _stashGrid.GrabSomeItems(gBid, count); } } diff --git a/src/DiIiS-NA/D3-GameServer/GSSystem/PlayerSystem/Player.cs b/src/DiIiS-NA/D3-GameServer/GSSystem/PlayerSystem/Player.cs index 66190d0..83e6b39 100644 --- a/src/DiIiS-NA/D3-GameServer/GSSystem/PlayerSystem/Player.cs +++ b/src/DiIiS-NA/D3-GameServer/GSSystem/PlayerSystem/Player.cs @@ -2938,31 +2938,51 @@ public class Player : Actor, IMessageConsumer, IUpdateable return; var recipeDefinition = ItemGenerator.GetRecipeDefinition(trainHelper.TrainRecipeName); + + // 1) Validade the Gold. if (Inventory.GetGoldAmount() < recipeDefinition.Gold) return; - var requiredIngridients = recipeDefinition.Ingredients.Where(x => x.ItemsGBID > 0); - // FIXME: Inventory.HaveEnough doesn't work for some craft consumables - var haveEnoughIngredients = requiredIngridients.All(x => Inventory.HaveEnough(x.ItemsGBID, x.Count)); - if (!haveEnoughIngredients) - return; + // 2) Extract only valid ingredients (actual items). + var requiredIngredients = recipeDefinition.Ingredients + .Where(x => x.ItemsGBID > 0 && x.Count > 0) + .ToList(); + // 3) If the recipe requires items, validate whether they exist in the Inventory. + if (requiredIngredients.Any()) + { + + var haveEnoughIngredients = requiredIngredients + .All(x => Inventory.HaveEnough(x.ItemsGBID, x.Count, this)); + + if (!haveEnoughIngredients) + return; + + var playerAcc = this.InGameClient.BnetClient.Account.GameAccount; + + // We already know that Artisan training is just consume Death's breath. + playerAcc.CraftItem4--; + + } + + // 4) Always discount Gold (all recipes have a gold cost). Inventory.RemoveGoldAmount(recipeDefinition.Gold); - foreach (var ingr in requiredIngridients) - // FIXME: Inventory.GrabSomeItems doesn't work for some craft consumables - Inventory.GrabSomeItems(ingr.ItemsGBID, ingr.Count); + // 5) Advance the artisan's level. trainHelper.DbRef.Level++; World.Game.GameDbSession.SessionUpdate(trainHelper.DbRef); + // 6) Related achievements & criteria. if (trainHelper.Achievement is not null) GrantAchievement(trainHelper.Achievement.Value); + if (trainHelper.Criteria is not null) GrantCriteria(trainHelper.Criteria.Value); if (_artisanTrainHelpers.All(x => x.Value.HasMaxLevel)) GrantCriteria(74987249993545); + // 7) Notify the Client. client.SendMessage(new CrafterLevelUpMessage { Type = trainHelper.Type, @@ -2972,9 +2992,6 @@ public class Player : Actor, IMessageConsumer, IUpdateable }); LoadCrafterData(); - - - /**/ } public void UnlockTransmog(int transmogGBID)