diff options
author | Mikhail Redko <ovitnez@gmail.com> | 2021-10-01 17:33:21 +0300 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2022-03-19 16:55:55 +0100 |
commit | 72a4fccaab4e83801c33c533cbcfdba1c6c43022 (patch) | |
tree | 5703361b82071bd33b551536450a91d83193cf02 | |
parent | e33b3e4a81c64fc7c9918c2c15f66f1f259d1fde (diff) |
Core/Misc: Improvement item durability repair logic. (#26911)
* Core/Misc: Improvement item durability repair logic.
* Move item repair cost calculation logic from Player::DurabilityRepair to Item::CalculateDurabilityRepairCost
* Make item repair cost calculation more congruent with client logic (std::round)
* Do not repair items if player does not have enough money to pay for the repair of all items at once
* Withdrawing money from guild bank only once to avoid spam in the guild bank log and reduce number of transactions in the database
Closes #25937
* Fix build
* Remove unnecessary scope
* Fix visual studio warning and one more check for reliability
(cherry picked from commit 3cef126d0a449219f154a6498d26831f86220dfa)
-rw-r--r-- | src/server/game/Entities/Item/Item.cpp | 40 | ||||
-rw-r--r-- | src/server/game/Entities/Item/Item.h | 2 | ||||
-rw-r--r-- | src/server/game/Entities/Player/Player.cpp | 150 | ||||
-rw-r--r-- | src/server/game/Entities/Player/Player.h | 4 | ||||
-rw-r--r-- | src/server/game/Guilds/Guild.cpp | 9 | ||||
-rw-r--r-- | src/server/game/Guilds/Guild.h | 1 | ||||
-rw-r--r-- | src/server/game/Handlers/NPCHandler.cpp | 2 |
7 files changed, 134 insertions, 74 deletions
diff --git a/src/server/game/Entities/Item/Item.cpp b/src/server/game/Entities/Item/Item.cpp index 7f3039e9d81..252e881936e 100644 --- a/src/server/game/Entities/Item/Item.cpp +++ b/src/server/game/Entities/Item/Item.cpp @@ -43,6 +43,7 @@ #include "StringConvert.h" #include "TradeData.h" #include "UpdateData.h" +#include "World.h" #include "WorldSession.h" #include <sstream> @@ -1282,6 +1283,45 @@ void Item::SetCount(uint32 value) } } +uint64 Item::CalculateDurabilityRepairCost(float discount) const +{ + uint32 maxDurability = m_itemData->MaxDurability; + if (!maxDurability) + return 0; + + uint32 curDurability = m_itemData->Durability; + ASSERT(maxDurability >= curDurability); + + uint32 lostDurability = maxDurability - curDurability; + if (!lostDurability) + return 0; + + ItemTemplate const* itemTemplate = GetTemplate(); + + DurabilityCostsEntry const* durabilityCost = sDurabilityCostsStore.LookupEntry(GetItemLevel(GetOwner())); + if (!durabilityCost) + return 0; + + uint32 durabilityQualityEntryId = (GetQuality() + 1) * 2; + DurabilityQualityEntry const* durabilityQualityEntry = sDurabilityQualityStore.LookupEntry(durabilityQualityEntryId); + if (!durabilityQualityEntry) + return 0; + + uint32 dmultiplier = 0; + if (itemTemplate->GetClass() == ITEM_CLASS_WEAPON) + dmultiplier = durabilityCost->WeaponSubClassCost[itemTemplate->GetSubClass()]; + else if (itemTemplate->GetClass() == ITEM_CLASS_ARMOR) + dmultiplier = durabilityCost->ArmorSubClassCost[itemTemplate->GetSubClass()]; + + uint64 cost = std::round(lostDurability * dmultiplier * durabilityQualityEntry->Data * GetRepairCostMultiplier()); + cost = uint64(cost * discount * sWorld->getRate(RATE_REPAIRCOST)); + + if (cost == 0) // Fix for ITEM_QUALITY_ARTIFACT + cost = 1; + + return cost; +} + bool Item::HasEnchantRequiredSkill(Player const* player) const { // Check all enchants for required skill diff --git a/src/server/game/Entities/Item/Item.h b/src/server/game/Entities/Item/Item.h index 30ef7ffb170..32c81b5d77a 100644 --- a/src/server/game/Entities/Item/Item.h +++ b/src/server/game/Entities/Item/Item.h @@ -258,6 +258,8 @@ class TC_GAME_API Item : public Object void SetInTrade(bool b = true) { mb_in_trade = b; } bool IsInTrade() const { return mb_in_trade; } + uint64 CalculateDurabilityRepairCost(float discount) const; + bool HasEnchantRequiredSkill(Player const* player) const; uint32 GetEnchantRequiredLevel() const; diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index fb950efd189..d0feaf94fa1 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -4619,110 +4619,118 @@ void Player::DurabilityPointLossForEquipSlot(EquipmentSlots slot) DurabilityPointsLoss(pItem, 1); } -uint32 Player::DurabilityRepairAll(bool cost, float discountMod, bool guildBank) +void Player::DurabilityRepairAll(bool takeCost, float discountMod, bool guildBank) { - uint32 TotalCost = 0; + // Collecting all items that can be repaired and repair costs + std::list<std::pair<Item*, uint64>> itemRepairCostStore; + // equipped, backpack, bags itself uint8 inventoryEnd = INVENTORY_SLOT_ITEM_START + GetInventorySlotCount(); for (uint8 i = EQUIPMENT_SLOT_START; i < inventoryEnd; i++) - TotalCost += DurabilityRepair(((INVENTORY_SLOT_BAG_0 << 8) | i), cost, discountMod, guildBank); + if (Item* item = GetItemByPos(((INVENTORY_SLOT_BAG_0 << 8) | i))) + if (uint64 cost = item->CalculateDurabilityRepairCost(discountMod)) + itemRepairCostStore.push_back(std::make_pair(item, cost)); // bank, buyback and keys not repaired // items in inventory bags for (uint8 j = INVENTORY_SLOT_BAG_START; j < INVENTORY_SLOT_BAG_END; j++) for (uint8 i = 0; i < MAX_BAG_SIZE; i++) - TotalCost += DurabilityRepair(((j << 8) | i), cost, discountMod, guildBank); - return TotalCost; -} + if (Item* item = GetItemByPos(((j << 8) | i))) + if (uint64 cost = item->CalculateDurabilityRepairCost(discountMod)) + itemRepairCostStore.push_back(std::make_pair(item, cost)); -uint32 Player::DurabilityRepair(uint16 pos, bool cost, float discountMod, bool guildBank) -{ - Item* item = GetItemByPos(pos); + // Handling a free repair case - just repair every item without taking cost. + if (!takeCost) + { + for (auto const& [item, cost] : itemRepairCostStore) + DurabilityRepair(item->GetPos(), false, 0.f); - uint32 TotalCost = 0; - if (!item) - return TotalCost; + return; + } - uint32 maxDurability = item->m_itemData->MaxDurability; - if (!maxDurability) - return TotalCost; + 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. - uint32 curDurability = item->m_itemData->Durability; + Guild* guild = GetGuild(); + if (!guild) + return; // silent return, client shouldn't display this button for players without guild. - if (cost) - { - uint32 LostDurability = maxDurability - curDurability; - if (LostDurability>0) + uint64 const availableGuildMoney = guild->GetMemberAvailableMoneyForRepairItems(GetGUID()); + if (availableGuildMoney == 0) + return; + + // Sort the items by repair cost from lowest to highest + itemRepairCostStore.sort([](auto const& a, auto const& b) -> bool { return a.second < b.second; }); + + // 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 + uint64 totalCost = 0; + + for (auto const& [item, cost] : itemRepairCostStore) { - ItemTemplate const* ditemProto = item->GetTemplate(); + uint64 newTotalCost = totalCost + cost; + if (newTotalCost > availableGuildMoney || newTotalCost > MAX_MONEY_AMOUNT) + break; - DurabilityCostsEntry const* dcost = sDurabilityCostsStore.LookupEntry(ditemProto->GetBaseItemLevel()); - if (!dcost) - { - TC_LOG_ERROR("entities.player.items", "Player::DurabilityRepair: Player '%s' (%s) tried to repair an item (ItemID: %u) with invalid item level %u", - GetName().c_str(), GetGUID().ToString().c_str(), ditemProto->GetId(), ditemProto->GetBaseItemLevel()); - return TotalCost; - } + totalCost = newTotalCost; - uint32 dQualitymodEntryId = (ditemProto->GetQuality() + 1) * 2; - DurabilityQualityEntry const* dQualitymodEntry = sDurabilityQualityStore.LookupEntry(dQualitymodEntryId); - if (!dQualitymodEntry) - { - TC_LOG_ERROR("entities.player.items", "Player::DurabilityRepair: Player '%s' (%s) tried to repair an item (ItemID: %u) with invalid QualitymodEntry %u", - GetName().c_str(), GetGUID().ToString().c_str(), ditemProto->GetId(), dQualitymodEntryId); - return TotalCost; - } + // Repair item without taking cost. We'll do it later. + DurabilityRepair(item->GetPos(), false, 0.f); + } - uint32 dmultiplier = 0; - if (ditemProto->GetClass() == ITEM_CLASS_WEAPON) - dmultiplier = dcost->WeaponSubClassCost[ditemProto->GetSubClass()]; - else if (ditemProto->GetClass() == ITEM_CLASS_ARMOR) - dmultiplier = dcost->ArmorSubClassCost[ditemProto->GetSubClass()]; + // 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. - uint32 costs = uint32(LostDurability * dmultiplier * double(dQualitymodEntry->Data) * item->GetRepairCostMultiplier()); + uint64 totalCost = 0; + for (auto const& [item, cost] : itemRepairCostStore) + totalCost += cost; - costs = uint32(costs * discountMod * sWorld->getRate(RATE_REPAIRCOST)); + if (!HasEnoughMoney(totalCost)) + return; // silent return, client should display error by itself and not send opcode. - if (costs == 0) //fix for ITEM_QUALITY_ARTIFACT - costs = 1; + ModifyMoney(-int32(totalCost)); - if (guildBank) - { - if (!GetGuildId()) - { - TC_LOG_DEBUG("entities.player.items", "Player::DurabilityRepair: Player '%s' (%s) tried to repair item in a guild bank but is not member of a guild", - GetName().c_str(), GetGUID().ToString().c_str()); - return TotalCost; - } + // Payment for repair has already been taken, so just repair every item without taking cost. + for (auto const& [item, cost] : itemRepairCostStore) + DurabilityRepair(item->GetPos(), false, 0.f); + } +} - Guild* guild = sGuildMgr->GetGuildById(GetGuildId()); - if (!guild) - return TotalCost; +void Player::DurabilityRepair(uint16 pos, bool takeCost, float discountMod) +{ + Item* item = GetItemByPos(pos); + if (!item) + return; - if (!guild->HandleMemberWithdrawMoney(GetSession(), costs, true)) - return TotalCost; + if (takeCost) + { + uint64 cost = item->CalculateDurabilityRepairCost(discountMod); - TotalCost = costs; - } - else if (!HasEnoughMoney(uint64(costs))) - { - TC_LOG_DEBUG("entities.player.items", "Player::DurabilityRepair: Player '%s' (%s) has not enough money to repair item", - GetName().c_str(), GetGUID().ToString().c_str()); - return TotalCost; - } - else - ModifyMoney(-int64(costs)); + if (!HasEnoughMoney(cost)) + { + TC_LOG_DEBUG("entities.player.items", "Player::DurabilityRepair: Player '%s' (%s) has not enough money to repair item", + GetName().c_str(), GetGUID().ToString().c_str()); + return; } + + ModifyMoney(-int32(cost)); } - item->SetDurability(maxDurability); + bool isBroken = item->IsBroken(); + + item->SetDurability(item->m_itemData->MaxDurability); item->SetState(ITEM_CHANGED, this); // reapply mods for total broken and repaired item if equipped - if (IsEquipmentPos(pos) && !curDurability) + if (IsEquipmentPos(pos) && isBroken) _ApplyItemMods(item, pos & 255, true); - return TotalCost; } void Player::RepopAtGraveyard() diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index c70df1ecb50..6684786987d 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -2148,8 +2148,8 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> void DurabilityPointsLossAll(int32 points, bool inventory); void DurabilityPointsLoss(Item* item, int32 points); void DurabilityPointLossForEquipSlot(EquipmentSlots slot); - uint32 DurabilityRepairAll(bool cost, float discountMod, bool guildBank); - uint32 DurabilityRepair(uint16 pos, bool cost, float discountMod, bool guildBank); + void DurabilityRepairAll(bool takeCost, float discountMod, bool guildBank); + void DurabilityRepair(uint16 pos, bool takeCost, float discountMod); void UpdateMirrorTimers(); void StopMirrorTimers(); diff --git a/src/server/game/Guilds/Guild.cpp b/src/server/game/Guilds/Guild.cpp index 0f79f57358c..83c8894fc82 100644 --- a/src/server/game/Guilds/Guild.cpp +++ b/src/server/game/Guilds/Guild.cpp @@ -2884,6 +2884,15 @@ bool Guild::IsMember(ObjectGuid guid) const return m_members.find(guid) != m_members.end(); } +uint64 Guild::GetMemberAvailableMoneyForRepairItems(ObjectGuid guid) const +{ + Member const* member = GetMember(guid); + if (!member) + return 0; + + return std::min(m_bankMoney, static_cast<uint64>(_GetMemberRemainingMoney(*member))); +} + // Bank (items move) void Guild::SwapItems(Player* player, uint8 tabId, uint8 slotId, uint8 destTabId, uint8 destSlotId, uint32 splitedAmount) { diff --git a/src/server/game/Guilds/Guild.h b/src/server/game/Guilds/Guild.h index d02feb29fed..179ee6073de 100644 --- a/src/server/game/Guilds/Guild.h +++ b/src/server/game/Guilds/Guild.h @@ -824,6 +824,7 @@ class TC_GAME_API Guild bool ChangeMemberRank(CharacterDatabaseTransaction trans, ObjectGuid guid, GuildRankId newRank); bool IsMember(ObjectGuid guid) const; uint32 GetMembersCount() const { return uint32(m_members.size()); } + uint64 GetMemberAvailableMoneyForRepairItems(ObjectGuid guid) const; // Bank void SwapItems(Player* player, uint8 tabId, uint8 slotId, uint8 destTabId, uint8 destSlotId, uint32 splitedAmount); diff --git a/src/server/game/Handlers/NPCHandler.cpp b/src/server/game/Handlers/NPCHandler.cpp index 4366175f1b7..d19a83da7a7 100644 --- a/src/server/game/Handlers/NPCHandler.cpp +++ b/src/server/game/Handlers/NPCHandler.cpp @@ -583,7 +583,7 @@ void WorldSession::HandleRepairItemOpcode(WorldPackets::Item::RepairItem& packet Item* item = _player->GetItemByGuid(packet.ItemGUID); if (item) - _player->DurabilityRepair(item->GetPos(), true, discountMod, packet.UseGuildBank); + _player->DurabilityRepair(item->GetPos(), true, discountMod); } else { |