From 9715afee6841d4fb8cbd62c6ee5ef754227db78d Mon Sep 17 00:00:00 2001 From: ElderShell Date: Sun, 3 May 2026 02:19:13 -0600 Subject: [PATCH] fixed gold dupping, empty corpses now reliably show as empty once opened --- src/server/game/Entities/Player/Player.cpp | 121 ++++++++++++++++----- src/server/game/Entities/Player/Player.h | 3 + src/server/game/Handlers/LootHandler.cpp | 44 ++++++++ 3 files changed, 139 insertions(+), 29 deletions(-) diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 8ddb5926c..7973b0193 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -41,6 +41,7 @@ #include "Config.h" #include "CreatureAI.h" #include "DatabaseEnv.h" +#include "DatabaseEnvFwd.h" #include "DisableMgr.h" #include "Formulas.h" #include "GameEventMgr.h" @@ -60,6 +61,7 @@ #include "MapMgr.h" #include "MiscPackets.h" #include "ObjectAccessor.h" +#include "ObjectGuid.h" #include "ObjectMgr.h" #include "OutdoorPvP.h" #include "OutdoorPvPMgr.h" @@ -8079,6 +8081,9 @@ void Player::SendLoot(ObjectGuid guid, LootType loot_type) permission = NONE_PERMISSION; + ObjectGuid corpseGUID = bones->GetGUID(); + CheckCorpseByGUID(corpseGUID); + if (Corpse* bones = ObjectAccessor::GetCorpse(*this, guid)) { if (bones->GetOwnerGUID() != GetGUID()) @@ -8304,11 +8309,16 @@ void Player::LoadLostCorpseLoot(ObjectGuid playerGuid, Loot& loot) return; QueryResult corpseResult = CharacterDatabase.Query( - "SELECT lost_corpse_id, money " + "SELECT lost_corpse_id, money, money_collected " "FROM lost_corpses " "WHERE player_guid = {} " + "AND SQRT(POW(position_x - {}, 2) + POW(position_y - {}, 2) + POW(position_z - {}, 2)) <= 5 " + "AND active = 1 " "ORDER BY lost_corpse_id DESC LIMIT 1", - playerGuid.GetCounter() + playerGuid.GetCounter(), + GetPositionX(), + GetPositionY(), + GetPositionZ() ); if (!corpseResult) @@ -8318,8 +8328,12 @@ void Player::LoadLostCorpseLoot(ObjectGuid playerGuid, Loot& loot) uint32 lostCorpseId = corpseFields[0].Get(); uint32 money = corpseFields[1].Get(); + bool moneyCollected = corpseFields[2].Get(); - loot.gold = money; + if (!moneyCollected) + { + loot.gold = money; + } QueryResult result = CharacterDatabase.Query( "SELECT id, lost_corpse_id, item_entry, count, durability " @@ -8360,6 +8374,80 @@ void Player::LoadLostCorpseLoot(ObjectGuid playerGuid, Loot& loot) } while (result->NextRow()); } +void Player::CheckCorpse(uint32 lostCorpseId, ObjectGuid corpseGuid) +{ + // Check if any items remain unlooted + QueryResult remaining = CharacterDatabase.Query( + "SELECT COUNT(*) FROM lost_corpse_items " + "WHERE lost_corpse_id = {} AND looted = 0", + lostCorpseId + ); + + if (remaining) + { + uint32 count = remaining->Fetch()[0].Get(); + + if (count == 0) + { + QueryResult moneyCheck = CharacterDatabase.Query( + "SELECT money_collected, money FROM lost_corpses " + "WHERE lost_corpse_id = {} OR money = 0", + lostCorpseId + ); + + if (moneyCheck) + { + bool moneyCollected = moneyCheck->Fetch()[0].Get(); + uint32 money = moneyCheck->Fetch()[1].Get(); + + if (moneyCollected || money == 0) + { + // No items left, deactivate corpse + CharacterDatabase.Execute( + "UPDATE lost_corpses SET active = 0 WHERE lost_corpse_id = {}", + lostCorpseId + ); + + ObjectGuid guidToUse = corpseGuid.IsEmpty() ? GetLootGUID() : corpseGuid; + + // Remove lootability in-game + if (Corpse* corpse = ObjectAccessor::GetCorpse(*this, guidToUse)) + { + corpse->RemoveFlag(CORPSE_FIELD_DYNAMIC_FLAGS, CORPSE_DYNFLAG_LOOTABLE); + } + } + } + } + } +} + +void Player::CheckCorpseByGUID(ObjectGuid corpseGuid) +{ + uint32 guid = corpseGuid.GetCounter(); + uint32 playerGUID = GetGUID().GetCounter(); + float player_x = GetPositionX(); + float player_y = GetPositionY(); + float player_z = GetPositionZ(); + + QueryResult result = CharacterDatabase.Query( + "SELECT lost_corpse_id " + "FROM lost_corpses " + "WHERE corpse_guid = {} AND player_guid = {} " + "AND SQRT(POW(position_x - {}, 2) + POW(position_y - {}, 2) + POW(position_z - {}, 2)) <= 5", + guid, + playerGUID, + player_x, + player_y, + player_z + ); + + if (result) + { + uint32 lostCorpseId = result->Fetch()[0].Get(); + CheckCorpse(lostCorpseId, corpseGuid); + } +} + void Player::SendLootError(ObjectGuid guid, LootError error) { WorldPacket data(SMSG_LOOT_RESPONSE, 10); @@ -13801,32 +13889,7 @@ LootItem* Player::StoreLootItem(uint8 lootSlot, Loot* loot, InventoryResult& msg dbId ); - // Check if any items remain unlooted - QueryResult remaining = CharacterDatabase.Query( - "SELECT COUNT(*) FROM lost_corpse_items " - "WHERE lost_corpse_id = {} AND looted = 0", - corpseId - ); - - if (remaining) - { - uint32 count = remaining->Fetch()[0].Get(); - - if (count == 0) - { - // No items left, deactivate corpse - CharacterDatabase.Execute( - "UPDATE lost_corpses SET active = 0 WHERE lost_corpse_id = {}", - corpseId - ); - - // Remove lootability in-game - if (Corpse* corpse = ObjectAccessor::GetCorpse(*this, GetLootGUID())) - { - corpse->RemoveFlag(CORPSE_FIELD_DYNAMIC_FLAGS, CORPSE_DYNFLAG_LOOTABLE); - } - } - } + CheckCorpse(corpseId); } } diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 8b5d358a5..574b08cd3 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1101,6 +1101,9 @@ public: explicit Player(WorldSession* session); ~Player() override; + void CheckCorpse(uint32 lostCorpseId, ObjectGuid corpseGuid = ObjectGuid::Empty); + void CheckCorpseByGUID(ObjectGuid corpseGuid); + void CleanupsBeforeDelete(bool finalCleanup = true) override; void AddToWorld() override; diff --git a/src/server/game/Handlers/LootHandler.cpp b/src/server/game/Handlers/LootHandler.cpp index 8a32702ba..11568d1b4 100644 --- a/src/server/game/Handlers/LootHandler.cpp +++ b/src/server/game/Handlers/LootHandler.cpp @@ -17,12 +17,14 @@ #include "Corpse.h" #include "Creature.h" +#include "DatabaseEnvFwd.h" #include "GameObject.h" #include "Group.h" #include "LootItemStorage.h" #include "LootMgr.h" #include "Object.h" #include "ObjectAccessor.h" +#include "ObjectGuid.h" #include "ObjectMgr.h" #include "Opcodes.h" #include "Player.h" @@ -146,7 +148,46 @@ void WorldSession::HandleLootMoneyOpcode(WorldPacket& /*recvData*/) loot = &bones->loot; shareMoney = false; } + else + { + player->SendLootError(guid, LOOT_ERROR_DIDNT_KILL); + break; + } + QueryResult result = CharacterDatabase.Query( + "SELECT lost_corpse_id, money " + "FROM lost_corpses " + "WHERE corpse_guid = {} AND player_guid = {} " + "AND active = 1 AND money_collected = 0 " + "AND SQRT(POW(position_x - {}, 2) + POW(position_y - {}, 2) + POW(position_z - {}, 2)) <= 5", + bones->GetGUID().GetCounter(), + player->GetGUID().GetCounter(), + player->GetPositionX(), + player->GetPositionY(), + player->GetPositionZ() + ); + + if (result) + { + Field* fields = result->Fetch(); + + uint32 lostCorpseId = fields[0].Get(); + uint32 totalMoney = fields[1].Get(); + + loot->gold = totalMoney; + + CharacterDatabase.Execute( + "UPDATE lost_corpses SET money_collected = 1 " + "WHERE lost_corpse_id = {}", + lostCorpseId + ); + + player->CheckCorpse(lostCorpseId); + } + else + { + loot->gold = 0; + } break; } case HighGuid::Item: @@ -342,6 +383,9 @@ void WorldSession::DoLootRelease(ObjectGuid lguid) loot = &corpse->loot; + ObjectGuid corpseGUID = corpse->GetGUID(); + player->CheckCorpseByGUID(corpseGUID); + // Xinef: Buggs client? (Opening loot after closing) //if (loot->isLooted()) //{