mirror of
https://github.com/brighton-chi/mod-aoe-loot.git
synced 2026-01-13 00:58:34 +00:00
772 lines
22 KiB
C++
772 lines
22 KiB
C++
#include "aoe_loot.h"
|
|
#include "ScriptMgr.h"
|
|
#include "LootMgr.h"
|
|
#include "ServerScript.h"
|
|
#include "WorldSession.h"
|
|
#include "WorldPacket.h"
|
|
#include "Player.h"
|
|
#include "Chat.h"
|
|
#include "ChatCommand.h"
|
|
#include "ChatCommandArgs.h"
|
|
#include "Creature.h"
|
|
#include "Config.h"
|
|
#include "Log.h"
|
|
#include "Map.h"
|
|
#include "Corpse.h"
|
|
#include "Group.h"
|
|
#include "ObjectMgr.h"
|
|
#include <mutex>
|
|
#include <set>
|
|
|
|
using namespace Acore::ChatCommands;
|
|
using namespace WorldPackets;
|
|
|
|
// Thread-safe player AoE loot preference storage (renamed)
|
|
std::map<uint64, bool> playerAoeLootPreferences;
|
|
std::mutex AoeLootPreferencesMutex;
|
|
|
|
// Helper function to check if loot system module is enabled
|
|
bool IsAoeLootModuleEnabled()
|
|
{
|
|
return sConfigMgr->GetOption<bool>("AoeLoot.EnableMod", true);
|
|
}
|
|
|
|
// Helper function to get configured loot method from config value
|
|
LootMethod GetLootMethodFromConfig(uint32 configValue)
|
|
{
|
|
switch (configValue)
|
|
{
|
|
case 0: return FREE_FOR_ALL;
|
|
case 1: return ROUND_ROBIN;
|
|
case 2: return MASTER_LOOT;
|
|
case 3: return GROUP_LOOT;
|
|
case 4: return NEED_BEFORE_GREED;
|
|
default: return GROUP_LOOT;
|
|
}
|
|
}
|
|
|
|
// Helper function to apply default loot settings to a group
|
|
void ApplyDefaultLootSettings(Group* group, Player* leaderOrPlayer)
|
|
{
|
|
if (!group || !leaderOrPlayer)
|
|
return;
|
|
|
|
uint32 defaultLootMethodConfig = sConfigMgr->GetOption<uint32>("AoeLoot.DefaultLootMethod", 3);
|
|
LootMethod lootMethod = GetLootMethodFromConfig(defaultLootMethodConfig);
|
|
group->SetLootMethod(lootMethod);
|
|
|
|
if (lootMethod == NEED_BEFORE_GREED || lootMethod == GROUP_LOOT)
|
|
{
|
|
uint32 defaultLootThreshold = sConfigMgr->GetOption<uint32>("AoeLoot.DefaultLootThreshold", 2);
|
|
group->SetLootThreshold(ItemQualities(defaultLootThreshold));
|
|
}
|
|
|
|
if (lootMethod == MASTER_LOOT)
|
|
{
|
|
group->SetMasterLooterGuid(leaderOrPlayer->GetGUID());
|
|
}
|
|
}
|
|
|
|
// Define the roll vote enum value - use the correct RollVote enum
|
|
#ifndef NOT_EMITED_YET
|
|
#define NOT_EMITED_YET RollVote(0)
|
|
#endif
|
|
|
|
// Helper function to check if AOE loot is enabled for current context
|
|
bool AoeLootCommandScript::IsAoeLootEnabledForPlayer(Player* player)
|
|
{
|
|
uint32 AoeLootMode = sConfigMgr->GetOption<uint32>("AoeLoot.EnableAOELoot", 2);
|
|
|
|
switch (AoeLootMode)
|
|
{
|
|
case 0: // Disabled
|
|
return false;
|
|
|
|
case 1: // Enabled for solo play only
|
|
return !player->GetGroup();
|
|
|
|
case 2: // Enabled for both solo and group play
|
|
return true;
|
|
|
|
default: // Enabled for both solo and group play
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool AoeLootServer::CanPacketReceive(WorldSession* session, WorldPacket& packet)
|
|
{
|
|
if (!IsAoeLootModuleEnabled())
|
|
return true;
|
|
|
|
if (packet.GetOpcode() == CMSG_LOOT)
|
|
{
|
|
Player* player = session->GetPlayer();
|
|
if (!player)
|
|
return true;
|
|
|
|
// Check if AOE loot is enabled for this player's context
|
|
if (!AoeLootCommandScript::IsAoeLootEnabledForPlayer(player))
|
|
return true;
|
|
|
|
// Additional safety checks
|
|
if (!player->IsInWorld() || player->isDead())
|
|
return true;
|
|
|
|
// Extract target GUID from loot packet to determine loot type
|
|
ObjectGuid targetGuid;
|
|
packet >> targetGuid;
|
|
|
|
// Only trigger AOE loot for CREATURE corpses (not other loot types)
|
|
if (Creature* creature = player->GetMap()->GetCreature(targetGuid))
|
|
{
|
|
// Skip if creature is alive (pickpocketing)
|
|
if (creature->IsAlive())
|
|
{
|
|
if (player->IsClass(CLASS_ROGUE, CLASS_CONTEXT_ABILITY) &&
|
|
creature->loot.loot_type == LOOT_PICKPOCKETING)
|
|
{
|
|
// This is pickpocketing - let normal loot proceed
|
|
return true;
|
|
}
|
|
// For other live creature interactions, don't trigger AOE
|
|
return true;
|
|
}
|
|
|
|
// Only trigger AOE for dead creature corpses
|
|
if (!creature->isDead())
|
|
return true;
|
|
|
|
// Proceed with AOE loot for dead creatures
|
|
}
|
|
else
|
|
{
|
|
// Target is NOT a creature - don't trigger AOE loot
|
|
// This covers: GameObjects, Items, Corpses (player), etc.
|
|
return true;
|
|
}
|
|
|
|
uint64 guid = player->GetGUID().GetRawValue();
|
|
|
|
// Check if player has explicitly disabled AOE loot (thread-safe)
|
|
{
|
|
std::lock_guard<std::mutex> lock(AoeLootPreferencesMutex);
|
|
auto it = playerAoeLootPreferences.find(guid);
|
|
if (it != playerAoeLootPreferences.end() && !it->second)
|
|
{
|
|
// Let normal looting proceed
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Only trigger AOE loot if player is not already looting
|
|
if (player->GetLootGUID().IsEmpty())
|
|
{
|
|
ChatHandler handler(player->GetSession());
|
|
handler.ParseCommands(".AoeLoot lootall");
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
ChatCommandTable AoeLootCommandScript::GetCommands() const
|
|
{
|
|
static ChatCommandTable AoeLootSubCommandTable =
|
|
{
|
|
{ "lootall", TriggerAoeLootCommand, SEC_PLAYER, Console::No },
|
|
{ "on", EnableAoeLootCommand, SEC_PLAYER, Console::No },
|
|
{ "off", DisableAoeLootCommand, SEC_PLAYER, Console::No }
|
|
};
|
|
|
|
static ChatCommandTable AoeLootCommandTable =
|
|
{
|
|
{ "AoeLoot", AoeLootSubCommandTable }
|
|
};
|
|
|
|
return AoeLootCommandTable;
|
|
}
|
|
|
|
bool AoeLootCommandScript::EnableAoeLootCommand(ChatHandler* handler, Optional<std::string>)
|
|
{
|
|
if (!IsAoeLootModuleEnabled())
|
|
return true;
|
|
|
|
Player* player = handler->GetSession()->GetPlayer();
|
|
if (!player || !IsAoeLootEnabledForPlayer(player))
|
|
return true;
|
|
|
|
uint64 guid = player->GetGUID().GetRawValue();
|
|
{
|
|
std::lock_guard<std::mutex> lock(AoeLootPreferencesMutex);
|
|
playerAoeLootPreferences[guid] = true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool AoeLootCommandScript::DisableAoeLootCommand(ChatHandler* handler, Optional<std::string>)
|
|
{
|
|
if (!IsAoeLootModuleEnabled())
|
|
return true;
|
|
|
|
Player* player = handler->GetSession()->GetPlayer();
|
|
if (!player)
|
|
return true;
|
|
|
|
uint64 guid = player->GetGUID().GetRawValue();
|
|
|
|
// Thread-safe update
|
|
{
|
|
std::lock_guard<std::mutex> lock(AoeLootPreferencesMutex);
|
|
playerAoeLootPreferences[guid] = false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AoeLootCommandScript::ValidateLootingDistance(Player* player, ObjectGuid lguid, float maxDistance)
|
|
{
|
|
if (!player)
|
|
return false;
|
|
|
|
// Use configured AOE distance if no specific distance provided
|
|
if (maxDistance <= 0.0f)
|
|
maxDistance = sConfigMgr->GetOption<float>("AoeLoot.Range", 55.0f);
|
|
|
|
if (lguid.IsGameObject())
|
|
{
|
|
GameObject* go = player->GetMap()->GetGameObject(lguid);
|
|
if (!go)
|
|
return false;
|
|
|
|
// Special cases for owned objects or fishing holes
|
|
if (go->GetOwnerGUID() == player->GetGUID() || go->GetGoType() == GAMEOBJECT_TYPE_FISHINGHOLE)
|
|
return true;
|
|
|
|
return go->IsWithinDistInMap(player, maxDistance);
|
|
}
|
|
else if (lguid.IsItem())
|
|
{
|
|
Item* pItem = player->GetItemByGuid(lguid);
|
|
return (pItem != nullptr); // Items in inventory don't need distance check
|
|
}
|
|
else if (lguid.IsCorpse())
|
|
{
|
|
Corpse* corpse = ObjectAccessor::GetCorpse(*player, lguid);
|
|
if (!corpse)
|
|
return false;
|
|
|
|
return corpse->IsWithinDistInMap(player, maxDistance);
|
|
}
|
|
else // Creature
|
|
{
|
|
Creature* creature = player->GetMap()->GetCreature(lguid);
|
|
if (!creature)
|
|
return false;
|
|
|
|
// For pickpocketing, use strict interaction distance
|
|
if (creature->IsAlive() && player->IsClass(CLASS_ROGUE, CLASS_CONTEXT_ABILITY) &&
|
|
creature->loot.loot_type == LOOT_PICKPOCKETING)
|
|
{
|
|
return creature->IsWithinDistInMap(player, INTERACTION_DISTANCE);
|
|
}
|
|
|
|
// For corpses, use AOE distance but still validate
|
|
return creature->IsWithinDistInMap(player, maxDistance);
|
|
}
|
|
}
|
|
|
|
bool AoeLootCommandScript::ProcessCreatureGold(Player* player, Creature* creature)
|
|
{
|
|
if (!player || !creature)
|
|
return false;
|
|
|
|
// Validate distance before processing money
|
|
if (!ValidateLootingDistance(player, creature->GetGUID()))
|
|
{
|
|
player->SendLootError(creature->GetGUID(), LOOT_ERROR_TOO_FAR);
|
|
return false;
|
|
}
|
|
|
|
Loot* loot = &creature->loot;
|
|
if (!loot || loot->gold == 0)
|
|
return false;
|
|
|
|
uint32 goldAmount = loot->gold;
|
|
bool shareMoney = true; // Share by default for creature corpses
|
|
|
|
if (shareMoney && player->GetGroup())
|
|
{
|
|
Group* group = player->GetGroup();
|
|
std::vector<Player*> playersNear;
|
|
|
|
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
|
{
|
|
Player* member = itr->GetSource();
|
|
if (member)
|
|
playersNear.push_back(member);
|
|
}
|
|
|
|
uint32 goldPerPlayer = uint32((loot->gold) / (playersNear.size()));
|
|
|
|
for (Player* groupMember : playersNear)
|
|
{
|
|
groupMember->ModifyMoney(goldPerPlayer);
|
|
groupMember->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_MONEY, goldPerPlayer);
|
|
|
|
WorldPacket data(SMSG_LOOT_MONEY_NOTIFY, 4 + 1);
|
|
data << uint32(goldPerPlayer);
|
|
data << uint8(playersNear.size() > 1 ? 0 : 1);
|
|
groupMember->GetSession()->SendPacket(&data);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No group - give all gold to the player
|
|
player->ModifyMoney(loot->gold);
|
|
player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_MONEY, loot->gold);
|
|
|
|
WorldPacket data(SMSG_LOOT_MONEY_NOTIFY, 4 + 1);
|
|
data << uint32(loot->gold);
|
|
data << uint8(1);
|
|
player->GetSession()->SendPacket(&data);
|
|
}
|
|
|
|
// Mark the money as looted
|
|
loot->gold = 0;
|
|
loot->NotifyMoneyRemoved();
|
|
|
|
return true;
|
|
}
|
|
|
|
void AoeLootCommandScript::ReleaseAndCleanupLoot(ObjectGuid lguid, Player* player, Loot*)
|
|
{
|
|
player->SetLootGUID(ObjectGuid::Empty);
|
|
player->SendLootRelease(lguid);
|
|
player->RemoveUnitFlag(UNIT_FLAG_LOOTING);
|
|
|
|
if (!player->IsInWorld())
|
|
return;
|
|
|
|
Loot* loot = nullptr;
|
|
|
|
if (lguid.IsGameObject())
|
|
{
|
|
GameObject* go = player->GetMap()->GetGameObject(lguid);
|
|
if (!go)
|
|
{
|
|
player->SendLootRelease(lguid);
|
|
return;
|
|
}
|
|
|
|
// Validate distance for GameObjects
|
|
if (!ValidateLootingDistance(player, lguid))
|
|
{
|
|
player->SendLootRelease(lguid);
|
|
return;
|
|
}
|
|
|
|
loot = &go->loot;
|
|
}
|
|
else if (lguid.IsCorpse())
|
|
{
|
|
Corpse* corpse = ObjectAccessor::GetCorpse(*player, lguid);
|
|
if (!corpse)
|
|
return;
|
|
|
|
// Validate distance for corpses
|
|
if (!ValidateLootingDistance(player, lguid))
|
|
return;
|
|
|
|
loot = &corpse->loot;
|
|
}
|
|
else if (lguid.IsItem())
|
|
{
|
|
Item* pItem = player->GetItemByGuid(lguid);
|
|
if (!pItem)
|
|
return;
|
|
|
|
// For items, we don't need to process loot further
|
|
return;
|
|
}
|
|
else // Must be a creature
|
|
{
|
|
Creature* creature = player->GetMap()->GetCreature(lguid);
|
|
if (!creature)
|
|
return;
|
|
|
|
// Validate distance for creatures (includes pickpocketing checks)
|
|
if (!ValidateLootingDistance(player, lguid))
|
|
{
|
|
player->SendLootError(lguid, LOOT_ERROR_TOO_FAR);
|
|
return;
|
|
}
|
|
|
|
loot = &creature->loot;
|
|
|
|
if (!loot)
|
|
return;
|
|
|
|
if (loot->isLooted())
|
|
{
|
|
// skip pickpocketing loot for speed, skinning timer reduction is no-op in fact
|
|
if (!creature->IsAlive())
|
|
creature->AllLootRemovedFromCorpse();
|
|
|
|
creature->RemoveDynamicFlag(UNIT_DYNFLAG_LOOTABLE);
|
|
loot->clear();
|
|
}
|
|
else
|
|
{
|
|
// if the round robin player release, reset it.
|
|
if (player->GetGUID() == loot->roundRobinPlayer)
|
|
{
|
|
loot->roundRobinPlayer.Clear();
|
|
|
|
if (Group* group = player->GetGroup())
|
|
group->SendLooter(creature, nullptr);
|
|
}
|
|
// force dynflag update to update looter and lootable info
|
|
creature->ForceValuesUpdateAtIndex(UNIT_DYNAMIC_FLAGS);
|
|
}
|
|
}
|
|
|
|
// Player is not looking at loot list, he doesn't need to see updates on the loot list
|
|
if (!lguid.IsItem() && loot)
|
|
{
|
|
loot->RemoveLooter(player->GetGUID());
|
|
}
|
|
}
|
|
|
|
bool AoeLootCommandScript::ProcessSingleLootSlot(Player* player, ObjectGuid lguid, uint8 lootSlot)
|
|
{
|
|
if (!IsAoeLootModuleEnabled())
|
|
return true;
|
|
|
|
if (!player)
|
|
return false;
|
|
|
|
// Validate distance first
|
|
if (!ValidateLootingDistance(player, lguid))
|
|
{
|
|
player->SendLootError(lguid, LOOT_ERROR_TOO_FAR);
|
|
return false;
|
|
}
|
|
|
|
Loot* loot = nullptr;
|
|
|
|
// Get the loot object based on the GUID type
|
|
if (lguid.IsGameObject())
|
|
{
|
|
GameObject* go = player->GetMap()->GetGameObject(lguid);
|
|
if (!go)
|
|
{
|
|
player->SendLootRelease(lguid);
|
|
return false;
|
|
}
|
|
loot = &go->loot;
|
|
}
|
|
else if (lguid.IsItem())
|
|
{
|
|
Item* pItem = player->GetItemByGuid(lguid);
|
|
if (!pItem)
|
|
{
|
|
player->SendLootRelease(lguid);
|
|
return false;
|
|
}
|
|
loot = &pItem->loot;
|
|
}
|
|
else if (lguid.IsCorpse())
|
|
{
|
|
Corpse* bones = ObjectAccessor::GetCorpse(*player, lguid);
|
|
if (!bones)
|
|
{
|
|
player->SendLootRelease(lguid);
|
|
return false;
|
|
}
|
|
loot = &bones->loot;
|
|
}
|
|
else
|
|
{
|
|
Creature* creature = player->GetMap()->GetCreature(lguid);
|
|
if (!creature)
|
|
{
|
|
player->SendLootRelease(lguid);
|
|
return false;
|
|
}
|
|
loot = &creature->loot;
|
|
}
|
|
|
|
sScriptMgr->OnPlayerAfterCreatureLoot(player);
|
|
if (!loot)
|
|
return false;
|
|
|
|
// --- Begin standard loot logic for solo/group ---
|
|
Group* group = player->GetGroup();
|
|
LootItem* lootItem = nullptr;
|
|
InventoryResult msg = EQUIP_ERR_OK;
|
|
bool isGroupLoot = false;
|
|
bool isFFA = false;
|
|
bool isMasterLooter = false;
|
|
bool isRoundRobin = false;
|
|
bool isThreshold = false;
|
|
LootMethod lootMethod = GROUP_LOOT;
|
|
uint8 groupLootThreshold = 2; // Use group's configured threshold, fallback to Uncommon
|
|
|
|
// Bounds check for loot slot
|
|
if (lootSlot >= loot->items.size())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
isFFA = loot->items[lootSlot].freeforall;
|
|
|
|
if (group)
|
|
{
|
|
lootMethod = group->GetLootMethod();
|
|
groupLootThreshold = group->GetLootThreshold();
|
|
isMasterLooter = (lootMethod == MASTER_LOOT);
|
|
isRoundRobin = (lootMethod == ROUND_ROBIN);
|
|
isGroupLoot = (lootMethod == GROUP_LOOT || lootMethod == NEED_BEFORE_GREED);
|
|
|
|
// Bounds check before accessing loot item
|
|
if (lootSlot < loot->items.size())
|
|
{
|
|
ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(loot->items[lootSlot].itemid);
|
|
isThreshold = (itemTemplate && itemTemplate->Quality >= groupLootThreshold);
|
|
}
|
|
}
|
|
|
|
// If in group and item meets group loot threshold, trigger group roll
|
|
if (group && isGroupLoot && isThreshold && !isFFA && !isMasterLooter)
|
|
{
|
|
// Use the existing game's SendLootStartRoll - it doesn't have distance checks
|
|
if (lootSlot < loot->items.size() && !loot->items[lootSlot].is_blocked)
|
|
{
|
|
// Create Roll object using the constructor: Roll(ObjectGuid _guid, LootItem const &li)
|
|
Roll roll(lguid, loot->items[lootSlot]);
|
|
roll.itemSlot = lootSlot;
|
|
roll.setLoot(loot);
|
|
|
|
// Initialize player votes for group members
|
|
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
|
{
|
|
Player* member = itr->GetSource();
|
|
if (member && member->IsInWorld() && !member->isDead())
|
|
{
|
|
roll.playerVote[member->GetGUID()] = RollVote(0);
|
|
roll.totalPlayersRolling++;
|
|
}
|
|
}
|
|
|
|
// Call SendLootStartRoll using the group's configured roll timeout
|
|
group->SendLootStartRoll(60, player->GetMapId(), roll);
|
|
}
|
|
return true;
|
|
}
|
|
else if (group && isMasterLooter && !isFFA)
|
|
{
|
|
// Master looter: only master looter can loot
|
|
if (group->GetMasterLooterGuid() != player->GetGUID())
|
|
{
|
|
player->SendLootError(lguid, LOOT_ERROR_MASTER_OTHER);
|
|
return false;
|
|
}
|
|
}
|
|
else if (group && isRoundRobin && loot->roundRobinPlayer && loot->roundRobinPlayer != player->GetGUID())
|
|
{
|
|
// Round robin: only designated player can loot
|
|
return false;
|
|
}
|
|
|
|
// If not blocked by group loot, proceed to loot the item
|
|
lootItem = player->StoreLootItem(lootSlot, loot, msg);
|
|
if (msg != EQUIP_ERR_OK && lguid.IsItem() && loot->loot_type != LOOT_CORPSE)
|
|
{
|
|
lootItem->is_looted = true;
|
|
loot->NotifyItemRemoved(lootItem->itemIndex);
|
|
loot->unlootedCount--;
|
|
player->SendItemRetrievalMail(lootItem->itemid, lootItem->count);
|
|
}
|
|
|
|
// If player is removing the last LootItem, delete the empty container
|
|
if (loot->isLooted() && lguid.IsItem())
|
|
{
|
|
ReleaseAndCleanupLoot(lguid, player, loot);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool AoeLootCommandScript::TriggerAoeLootCommand(ChatHandler* handler, Optional<std::string> /*args*/)
|
|
{
|
|
if (!IsAoeLootModuleEnabled())
|
|
return true;
|
|
|
|
Player* player = handler->GetSession()->GetPlayer();
|
|
if (!player)
|
|
return true;
|
|
|
|
// Check if AOE loot is enabled for this player's context
|
|
if (!IsAoeLootEnabledForPlayer(player))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
float range = sConfigMgr->GetOption<float>("AoeLoot.Range", 55.0);
|
|
|
|
std::list<Creature*> nearbyCorpses;
|
|
player->GetDeadCreatureListInGrid(nearbyCorpses, range);
|
|
|
|
// Filter valid corpses
|
|
std::list<Creature*> validCorpses;
|
|
for (auto* creature : nearbyCorpses)
|
|
{
|
|
if (!player || !creature)
|
|
continue;
|
|
|
|
if (!player->isAllowedToLoot(creature))
|
|
continue;
|
|
|
|
if (!creature->HasDynamicFlag(UNIT_DYNFLAG_LOOTABLE))
|
|
continue;
|
|
|
|
if (!creature->hasLootRecipient())
|
|
continue;
|
|
|
|
if (!creature->isTappedBy(player))
|
|
continue;
|
|
|
|
// Get player's group and check loot permissions based on group loot method
|
|
Group* group = player->GetGroup();
|
|
if (group)
|
|
{
|
|
Loot* loot = &creature->loot;
|
|
LootMethod lootMethod = group->GetLootMethod();
|
|
// For Round Robin loot, check if this player is the designated looter
|
|
if (lootMethod == ROUND_ROBIN)
|
|
{
|
|
if (loot->roundRobinPlayer && loot->roundRobinPlayer != player->GetGUID())
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
// For Master Loot, check if this player is the master looter
|
|
else if (lootMethod == MASTER_LOOT)
|
|
{
|
|
if (group->GetMasterLooterGuid() != player->GetGUID())
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
validCorpses.push_back(creature);
|
|
}
|
|
|
|
// Process all valid corpses
|
|
for (auto* creature : validCorpses)
|
|
{
|
|
ObjectGuid lguid = creature->GetGUID();
|
|
Loot* loot = &creature->loot;
|
|
|
|
if (!loot)
|
|
continue;
|
|
|
|
// Double-check distance validation for security
|
|
if (!ValidateLootingDistance(player, lguid))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
player->SetLootGUID(lguid);
|
|
|
|
// Process all loot items
|
|
for (uint8 lootSlot = 0; lootSlot < loot->items.size(); ++lootSlot)
|
|
{
|
|
ProcessSingleLootSlot(player, lguid, lootSlot);
|
|
}
|
|
|
|
// Handle money
|
|
if (loot->gold > 0)
|
|
{
|
|
uint32 goldAmount = loot->gold;
|
|
ProcessCreatureGold(player, creature);
|
|
}
|
|
|
|
if (loot->isLooted())
|
|
{
|
|
ReleaseAndCleanupLoot(lguid, player, loot);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void AoeLootPlayer::OnPlayerLogin(Player* player)
|
|
{
|
|
if (!IsAoeLootModuleEnabled())
|
|
return;
|
|
|
|
// If player is in a group, apply default loot settings
|
|
if (Group* group = player->GetGroup())
|
|
{
|
|
ApplyDefaultLootSettings(group, player);
|
|
}
|
|
|
|
uint32 AoeLootMode = sConfigMgr->GetOption<uint32>("AoeLoot.EnableAOELoot", 2);
|
|
|
|
if (AoeLootMode > 0 && sConfigMgr->GetOption<bool>("AoeLoot.Message", true))
|
|
{
|
|
std::string message = "AOE looting has been enabled for your character";
|
|
|
|
switch (AoeLootMode)
|
|
{
|
|
case 1:
|
|
message += " (solo play only)";
|
|
break;
|
|
case 2:
|
|
message += " (solo and group play)";
|
|
break;
|
|
}
|
|
|
|
message += ". Type: '.AoeLoot off' to turn AoE Looting Off.";
|
|
}
|
|
}
|
|
|
|
void AoeLootGroupScript::OnCreate(Group* group, Player* leader)
|
|
{
|
|
if (!IsAoeLootModuleEnabled())
|
|
return;
|
|
|
|
if (!group || !leader)
|
|
return;
|
|
|
|
// Apply default loot settings to the group
|
|
ApplyDefaultLootSettings(group, leader);
|
|
}
|
|
|
|
class AoeLootQuestParty : public PlayerScript
|
|
{
|
|
public:
|
|
AoeLootQuestParty() : PlayerScript("AoeLootQuestParty") { }
|
|
|
|
void OnPlayerBeforeFillQuestLootItem(Player* /*player*/, LootItem& item) override
|
|
{
|
|
ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(item.itemid);
|
|
if (itemTemplate &&
|
|
itemTemplate->Quality == ITEM_QUALITY_NORMAL &&
|
|
itemTemplate->Class == ITEM_CLASS_QUEST &&
|
|
itemTemplate->SubClass == ITEM_SUBCLASS_QUEST &&
|
|
itemTemplate->Bonding == BIND_QUEST_ITEM)
|
|
{
|
|
item.freeforall = true;
|
|
}
|
|
}
|
|
};
|
|
|
|
void AddSC_AoeLoot()
|
|
{
|
|
new AoeLootPlayer();
|
|
new AoeLootServer();
|
|
new AoeLootCommandScript();
|
|
new AoeLootGroupScript();
|
|
new AoeLootQuestParty();
|
|
}
|