From 44b70d8db9de541b285c9433ee662c511e4d434a Mon Sep 17 00:00:00 2001 From: hondacrx Date: Sun, 29 May 2022 23:23:54 -0400 Subject: [PATCH] Core/Misc: Improvement item durability repair logic. Port From (https://github.com/TrinityCore/TrinityCore/commit/72a4fccaab4e83801c33c533cbcfdba1c6c43022) --- Source/Game/Entities/Item/Item.cs | 39 ++++ Source/Game/Entities/Player/Player.Items.cs | 190 +++++++++++--------- Source/Game/Guilds/Guild.cs | 9 + Source/Game/Handlers/NPCHandler.cs | 2 +- 4 files changed, 156 insertions(+), 84 deletions(-) diff --git a/Source/Game/Entities/Item/Item.cs b/Source/Game/Entities/Item/Item.cs index 87eb38d83..70af94ce1 100644 --- a/Source/Game/Entities/Item/Item.cs +++ b/Source/Game/Entities/Item/Item.cs @@ -850,6 +850,45 @@ namespace Game.Entities } } + public ulong CalculateDurabilityRepairCost(float discount) + { + uint maxDurability = m_itemData.MaxDurability; + if (maxDurability == 0) + return 0; + + uint curDurability = m_itemData.Durability; + Cypher.Assert(maxDurability >= curDurability); + + uint lostDurability = maxDurability - curDurability; + if (lostDurability == 0) + return 0; + + ItemTemplate itemTemplate = GetTemplate(); + + var durabilityCost = CliDB.DurabilityCostsStorage.LookupByKey(GetItemLevel(GetOwner())); + if (durabilityCost == null) + return 0; + + uint durabilityQualityEntryId = ((uint)GetQuality() + 1) * 2; + var durabilityQualityEntry = CliDB.DurabilityQualityStorage.LookupByKey(durabilityQualityEntryId); + if (durabilityQualityEntry == null) + return 0; + + uint dmultiplier = 0; + if (itemTemplate.GetClass() == ItemClass.Weapon) + dmultiplier = durabilityCost.WeaponSubClassCost[itemTemplate.GetSubClass()]; + else if (itemTemplate.GetClass() == ItemClass.Armor) + dmultiplier = durabilityCost.ArmorSubClassCost[itemTemplate.GetSubClass()]; + + ulong cost = (ulong)Math.Round(lostDurability * dmultiplier * durabilityQualityEntry.Data * GetRepairCostMultiplier()); + cost = (ulong)(cost * discount * WorldConfig.GetFloatValue(WorldCfg.RateRepaircost)); + + if (cost == 0) // Fix for ITEM_QUALITY_ARTIFACT + cost = 1; + + return cost; + } + bool HasEnchantRequiredSkill(Player player) { // Check all enchants for required skill diff --git a/Source/Game/Entities/Player/Player.Items.cs b/Source/Game/Entities/Player/Player.Items.cs index 1e59305ca..3709e9465 100644 --- a/Source/Game/Entities/Player/Player.Items.cs +++ b/Source/Game/Entities/Player/Player.Items.cs @@ -385,102 +385,126 @@ namespace Game.Entities if (pItem != null) DurabilityPointsLoss(pItem, 1); } - public uint DurabilityRepairAll(bool cost, float discountMod, bool guildBank) + public void DurabilityRepairAll(bool takeCost, float discountMod, bool guildBank) { - uint TotalCost = 0; + // Collecting all items that can be repaired and repair costs + List<(Item item, ulong cost)> itemRepairCostStore = new(); + // equipped, backpack, bags itself int inventoryEnd = InventorySlots.ItemStart + GetInventorySlotCount(); for (byte i = EquipmentSlot.Start; i < inventoryEnd; i++) - TotalCost += DurabilityRepair((ushort)((InventorySlots.Bag0 << 8) | i), cost, discountMod, guildBank); - - // items in inventory bags - for (byte j = InventorySlots.BagStart; j < InventorySlots.BagEnd; j++) - for (byte i = 0; i < ItemConst.MaxBagSize; i++) - TotalCost += DurabilityRepair((ushort)((j << 8) | i), cost, discountMod, guildBank); - return TotalCost; - } - public uint DurabilityRepair(ushort pos, bool cost, float discountMod, bool guildBank) - { - Item item = GetItemByPos(pos); - - uint TotalCost = 0; - if (item == null) - return TotalCost; - - uint maxDurability = item.m_itemData.MaxDurability; - if (maxDurability == 0) - return TotalCost; - - uint curDurability = item.m_itemData.Durability; - - if (cost) { - uint LostDurability = maxDurability - curDurability; - if (LostDurability > 0) + Item item = GetItemByPos((ushort)((InventorySlots.Bag0 << 8) | i)); + if (item != null) { - ItemTemplate ditemProto = item.GetTemplate(); - - DurabilityCostsRecord dcost = CliDB.DurabilityCostsStorage.LookupByKey(ditemProto.GetBaseItemLevel()); - if (dcost == null) - { - Log.outError(LogFilter.Player, "RepairDurability: Wrong item lvl {0}", ditemProto.GetBaseItemLevel()); - return TotalCost; - } - - uint dQualitymodEntryId = (uint)(ditemProto.GetQuality() + 1) * 2; - DurabilityQualityRecord dQualitymodEntry = CliDB.DurabilityQualityStorage.LookupByKey(dQualitymodEntryId); - if (dQualitymodEntry == null) - { - Log.outError(LogFilter.Player, "RepairDurability: Wrong dQualityModEntry {0}", dQualitymodEntryId); - return TotalCost; - } - - uint dmultiplier = 0; - if (ditemProto.GetClass() == ItemClass.Weapon) - dmultiplier = dcost.WeaponSubClassCost[ditemProto.GetSubClass()]; - else if (ditemProto.GetClass() == ItemClass.Armor) - dmultiplier = dcost.ArmorSubClassCost[ditemProto.GetSubClass()]; - - uint costs = (uint)(LostDurability * dmultiplier * (double)dQualitymodEntry.Data * item.GetRepairCostMultiplier()); - costs = (uint)(costs * discountMod * WorldConfig.GetFloatValue(WorldCfg.RateRepaircost)); - - if (costs == 0) //fix for ITEM_QUALITY_ARTIFACT - costs = 1; - - if (guildBank) - { - if (GetGuildId() == 0) - { - Log.outDebug(LogFilter.Player, "You are not member of a guild"); - return TotalCost; - } - - var guild = Global.GuildMgr.GetGuildById(GetGuildId()); - if (guild == null) - return TotalCost; - - if (!guild.HandleMemberWithdrawMoney(GetSession(), costs, true)) - return TotalCost; - - TotalCost = costs; - } - else if (!HasEnoughMoney(costs)) - { - Log.outDebug(LogFilter.Player, "You do not have enough money"); - return TotalCost; - } - else - ModifyMoney(-costs); + ulong cost = item.CalculateDurabilityRepairCost(discountMod); + if (cost != 0) + itemRepairCostStore.Add((item, cost)); } } - item.SetDurability(maxDurability); + // items in inventory bags + for (byte j = InventorySlots.BagStart; j < InventorySlots.BagEnd; j++) + { + for (byte i = 0; i < ItemConst.MaxBagSize; i++) + { + Item item = GetItemByPos((ushort)((j << 8) | i)); + if (item != null) + { + ulong cost = item.CalculateDurabilityRepairCost(discountMod); + if (cost != 0) + itemRepairCostStore.Add((item, cost)); + } + } + } + + // Handling a free repair case - just repair every item without taking cost. + if (!takeCost) + { + foreach (var (item, _) in itemRepairCostStore) + DurabilityRepair(item.GetPos(), false, 0.0f); + return; + } + + if (guildBank) + { + // Handling a repair for guild money case. + // We have to repair items one by one until the guild bank has enough money available for withdrawal or until all items are repaired. + + Guild guild = GetGuild(); + if (guild == null) + return; // silent return, client shouldn't display this button for players without guild. + + ulong availableGuildMoney = guild.GetMemberAvailableMoneyForRepairItems(GetGUID()); + if (availableGuildMoney == 0) + return; + + // Sort the items by repair cost from lowest to highest + itemRepairCostStore.OrderByDescending(a => a.cost); + + // We must calculate total repair cost and take money once to avoid spam in the guild bank log and reduce number of transactions in the database + ulong totalCost = 0; + + foreach (var (item, cost) in itemRepairCostStore) + { + ulong newTotalCost = totalCost + cost; + if (newTotalCost > availableGuildMoney || newTotalCost > PlayerConst.MaxMoneyAmount) + break; + + totalCost = newTotalCost; + // Repair item without taking cost. We'll do it later. + DurabilityRepair(item.GetPos(), false, 0.0f); + } + // Take money for repairs from the guild bank + guild.HandleMemberWithdrawMoney(GetSession(), totalCost, true); + } + else + { + // Handling a repair for player's money case. + // Unlike repairing for guild money, in this case we must first check if player has enough money to repair all the items at once. + + ulong totalCost = 0; + foreach (var (_, cost) in itemRepairCostStore) + totalCost += cost; + + if (!HasEnoughMoney(totalCost)) + return; // silent return, client should display error by itself and not send opcode. + + ModifyMoney(-(int)totalCost); + + // Payment for repair has already been taken, so just repair every item without taking cost. + foreach (var (item, cost) in itemRepairCostStore) + DurabilityRepair(item.GetPos(), false, 0.0f); + } + } + + public void DurabilityRepair(ushort pos, bool takeCost, float discountMod) + { + Item item = GetItemByPos(pos); + if (item == null) + return; + + + if (takeCost) + { + ulong cost = item.CalculateDurabilityRepairCost(discountMod); + if (!HasEnoughMoney(cost)) + { + Log.outDebug(LogFilter.PlayerItems, $"Player::DurabilityRepair: Player '{GetName()}' ({GetGUID()}) has not enough money to repair item"); + return; + } + + ModifyMoney(-(int)cost); + } + + bool isBroken = item.IsBroken(); + + item.SetDurability(item.m_itemData.MaxDurability); item.SetState(ItemUpdateState.Changed, this); // reapply mods for total broken and repaired item if equipped - if (IsEquipmentPos(pos) && curDurability == 0) + if (IsEquipmentPos(pos) && isBroken) _ApplyItemMods(item, (byte)(pos & 255), true); - return TotalCost; } //Store Item diff --git a/Source/Game/Guilds/Guild.cs b/Source/Game/Guilds/Guild.cs index 85b5a74f7..acc2ce7f2 100644 --- a/Source/Game/Guilds/Guild.cs +++ b/Source/Game/Guilds/Guild.cs @@ -1758,6 +1758,15 @@ namespace Game.Guilds return m_members.ContainsKey(guid); } + public ulong GetMemberAvailableMoneyForRepairItems(ObjectGuid guid) + { + Member member = GetMember(guid); + if (member == null) + return 0; + + return Math.Min(m_bankMoney, (ulong)_GetMemberRemainingMoney(member)); + } + public void SwapItems(Player player, byte tabId, byte slotId, byte destTabId, byte destSlotId, uint splitedAmount) { if (tabId >= _GetPurchasedTabsSize() || slotId >= GuildConst.MaxBankSlots || diff --git a/Source/Game/Handlers/NPCHandler.cs b/Source/Game/Handlers/NPCHandler.cs index cd6d00866..1088475d3 100644 --- a/Source/Game/Handlers/NPCHandler.cs +++ b/Source/Game/Handlers/NPCHandler.cs @@ -570,7 +570,7 @@ namespace Game Item item = GetPlayer().GetItemByGuid(packet.ItemGUID); if (item) - GetPlayer().DurabilityRepair(item.GetPos(), true, discountMod, packet.UseGuildBank); + GetPlayer().DurabilityRepair(item.GetPos(), true, discountMod); } else {