Merge pull request #221 from rgto/community

Fix the Artisan Upgrade (without anim lvl 11 and 12), improve the inventory check and consumable materials.
This commit is contained in:
Enthusiast 2025-10-01 16:49:09 -03:00 committed by GitHub
commit 88089dbef3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 108 additions and 49 deletions

View File

@ -8,7 +8,7 @@
| | `add` | `!account add test@ 12345678 test` | Allows you to add a new user account | | | `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 | | | `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 | | | `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 | | Mute Command | `mute` | `!mute test@` | Disable chat functions for user |
## Game Commands ## Game Commands

View File

@ -856,7 +856,7 @@ namespace DiIiS_NA.GameServer.GSSystem.ItemsSystem
Destroy(); Destroy();
break; break;
case ActorSno._crafting_looted_reagent_05: //Death's Breath case ActorSno._crafting_looted_reagent_05: //Death's Breath GBID? 2087837753
playerAcc.CraftItem4++; playerAcc.CraftItem4++;
Destroy(); Destroy();
break; break;

View File

@ -10,24 +10,34 @@ namespace DiIiS_NA.D3_GameServer.GSSystem.PlayerSystem
{ {
private const int maxLevel = 12; private const int maxLevel = 12;
private static readonly ArtisanType[] canBeTrained = new[] { ArtisanType.Blacksmith, ArtisanType.Jeweler, ArtisanType.Mystic }; private static readonly ArtisanType[] canBeTrained = new[] { ArtisanType.Blacksmith, ArtisanType.Jeweler, ArtisanType.Mystic };
private static readonly Dictionary<ArtisanType, string> recipeTemplates = new() private static readonly Dictionary<ArtisanType, string> recipeTemplates = new()
{ {
[ArtisanType.Blacksmith] = "BlackSmith_Train_Level{0}", [ArtisanType.Blacksmith] = "BlackSmith_Train_Level{0}",
[ArtisanType.Jeweler] = "Jeweler_Train_Level{0}", [ArtisanType.Jeweler] = "Jeweler_Train_Level{0}",
[ArtisanType.Mystic] = "Mystic_Train_Level{0}" [ArtisanType.Mystic] = "Mystic_Train_Level{0}"
}; };
private static readonly Dictionary<ArtisanType, long[]> achievements = new() private static readonly Dictionary<ArtisanType, long[]> achievements = new()
{ {
[ArtisanType.Blacksmith] = new[] { 74987243307767, 74987243307768, 74987243307769, 74987251817289 }, [ArtisanType.Blacksmith] = new[] { 74987243307767, 74987243307768, 74987243307769, 74987251817289 },
[ArtisanType.Jeweler] = new[] { 74987243307781, 74987243307782, 74987243307783, 74987257153995 }, [ArtisanType.Jeweler] = new[] { 74987243307781, 74987243307782, 74987243307783, 74987257153995 },
[ArtisanType.Mystic] = new[] { 74987253584575, 74987256660015, 74987248802163, 74987251397159 } [ArtisanType.Mystic] = new[] { 74987253584575, 74987256660015, 74987248802163, 74987251397159 }
}; };
private static readonly Dictionary<ArtisanType, long> criteriaForLevel10 = new() private static readonly Dictionary<ArtisanType, long> criteriaForLevel10 = new()
{ {
[ArtisanType.Blacksmith] = 74987249071497, [ArtisanType.Blacksmith] = 74987249071497,
[ArtisanType.Jeweler] = 74987245845978, [ArtisanType.Jeweler] = 74987245845978,
[ArtisanType.Mystic] = 74987259424359 [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[] { private static readonly int[] animationTags = new[] {
0x00011500, 0x00011500,
0x00011510, 0x00011510,
@ -38,12 +48,15 @@ namespace DiIiS_NA.D3_GameServer.GSSystem.PlayerSystem
0x00011560, 0x00011560,
0x00011570, 0x00011570,
0x00011580, 0x00011580,
0x00011590, 0x00011590
// fixme no animation
0x00011600,
// fixme no animation
0x00011610,
}; };
// 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[] { private static readonly int[] idleAnimationTags = new[] {
0x00011210, 0x00011210,
0x00011220, 0x00011220,
@ -54,22 +67,20 @@ namespace DiIiS_NA.D3_GameServer.GSSystem.PlayerSystem
0x00011270, 0x00011270,
0x00011280, 0x00011280,
0x00011290, 0x00011290,
0x00011300, 0x00011300
// fixme no animation
0x00011310,
// fixme no animation
0x00011320
}; };
private readonly ArtisanType artisanType; private readonly ArtisanType artisanType;
internal ArtisanTrainHelper(DBCraft dBCraft, ArtisanType type) internal ArtisanTrainHelper(DBCraft dBCraft, ArtisanType type)
{ {
if (!canBeTrained.Contains(type)) if (!canBeTrained.Contains(type))
throw new ArgumentException("Unsupported artisan type", nameof(type)); throw new ArgumentException("Unsupported artisan type", nameof(type));
DbRef = dBCraft ?? throw new ArgumentNullException(nameof(dBCraft)); DbRef = dBCraft ?? throw new ArgumentNullException(nameof(dBCraft));
artisanType = type; artisanType = type;
} }
internal DBCraft DbRef { get; } internal DBCraft DbRef { get; }
internal string TrainRecipeName => string.Format(recipeTemplates[artisanType], Math.Min(DbRef.Level, maxLevel - 1)); internal string TrainRecipeName => string.Format(recipeTemplates[artisanType], Math.Min(DbRef.Level, maxLevel - 1));
@ -85,8 +96,27 @@ namespace DiIiS_NA.D3_GameServer.GSSystem.PlayerSystem
internal ulong? Criteria => DbRef.Level == 10 ? (ulong)criteriaForLevel10[artisanType] : null; internal ulong? Criteria => DbRef.Level == 10 ? (ulong)criteriaForLevel10[artisanType] : null;
internal int AnimationTag => animationTags[DbRef.Level - 1]; internal int AnimationTag
internal int IdleAnimationTag => idleAnimationTags[DbRef.Level - 1]; {
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); internal int Type => Array.IndexOf(canBeTrained, artisanType);
} }

View File

@ -1,28 +1,32 @@
using System; using DiIiS_NA.Core.Extensions;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using DiIiS_NA.Core.Logging;
using DiIiS_NA.Core.Helpers.Math; 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;
using DiIiS_NA.Core.MPQ.FileFormats; 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.Core.Types.SNO;
using DiIiS_NA.GameServer.GSSystem.ActorSystem.Implementations; using DiIiS_NA.GameServer.GSSystem.ActorSystem.Implementations;
using DiIiS_NA.GameServer.GSSystem.ItemsSystem; 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;
using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Inventory;
using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.ACD; using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.ACD;
using DiIiS_NA.GameServer.MessageSystem.Message.Definitions.Artisan; 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.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 static DiIiS_NA.Core.MPQ.FileFormats.GameBalance;
using DiIiS_NA.Core.Extensions;
namespace DiIiS_NA.GameServer.GSSystem.PlayerSystem namespace DiIiS_NA.GameServer.GSSystem.PlayerSystem
{ {
@ -98,21 +102,29 @@ namespace DiIiS_NA.GameServer.GSSystem.PlayerSystem
return _buybackGrid; 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;
} }
public void GrabSomeItems(int GBid, int count) return (_inventoryGrid.TotalItemCount(gBid) + _stashGrid.TotalItemCount(gBid)) >= 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 else
{ {
int inBag = _inventoryGrid.TotalItemCount(GBid); int inBag = _inventoryGrid.TotalItemCount(gBid);
_inventoryGrid.GrabSomeItems(GBid, inBag); _inventoryGrid.GrabSomeItems(gBid, inBag);
count -= inBag; count -= inBag;
_stashGrid.GrabSomeItems(GBid, count); _stashGrid.GrabSomeItems(gBid, count);
} }
} }

View File

@ -2938,31 +2938,51 @@ public class Player : Actor, IMessageConsumer, IUpdateable
return; return;
var recipeDefinition = ItemGenerator.GetRecipeDefinition(trainHelper.TrainRecipeName); var recipeDefinition = ItemGenerator.GetRecipeDefinition(trainHelper.TrainRecipeName);
// 1) Validade the Gold.
if (Inventory.GetGoldAmount() < recipeDefinition.Gold) if (Inventory.GetGoldAmount() < recipeDefinition.Gold)
return; return;
var requiredIngridients = recipeDefinition.Ingredients.Where(x => x.ItemsGBID > 0); // 2) Extract only valid ingredients (actual items).
// FIXME: Inventory.HaveEnough doesn't work for some craft consumables var requiredIngredients = recipeDefinition.Ingredients
var haveEnoughIngredients = requiredIngridients.All(x => Inventory.HaveEnough(x.ItemsGBID, x.Count)); .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) if (!haveEnoughIngredients)
return; return;
Inventory.RemoveGoldAmount(recipeDefinition.Gold); var playerAcc = this.InGameClient.BnetClient.Account.GameAccount;
foreach (var ingr in requiredIngridients)
// FIXME: Inventory.GrabSomeItems doesn't work for some craft consumables
Inventory.GrabSomeItems(ingr.ItemsGBID, ingr.Count);
// 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);
// 5) Advance the artisan's level.
trainHelper.DbRef.Level++; trainHelper.DbRef.Level++;
World.Game.GameDbSession.SessionUpdate(trainHelper.DbRef); World.Game.GameDbSession.SessionUpdate(trainHelper.DbRef);
// 6) Related achievements & criteria.
if (trainHelper.Achievement is not null) if (trainHelper.Achievement is not null)
GrantAchievement(trainHelper.Achievement.Value); GrantAchievement(trainHelper.Achievement.Value);
if (trainHelper.Criteria is not null) if (trainHelper.Criteria is not null)
GrantCriteria(trainHelper.Criteria.Value); GrantCriteria(trainHelper.Criteria.Value);
if (_artisanTrainHelpers.All(x => x.Value.HasMaxLevel)) if (_artisanTrainHelpers.All(x => x.Value.HasMaxLevel))
GrantCriteria(74987249993545); GrantCriteria(74987249993545);
// 7) Notify the Client.
client.SendMessage(new CrafterLevelUpMessage client.SendMessage(new CrafterLevelUpMessage
{ {
Type = trainHelper.Type, Type = trainHelper.Type,
@ -2972,9 +2992,6 @@ public class Player : Actor, IMessageConsumer, IUpdateable
}); });
LoadCrafterData(); LoadCrafterData();
/**/
} }
public void UnlockTransmog(int transmogGBID) public void UnlockTransmog(int transmogGBID)