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 |
| | `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

View File

@ -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;

View File

@ -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<ArtisanType, string> recipeTemplates = new()
{
[ArtisanType.Blacksmith] = "BlackSmith_Train_Level{0}",
[ArtisanType.Jeweler] = "Jeweler_Train_Level{0}",
[ArtisanType.Mystic] = "Mystic_Train_Level{0}"
};
private static readonly Dictionary<ArtisanType, long[]> 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<ArtisanType, long> 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,8 +96,27 @@ 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);
}

View File

@ -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);
}
}

View File

@ -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)