mirror of
https://github.com/brighton-chi/mod-aoe-loot.git
synced 2026-01-13 00:58:34 +00:00
initial commit
This commit is contained in:
766
src/aoe_loot.cpp
766
src/aoe_loot.cpp
@@ -18,49 +18,147 @@
|
||||
#include "Corpse.h"
|
||||
#include "Group.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
|
||||
using namespace Acore::ChatCommands;
|
||||
using namespace WorldPackets;
|
||||
|
||||
std::map<uint64, bool> playerAoeLootEnabled;
|
||||
// Thread-safe player AoE loot preference storage (renamed)
|
||||
std::map<uint64, bool> playerAoeLootPreferences;
|
||||
std::mutex aoeLootPreferencesMutex;
|
||||
|
||||
// Track groups that have had default loot settings applied by us
|
||||
std::set<uint64> groupsWithAppliedDefaults;
|
||||
std::mutex groupLootMethodMutex;
|
||||
|
||||
// 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:
|
||||
LOG_WARN("module.aoe_loot", "Invalid AOELoot.DefaultLootMethod value: {}. Using Group Loot.", configValue);
|
||||
return GROUP_LOOT;
|
||||
}
|
||||
}
|
||||
|
||||
// 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.Enable", 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: // Invalid value, default to solo + group
|
||||
LOG_WARN("module.aoe_loot", "Invalid AOELoot.Enable value: {}. Using default (2).", aoeLootMode);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool AoeLootServer::CanPacketReceive(WorldSession* session, WorldPacket& packet)
|
||||
{
|
||||
if (packet.GetOpcode() == CMSG_LOOT)
|
||||
{
|
||||
Player* player = session->GetPlayer();
|
||||
if (player)
|
||||
{
|
||||
uint64 guid = player->GetGUID().GetRawValue();
|
||||
if (!player)
|
||||
return true;
|
||||
|
||||
// Check if player has explicitly disabled AOE loot
|
||||
if (playerAoeLootEnabled.find(guid) != playerAoeLootEnabled.end() &&
|
||||
!playerAoeLootEnabled[guid])
|
||||
// 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;
|
||||
}
|
||||
// Trigger AOE loot when a player attempts to loot a corpse
|
||||
}
|
||||
|
||||
// Only trigger AOE loot if player is not already looting
|
||||
if (player->GetLootGUID().IsEmpty())
|
||||
{
|
||||
ChatHandler handler(player->GetSession());
|
||||
handler.ParseCommands(".aoeloot startaoeloot");
|
||||
handler.ParseCommands(".aoeloot lootall");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
ChatCommandTable AoeLootCommandScript::GetCommands() const
|
||||
{
|
||||
static ChatCommandTable aoeLootSubCommandTable =
|
||||
{
|
||||
{ "startaoeloot", HandleStartAoeLootCommand, SEC_PLAYER, Console::No },
|
||||
{ "on", HandleAoeLootOnCommand, SEC_PLAYER, Console::No },
|
||||
{ "off", HandleAoeLootOffCommand, SEC_PLAYER, Console::No }
|
||||
{ "lootall", TriggerAoeLootCommand, SEC_PLAYER, Console::No },
|
||||
{ "on", EnableAoeLootCommand, SEC_PLAYER, Console::No },
|
||||
{ "off", DisableAoeLootCommand, SEC_PLAYER, Console::No }
|
||||
};
|
||||
|
||||
static ChatCommandTable aoeLootCommandTable =
|
||||
@@ -71,126 +169,127 @@ ChatCommandTable AoeLootCommandScript::GetCommands() const
|
||||
return aoeLootCommandTable;
|
||||
}
|
||||
|
||||
bool AoeLootCommandScript::HandleAoeLootOnCommand(ChatHandler* handler, Optional<std::string> /*args*/)
|
||||
bool AoeLootCommandScript::EnableAoeLootCommand(ChatHandler* handler, Optional<std::string> /*args*/)
|
||||
{
|
||||
Player* player = handler->GetSession()->GetPlayer();
|
||||
if (!player)
|
||||
return true;
|
||||
|
||||
uint64 guid = player->GetGUID().GetRawValue();
|
||||
playerAoeLootEnabled[guid] = true;
|
||||
|
||||
// Check if AOE loot is enabled server-side
|
||||
if (!IsAoeLootEnabledForPlayer(player))
|
||||
{
|
||||
uint32 aoeLootMode = sConfigMgr->GetOption<uint32>("AOELoot.Enable", 2);
|
||||
switch (aoeLootMode)
|
||||
{
|
||||
case 0:
|
||||
handler->PSendSysMessage("AOE looting is completely disabled on this server.");
|
||||
break;
|
||||
case 1:
|
||||
handler->PSendSysMessage("AOE looting is only available for solo play (you are currently in a group).");
|
||||
break;
|
||||
default:
|
||||
handler->PSendSysMessage("AOE looting is not available in your current context.");
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Thread-safe update
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(aoeLootPreferencesMutex);
|
||||
playerAoeLootPreferences[guid] = true;
|
||||
}
|
||||
|
||||
handler->PSendSysMessage("AOE looting has been enabled for your character. Type: '.aoeloot off' to turn AoE Looting Off.");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AoeLootCommandScript::HandleAoeLootOffCommand(ChatHandler* handler, Optional<std::string> /*args*/)
|
||||
bool AoeLootCommandScript::DisableAoeLootCommand(ChatHandler* handler, Optional<std::string> /*args*/)
|
||||
{
|
||||
Player* player = handler->GetSession()->GetPlayer();
|
||||
if (!player)
|
||||
return true;
|
||||
|
||||
uint64 guid = player->GetGUID().GetRawValue();
|
||||
playerAoeLootEnabled[guid] = false;
|
||||
|
||||
// Thread-safe update
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(aoeLootPreferencesMutex);
|
||||
playerAoeLootPreferences[guid] = false;
|
||||
}
|
||||
|
||||
handler->PSendSysMessage("AOE looting has been disabled for your character. Type: '.aoeloot on' to turn AoE Looting on.");
|
||||
return true;
|
||||
}
|
||||
|
||||
void AoeLootCommandScript::ProcessLootRelease(ObjectGuid lguid, Player* player, Loot* loot)
|
||||
// Rename ValidateLootDistance to ValidateLootingDistance
|
||||
bool AoeLootCommandScript::ValidateLootingDistance(Player* player, ObjectGuid lguid, float maxDistance)
|
||||
{
|
||||
player->SetLootGUID(ObjectGuid::Empty);
|
||||
player->SendLootRelease(lguid);
|
||||
player->RemoveUnitFlag(UNIT_FLAG_LOOTING);
|
||||
|
||||
if (!player->IsInWorld())
|
||||
return;
|
||||
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);
|
||||
|
||||
// not check distance for GO in case owned GO (fishing bobber case, for example) or Fishing hole GO
|
||||
if (!go || ((go->GetOwnerGUID() != player->GetGUID() && go->GetGoType() != GAMEOBJECT_TYPE_FISHINGHOLE) && !go->IsWithinDistInMap(player)))
|
||||
{
|
||||
player->SendLootRelease(lguid);
|
||||
return;
|
||||
}
|
||||
|
||||
loot = &go->loot;
|
||||
|
||||
}
|
||||
else if (lguid.IsCorpse()) // ONLY remove insignia at BG
|
||||
{
|
||||
Corpse* corpse = ObjectAccessor::GetCorpse(*player, lguid);
|
||||
|
||||
if (!corpse)
|
||||
return;
|
||||
|
||||
loot = &corpse->loot;
|
||||
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);
|
||||
if (!pItem)
|
||||
return;
|
||||
|
||||
return (pItem != nullptr); // Items in inventory don't need distance check
|
||||
}
|
||||
else // Must be a creature
|
||||
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);
|
||||
|
||||
// Skip distance check for dead creatures (corpses)
|
||||
// Keep distance check for pickpocketing (live creatures)
|
||||
bool isPickpocketing = creature && creature->IsAlive() && player->IsClass(CLASS_ROGUE, CLASS_CONTEXT_ABILITY) && creature->loot.loot_type == LOOT_PICKPOCKETING;
|
||||
|
||||
// Only check distance for pickpocketing, not for corpse looting
|
||||
if (isPickpocketing && !creature->IsWithinDistInMap(player, INTERACTION_DISTANCE))
|
||||
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)
|
||||
{
|
||||
player->SendLootError(lguid, LOOT_ERROR_TOO_FAR);
|
||||
return;
|
||||
return creature->IsWithinDistInMap(player, INTERACTION_DISTANCE);
|
||||
}
|
||||
|
||||
loot = &creature->loot;
|
||||
|
||||
|
||||
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->RemoveLooter(player->GetGUID());
|
||||
// For corpses, use AOE distance but still validate
|
||||
return creature->IsWithinDistInMap(player, maxDistance);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle gold looting without distance checks
|
||||
bool AoeLootCommandScript::ProcessLootMoney(Player* player, Creature* creature)
|
||||
// Rename ProcessLootMoney to ProcessCreatureGold
|
||||
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;
|
||||
@@ -219,7 +318,7 @@ bool AoeLootCommandScript::ProcessLootMoney(Player* player, Creature* creature)
|
||||
|
||||
WorldPacket data(SMSG_LOOT_MONEY_NOTIFY, 4 + 1);
|
||||
data << uint32(goldPerPlayer);
|
||||
data << uint8(playersNear.size() > 1 ? 0 : 1); // 0 is "Your share is..." and 1 is "You loot..."
|
||||
data << uint8(playersNear.size() > 1 ? 0 : 1);
|
||||
groupMember->GetSession()->SendPacket(&data);
|
||||
}
|
||||
}
|
||||
@@ -231,7 +330,7 @@ bool AoeLootCommandScript::ProcessLootMoney(Player* player, Creature* creature)
|
||||
|
||||
WorldPacket data(SMSG_LOOT_MONEY_NOTIFY, 4 + 1);
|
||||
data << uint32(loot->gold);
|
||||
data << uint8(1); // "You loot..."
|
||||
data << uint8(1);
|
||||
player->GetSession()->SendPacket(&data);
|
||||
}
|
||||
|
||||
@@ -242,19 +341,126 @@ bool AoeLootCommandScript::ProcessLootMoney(Player* player, Creature* creature)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AoeLootCommandScript::ProcessLootSlot(Player* player, ObjectGuid lguid, uint8 lootSlot)
|
||||
// Rename ProcessLootRelease to ReleaseAndCleanupLoot
|
||||
void AoeLootCommandScript::ReleaseAndCleanupLoot(ObjectGuid lguid, Player* player, Loot* /*originalLoot*/)
|
||||
{
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
// Rename ProcessLootSlot to ProcessSingleLootSlot
|
||||
bool AoeLootCommandScript::ProcessSingleLootSlot(Player* player, ObjectGuid lguid, uint8 lootSlot)
|
||||
{
|
||||
if (!player)
|
||||
return false;
|
||||
|
||||
// Validate distance first
|
||||
if (!ValidateLootingDistance(player, lguid))
|
||||
{
|
||||
player->SendLootError(lguid, LOOT_ERROR_TOO_FAR);
|
||||
return false;
|
||||
}
|
||||
|
||||
Loot* loot = nullptr;
|
||||
float aoeDistance = sConfigMgr->GetOption<float>("AOELoot.Range", 55.0f);
|
||||
|
||||
// Get the loot object based on the GUID type
|
||||
if (lguid.IsGameObject())
|
||||
{
|
||||
GameObject* go = player->GetMap()->GetGameObject(lguid);
|
||||
if (!go || ((go->GetOwnerGUID() != player->GetGUID() && go->GetGoType() != GAMEOBJECT_TYPE_FISHINGHOLE) && !go->IsWithinDistInMap(player, aoeDistance)))
|
||||
if (!go)
|
||||
{
|
||||
player->SendLootRelease(lguid);
|
||||
return false;
|
||||
@@ -284,13 +490,9 @@ bool AoeLootCommandScript::ProcessLootSlot(Player* player, ObjectGuid lguid, uin
|
||||
else
|
||||
{
|
||||
Creature* creature = player->GetMap()->GetCreature(lguid);
|
||||
// Skip distance check for dead creatures (corpses)
|
||||
// Keep distance check for pickpocketing (live creatures)
|
||||
bool isPickpocketing = creature && creature->IsAlive() && player->IsClass(CLASS_ROGUE, CLASS_CONTEXT_ABILITY) && creature->loot.loot_type == LOOT_PICKPOCKETING;
|
||||
// Only check distance for pickpocketing, not for corpse looting
|
||||
if (isPickpocketing && !creature->IsWithinDistInMap(player, INTERACTION_DISTANCE))
|
||||
if (!creature)
|
||||
{
|
||||
player->SendLootError(lguid, LOOT_ERROR_TOO_FAR);
|
||||
player->SendLootRelease(lguid);
|
||||
return false;
|
||||
}
|
||||
loot = &creature->loot;
|
||||
@@ -305,12 +507,20 @@ bool AoeLootCommandScript::ProcessLootSlot(Player* player, ObjectGuid lguid, uin
|
||||
LootItem* lootItem = nullptr;
|
||||
InventoryResult msg = EQUIP_ERR_OK;
|
||||
bool isGroupLoot = false;
|
||||
bool isFFA = loot->items[lootSlot].freeforall;
|
||||
bool isFFA = false;
|
||||
bool isMasterLooter = false;
|
||||
bool isRoundRobin = false;
|
||||
bool isThreshold = false;
|
||||
LootMethod lootMethod = GROUP_LOOT;
|
||||
uint8 groupLootThreshold = 2; // Default: Uncommon
|
||||
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)
|
||||
{
|
||||
@@ -319,8 +529,13 @@ bool AoeLootCommandScript::ProcessLootSlot(Player* player, ObjectGuid lguid, uin
|
||||
isMasterLooter = (lootMethod == MASTER_LOOT);
|
||||
isRoundRobin = (lootMethod == ROUND_ROBIN);
|
||||
isGroupLoot = (lootMethod == GROUP_LOOT || lootMethod == NEED_BEFORE_GREED);
|
||||
ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(loot->items[lootSlot].itemid);
|
||||
isThreshold = (itemTemplate && itemTemplate->Quality >= groupLootThreshold);
|
||||
|
||||
// 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
|
||||
@@ -345,7 +560,7 @@ bool AoeLootCommandScript::ProcessLootSlot(Player* player, ObjectGuid lguid, uin
|
||||
}
|
||||
}
|
||||
|
||||
// Call SendLootStartRoll with 60 second countdown
|
||||
// Call SendLootStartRoll using the group's configured roll timeout
|
||||
group->SendLootStartRoll(60, player->GetMapId(), roll);
|
||||
}
|
||||
return true;
|
||||
@@ -378,22 +593,24 @@ bool AoeLootCommandScript::ProcessLootSlot(Player* player, ObjectGuid lguid, uin
|
||||
// If player is removing the last LootItem, delete the empty container
|
||||
if (loot->isLooted() && lguid.IsItem())
|
||||
{
|
||||
ProcessLootRelease(lguid, player, loot);
|
||||
ReleaseAndCleanupLoot(lguid, player, loot);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool AoeLootCommandScript::HandleStartAoeLootCommand(ChatHandler* handler, Optional<std::string> /*args*/)
|
||||
bool AoeLootCommandScript::TriggerAoeLootCommand(ChatHandler* handler, Optional<std::string> /*args*/)
|
||||
{
|
||||
if (!sConfigMgr->GetOption<bool>("AOELoot.Enable", true))
|
||||
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))
|
||||
{
|
||||
handler->PSendSysMessage("AOE looting is not available in your current context.");
|
||||
return true;
|
||||
}
|
||||
|
||||
float range = sConfigMgr->GetOption<float>("AOELoot.Range", 55.0);
|
||||
bool debugMode = sConfigMgr->GetOption<bool>("AOELoot.Debug", false);
|
||||
|
||||
@@ -474,94 +691,46 @@ bool AoeLootCommandScript::HandleStartAoeLootCommand(ChatHandler* handler, Optio
|
||||
{
|
||||
ObjectGuid lguid = creature->GetGUID();
|
||||
Loot* loot = &creature->loot;
|
||||
if (validCorpses.size() <= 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (!loot)
|
||||
continue;
|
||||
|
||||
// Double-check distance validation for security
|
||||
if (!ValidateLootingDistance(player, lguid))
|
||||
{
|
||||
if (debugMode)
|
||||
LOG_DEBUG("module.aoe_loot", "AOE Loot: Skipping creature {} - too far away", lguid.ToString());
|
||||
continue;
|
||||
}
|
||||
|
||||
player->SetLootGUID(lguid);
|
||||
|
||||
// Process quest items
|
||||
QuestItemMap const& playerNonQuestNonFFAConditionalItems = loot->GetPlayerNonQuestNonFFAConditionalItems();
|
||||
if (!playerNonQuestNonFFAConditionalItems.empty())
|
||||
{
|
||||
for (uint8 i = 0; i < playerNonQuestNonFFAConditionalItems.size(); ++i)
|
||||
{
|
||||
uint8 lootSlot = playerNonQuestNonFFAConditionalItems.size() + i;
|
||||
ProcessLootSlot(player, lguid, lootSlot);
|
||||
if (debugMode)
|
||||
LOG_DEBUG("module.aoe_loot", "AOE Loot: looted quest item in slot {}", lootSlot);
|
||||
}
|
||||
}
|
||||
|
||||
// Process quest items
|
||||
QuestItemMap const& playerFFAItems = loot->GetPlayerFFAItems();
|
||||
if (!playerFFAItems.empty())
|
||||
{
|
||||
for (uint8 i = 0; i < playerFFAItems.size(); ++i)
|
||||
{
|
||||
uint8 lootSlot = playerFFAItems.size() + i;
|
||||
ProcessLootSlot(player, lguid, lootSlot);
|
||||
if (debugMode)
|
||||
LOG_DEBUG("module.aoe_loot", "AOE Loot: looted quest item in slot {}", lootSlot);
|
||||
}
|
||||
}
|
||||
|
||||
// Process quest items
|
||||
QuestItemMap const& playerQuestItems = loot->GetPlayerQuestItems();
|
||||
if (!playerQuestItems.empty())
|
||||
{
|
||||
for (uint8 i = 0; i < playerQuestItems.size(); ++i)
|
||||
{
|
||||
uint8 lootSlot = playerQuestItems.size() + i;
|
||||
ProcessLootSlot(player, lguid, lootSlot);
|
||||
if (debugMode)
|
||||
LOG_DEBUG("module.aoe_loot", "AOE Loot: looted quest item in slot {}", lootSlot);
|
||||
}
|
||||
}
|
||||
|
||||
// Process quest items
|
||||
if (!loot->quest_items.empty())
|
||||
{
|
||||
for (uint8 i = 0; i < loot->quest_items.size(); ++i)
|
||||
{
|
||||
uint8 lootSlot = loot->items.size() + i;
|
||||
ProcessLootSlot(player, lguid, lootSlot);
|
||||
if (debugMode)
|
||||
LOG_DEBUG("module.aoe_loot", "AOE Loot: looted quest item in slot {}", lootSlot);
|
||||
}
|
||||
}
|
||||
|
||||
// Process quest items
|
||||
if (!loot->quest_items.empty())
|
||||
{
|
||||
for (uint8 i = 0; i < loot->quest_items.size(); ++i)
|
||||
{
|
||||
uint8 lootSlot = loot->items.size() + i;
|
||||
ProcessLootSlot(player, lguid, lootSlot);
|
||||
if (debugMode)
|
||||
LOG_DEBUG("module.aoe_loot", "AOE Loot: looted quest item in slot {}", lootSlot);
|
||||
}
|
||||
}
|
||||
|
||||
// Process regular items
|
||||
// Process all regular items (includes quest items and FFA items automatically)
|
||||
for (uint8 lootSlot = 0; lootSlot < loot->items.size(); ++lootSlot)
|
||||
{
|
||||
player->SetLootGUID(lguid);
|
||||
ProcessLootSlot(player, lguid, lootSlot);
|
||||
if (debugMode && lootSlot < loot->items.size())
|
||||
ProcessSingleLootSlot(player, lguid, lootSlot);
|
||||
if (debugMode)
|
||||
{
|
||||
LOG_DEBUG("module.aoe_loot", "AOE Loot: looted item from slot {} (ID: {}) from {}", lootSlot, loot->items[lootSlot].itemid, lguid.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
// Process quest items separately if they exist
|
||||
for (uint8 i = 0; i < loot->quest_items.size(); ++i)
|
||||
{
|
||||
uint8 questLootSlot = loot->items.size() + i;
|
||||
ProcessSingleLootSlot(player, lguid, questLootSlot);
|
||||
if (debugMode)
|
||||
{
|
||||
LOG_DEBUG("module.aoe_loot", "AOE Loot: looted quest item in slot {}", questLootSlot);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle money
|
||||
if (loot->gold > 0)
|
||||
{
|
||||
uint32 goldAmount = loot->gold;
|
||||
ProcessLootMoney(player, creature);
|
||||
ProcessCreatureGold(player, creature);
|
||||
if (debugMode)
|
||||
{
|
||||
LOG_DEBUG("module.aoe_loot", "AOE Loot: Looted {} copper from {}", goldAmount, lguid.ToString());
|
||||
@@ -570,7 +739,7 @@ bool AoeLootCommandScript::HandleStartAoeLootCommand(ChatHandler* handler, Optio
|
||||
|
||||
if (loot->isLooted())
|
||||
{
|
||||
ProcessLootRelease(lguid, player, loot);
|
||||
ReleaseAndCleanupLoot(lguid, player, loot);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -579,10 +748,230 @@ bool AoeLootCommandScript::HandleStartAoeLootCommand(ChatHandler* handler, Optio
|
||||
// Display login message to player
|
||||
void AoeLootPlayer::OnPlayerLogin(Player* player)
|
||||
{
|
||||
if (sConfigMgr->GetOption<bool>("AOELoot.Enable", true) &&
|
||||
sConfigMgr->GetOption<bool>("AOELoot.Message", true))
|
||||
// Check if AOE loot is enabled at all (any mode > 0)
|
||||
uint32 aoeLootMode = sConfigMgr->GetOption<uint32>("AOELoot.Enable", 2);
|
||||
|
||||
if (aoeLootMode > 0 && sConfigMgr->GetOption<bool>("AOELoot.Message", true))
|
||||
{
|
||||
ChatHandler(player->GetSession()).PSendSysMessage("AOE looting has been enabled for your character. Type: '.aoeloot off' to turn AoE Looting Off.");
|
||||
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.";
|
||||
ChatHandler(player->GetSession()).PSendSysMessage(message.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Group script implementation
|
||||
void AoeLootGroupScript::OnCreate(Group* group, Player* leader)
|
||||
{
|
||||
// Only apply default loot method to brand new groups, not reformed/rejoined groups
|
||||
if (!group || !leader)
|
||||
return;
|
||||
|
||||
// Only apply to truly new groups (single member = the leader)
|
||||
// When a player rejoins an existing group, the group already has its loot method preserved
|
||||
if (group->GetMembersCount() > 1)
|
||||
return;
|
||||
|
||||
// Check if this is a playerbot scenario - if the leader is a bot, delay loot method setting
|
||||
// to allow playerbot logic to handle leadership transfers when the player logs in
|
||||
// Note: Since IsBot() may not be available, we'll use a more conservative approach
|
||||
// and only apply defaults when we're certain it's a real player-led group
|
||||
if (!leader->GetSession() || !leader->GetSession()->GetPlayer())
|
||||
{
|
||||
// No valid session - likely a bot or disconnected player
|
||||
// Don't set loot method immediately
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply default loot settings for real player leaders
|
||||
ApplyDefaultLootSettings(group, leader);
|
||||
}
|
||||
|
||||
// Handle leadership changes (important for playerbot scenarios)
|
||||
void AoeLootGroupScript::OnChangeLeader(Group* group, ObjectGuid newLeaderGuid, ObjectGuid /*oldLeaderGuid*/)
|
||||
{
|
||||
if (!group)
|
||||
return;
|
||||
|
||||
Player* newLeader = ObjectAccessor::FindPlayer(newLeaderGuid);
|
||||
if (!newLeader)
|
||||
return;
|
||||
|
||||
// Only apply default loot settings if:
|
||||
// 1. The new leader is a real player (has valid session)
|
||||
// 2. The loot method hasn't been manually changed from our defaults
|
||||
if (newLeader->GetSession() && newLeader->GetSession()->GetPlayer())
|
||||
{
|
||||
// Safety check for valid group GUID
|
||||
ObjectGuid groupGuid = group->GetGUID();
|
||||
if (!groupGuid.IsEmpty())
|
||||
{
|
||||
uint64 groupId = groupGuid.GetRawValue();
|
||||
if (groupId != 0) // Additional safety check
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check if this group has had our defaults applied and if they've been changed
|
||||
std::lock_guard<std::mutex> lock(groupLootMethodMutex);
|
||||
if (groupsWithAppliedDefaults.find(groupId) == groupsWithAppliedDefaults.end())
|
||||
{
|
||||
// This group has never had our defaults applied
|
||||
// It's safe to apply our defaults
|
||||
ApplyDefaultLootSettings(group, newLeader);
|
||||
}
|
||||
else if (!IsLootMethodManuallySet(group))
|
||||
{
|
||||
// We've applied defaults before, but they haven't been manually changed
|
||||
// This handles bot-to-player leadership transfers where we want to reapply defaults
|
||||
ApplyDefaultLootSettings(group, newLeader);
|
||||
}
|
||||
// If the loot method has been manually changed, don't override it
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
LOG_ERROR("module.aoe_loot", "Failed to handle leadership change for group {}: {}", groupId, e.what());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_WARN("module.aoe_loot", "Group has invalid GUID (0) in OnChangeLeader");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_WARN("module.aoe_loot", "Group has empty GUID in OnChangeLeader");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle group dissolution - clean up tracking
|
||||
void AoeLootGroupScript::OnDisband(Group* group)
|
||||
{
|
||||
if (!group)
|
||||
return;
|
||||
|
||||
// Remove this group from our tracking when it's disbanded
|
||||
// Safety check for valid group GUID
|
||||
ObjectGuid groupGuid = group->GetGUID();
|
||||
if (!groupGuid.IsEmpty())
|
||||
{
|
||||
uint64 groupId = groupGuid.GetRawValue();
|
||||
if (groupId != 0) // Additional safety check
|
||||
{
|
||||
try
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(groupLootMethodMutex);
|
||||
groupsWithAppliedDefaults.erase(groupId);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
LOG_ERROR("module.aoe_loot", "Failed to clean up tracking for group {}: {}", groupId, e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to apply default loot settings
|
||||
void AoeLootGroupScript::ApplyDefaultLootSettings(Group* group, Player* leader)
|
||||
{
|
||||
if (!group || !leader)
|
||||
return;
|
||||
|
||||
// Get configured default loot method
|
||||
uint32 defaultLootMethodConfig = sConfigMgr->GetOption<uint32>("AOELoot.DefaultLootMethod", 3); // 3 = Group Loot (default)
|
||||
uint32 defaultLootThreshold = sConfigMgr->GetOption<uint32>("AOELoot.DefaultLootThreshold", 2); // Default: Uncommon
|
||||
|
||||
// Set the configured loot method for new groups
|
||||
LootMethod lootMethod = GetLootMethodFromConfig(defaultLootMethodConfig);
|
||||
group->SetLootMethod(lootMethod);
|
||||
|
||||
// If using Need Before Greed or Group Loot, also set the quality threshold
|
||||
if (lootMethod == NEED_BEFORE_GREED || lootMethod == GROUP_LOOT)
|
||||
{
|
||||
group->SetLootThreshold(ItemQualities(defaultLootThreshold));
|
||||
}
|
||||
|
||||
// If using Master Loot, set the leader as master looter
|
||||
if (lootMethod == MASTER_LOOT && leader)
|
||||
{
|
||||
group->SetMasterLooterGuid(leader->GetGUID());
|
||||
}
|
||||
|
||||
// Track that we've applied defaults to this group
|
||||
// Add safety check for valid group GUID
|
||||
ObjectGuid groupGuid = group->GetGUID();
|
||||
if (!groupGuid.IsEmpty())
|
||||
{
|
||||
uint64 groupId = groupGuid.GetRawValue();
|
||||
if (groupId != 0) // Additional safety check
|
||||
{
|
||||
try
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(groupLootMethodMutex);
|
||||
groupsWithAppliedDefaults.insert(groupId);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
LOG_ERROR("module.aoe_loot", "Failed to track group {} in ApplyDefaultLootSettings: {}", groupId, e.what());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_WARN("module.aoe_loot", "Group has invalid GUID (0) in ApplyDefaultLootSettings");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_WARN("module.aoe_loot", "Group has empty GUID in ApplyDefaultLootSettings");
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to check if loot method appears to be manually set
|
||||
bool AoeLootGroupScript::IsLootMethodManuallySet(Group* group)
|
||||
{
|
||||
if (!group)
|
||||
return false;
|
||||
|
||||
// Get our configured defaults
|
||||
uint32 defaultLootMethodConfig = sConfigMgr->GetOption<uint32>("AOELoot.DefaultLootMethod", 3);
|
||||
uint32 defaultLootThreshold = sConfigMgr->GetOption<uint32>("AOELoot.DefaultLootThreshold", 2);
|
||||
|
||||
LootMethod expectedDefaultMethod = GetLootMethodFromConfig(defaultLootMethodConfig);
|
||||
|
||||
// If the current loot method or threshold differs from our defaults,
|
||||
// it was likely changed manually by a player
|
||||
if (group->GetLootMethod() != expectedDefaultMethod)
|
||||
return true;
|
||||
|
||||
// For Group Loot and Need Before Greed, also check the threshold
|
||||
if ((expectedDefaultMethod == GROUP_LOOT || expectedDefaultMethod == NEED_BEFORE_GREED) &&
|
||||
group->GetLootThreshold() != defaultLootThreshold)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Helper function implementation for AoeLootGroupScript
|
||||
LootMethod AoeLootGroupScript::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; // Safe default
|
||||
}
|
||||
}
|
||||
|
||||
@@ -592,4 +981,5 @@ void AddSC_AoeLoot()
|
||||
new AoeLootPlayer();
|
||||
new AoeLootServer();
|
||||
new AoeLootCommandScript();
|
||||
new AoeLootGroupScript();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user