aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/server/game/Entities/Item/Item.cpp48
-rw-r--r--src/server/game/Entities/Item/Item.h2
-rw-r--r--src/server/game/Entities/Player/Player.cpp158
-rw-r--r--src/server/game/Entities/Player/Player.h4
-rw-r--r--src/server/game/Guilds/Guild.cpp9
-rw-r--r--src/server/game/Guilds/Guild.h1
-rw-r--r--src/server/game/Handlers/NPCHandler.cpp2
7 files changed, 142 insertions, 82 deletions
diff --git a/src/server/game/Entities/Item/Item.cpp b/src/server/game/Entities/Item/Item.cpp
index 761d7ba5f64..e555d02eef4 100644
--- a/src/server/game/Entities/Item/Item.cpp
+++ b/src/server/game/Entities/Item/Item.cpp
@@ -35,6 +35,7 @@
#include "Player.h"
#include "TradeData.h"
#include "UpdateData.h"
+#include "World.h"
#include "WorldPacket.h"
#include "WorldSession.h"
@@ -742,6 +743,53 @@ bool Item::CanBeTraded(bool mail, bool trade) const
return true;
}
+uint32 Item::CalculateDurabilityRepairCost(float discount) const
+{
+ uint32 maxDurability = GetUInt32Value(ITEM_FIELD_MAXDURABILITY);
+ if (!maxDurability)
+ return 0;
+
+ uint32 curDurability = GetUInt32Value(ITEM_FIELD_DURABILITY);
+ ASSERT(maxDurability >= curDurability);
+
+ uint32 lostDurability = maxDurability - curDurability;
+ if (!lostDurability)
+ return 0;
+
+ ItemTemplate const* itemTemplate = GetTemplate();
+
+ DurabilityCostsEntry const* durabilityCost = sDurabilityCostsStore.LookupEntry(itemTemplate->ItemLevel);
+ if (!durabilityCost)
+ return 0;
+
+ uint32 durabilityQualityEntryId = (itemTemplate->Quality + 1) * 2;
+ DurabilityQualityEntry const* durabilityQualityEntry = sDurabilityQualityStore.LookupEntry(durabilityQualityEntryId);
+ if (!durabilityQualityEntry)
+ return 0;
+
+ uint32 dmultiplier;
+ switch (itemTemplate->Class)
+ {
+ case ITEM_CLASS_WEAPON:
+ dmultiplier = durabilityCost->WeaponSubClassCost[itemTemplate->SubClass];
+ break;
+ case ITEM_CLASS_ARMOR:
+ dmultiplier = durabilityCost->ArmorSubClassCost[itemTemplate->SubClass];
+ break;
+ default:
+ dmultiplier = 0;
+ break;
+ }
+
+ uint32 cost = static_cast<uint32>(std::round(lostDurability * dmultiplier * double(durabilityQualityEntry->Data)));
+ cost = uint32(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 c2c6629bcac..450b052d90b 100644
--- a/src/server/game/Entities/Item/Item.h
+++ b/src/server/game/Entities/Item/Item.h
@@ -107,6 +107,8 @@ class TC_GAME_API Item : public Object
void SetInTrade(bool b = true) { mb_in_trade = b; }
bool IsInTrade() const { return mb_in_trade; }
+ uint32 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 212b3e0bb29..a309f972ac0 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -4874,117 +4874,117 @@ 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*, uint32>> itemRepairCostStore;
+
// equipped, backpack, bags itself
for (uint8 i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; i++)
- TotalCost += DurabilityRepair(((INVENTORY_SLOT_BAG_0 << 8) | i), cost, discountMod, guildBank);
+ if (Item* item = GetItemByPos(((INVENTORY_SLOT_BAG_0 << 8) | i)))
+ if (uint32 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 (uint32 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;
+ }
+
+ 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 maxDurability = item->GetUInt32Value(ITEM_FIELD_MAXDURABILITY);
- if (!maxDurability)
- return TotalCost;
+ Guild* guild = GetGuild();
+ if (!guild)
+ return; // silent return, client shouldn't display this button for players without guild.
- uint32 curDurability = item->GetUInt32Value(ITEM_FIELD_DURABILITY);
+ uint64 const availableGuildMoney = guild->GetMemberAvailableMoneyForRepairItems(GetGUID());
+ if (availableGuildMoney == 0)
+ return;
- if (cost)
- {
- uint32 LostDurability = maxDurability - curDurability;
- if (LostDurability>0)
+ // 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
+ uint32 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->ItemLevel);
- 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->ItemId, ditemProto->ItemLevel);
- return TotalCost;
- }
+ totalCost = static_cast<uint32>(newTotalCost);
- uint32 dQualitymodEntryId = (ditemProto->Quality+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->ItemId, dQualitymodEntryId);
- return TotalCost;
- }
+ // Repair item without taking cost. We'll do it later.
+ DurabilityRepair(item->GetPos(), false, 0.f);
+ }
- uint32 dmultiplier;
- switch (ditemProto->Class)
- {
- case ITEM_CLASS_WEAPON:
- dmultiplier = dcost->WeaponSubClassCost[ditemProto->SubClass];
- break;
- case ITEM_CLASS_ARMOR:
- dmultiplier = dcost->ArmorSubClassCost[ditemProto->SubClass];
- break;
- default:
- dmultiplier = 0;
- break;
- }
+ // 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));
+ uint32 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() == 0)
- {
- 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)
+ {
+ uint32 cost = item->CalculateDurabilityRepairCost(discountMod);
- TotalCost = costs;
- }
- else if (!HasEnoughMoney(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(-int32(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->SetUInt32Value(ITEM_FIELD_DURABILITY, maxDurability);
+ bool isBroken = item->IsBroken();
+
+ item->SetUInt32Value(ITEM_FIELD_DURABILITY, item->GetUInt32Value(ITEM_FIELD_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 b0ceb39ef1f..695dfbb69da 100644
--- a/src/server/game/Entities/Player/Player.h
+++ b/src/server/game/Entities/Player/Player.h
@@ -1717,8 +1717,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 690ecc14e52..01d1bfc3ae4 100644
--- a/src/server/game/Guilds/Guild.cpp
+++ b/src/server/game/Guilds/Guild.cpp
@@ -2347,6 +2347,15 @@ bool Guild::ChangeMemberRank(CharacterDatabaseTransaction trans, ObjectGuid guid
return false;
}
+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 5b8b6d38ac8..ba11f5fb470 100644
--- a/src/server/game/Guilds/Guild.h
+++ b/src/server/game/Guilds/Guild.h
@@ -716,6 +716,7 @@ class TC_GAME_API Guild
bool AddMember(CharacterDatabaseTransaction trans, ObjectGuid guid, uint8 rankId = GUILD_RANK_NONE);
void DeleteMember(CharacterDatabaseTransaction trans, ObjectGuid guid, bool isDisbanding = false, bool isKicked = false, bool canDeleteGuild = false);
bool ChangeMemberRank(CharacterDatabaseTransaction trans, ObjectGuid guid, uint8 newRank);
+ 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 efbad00db4a..dbc84d07225 100644
--- a/src/server/game/Handlers/NPCHandler.cpp
+++ b/src/server/game/Handlers/NPCHandler.cpp
@@ -734,7 +734,7 @@ void WorldSession::HandleRepairItemOpcode(WorldPacket& recvData)
Item* item = _player->GetItemByGuid(itemGUID);
if (item)
- _player->DurabilityRepair(item->GetPos(), true, discountMod, guildBank != 0);
+ _player->DurabilityRepair(item->GetPos(), true, discountMod);
}
else
{