preserve configured loot method after server restart

This commit is contained in:
crow
2025-08-15 20:37:55 -05:00
parent ab678cc91a
commit 6525d3912a
17 changed files with 232 additions and 321 deletions

View File

@@ -26,11 +26,13 @@ using namespace WorldPackets;
// Thread-safe player AoE loot preference storage (renamed)
std::map<uint64, bool> playerAoeLootPreferences;
std::mutex aoeLootPreferencesMutex;
std::mutex AoeLootPreferencesMutex;
// Track groups that have had default loot settings applied by us
std::set<uint64> groupsWithAppliedDefaults;
std::mutex groupLootMethodMutex;
// 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)
@@ -48,11 +50,33 @@ LootMethod GetLootMethodFromConfig(uint32 configValue)
case 4:
return NEED_BEFORE_GREED;
default:
LOG_WARN("module.aoe_loot", "Invalid AOELoot.DefaultLootMethod value: {}. Using Group Loot.", configValue);
LOG_WARN("module.aoe_loot", "Invalid AoeLoot.DefaultLootMethod value: {}. Using Group Loot.", configValue);
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)
@@ -61,9 +85,9 @@ LootMethod GetLootMethodFromConfig(uint32 configValue)
// 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)
uint32 AoeLootMode = sConfigMgr->GetOption<uint32>("AoeLoot.EnableAOELoot", 2);
switch (AoeLootMode)
{
case 0: // Disabled
return false;
@@ -75,13 +99,16 @@ bool AoeLootCommandScript::IsAoeLootEnabledForPlayer(Player* player)
return true;
default: // Invalid value, default to solo + group
LOG_WARN("module.aoe_loot", "Invalid AOELoot.Enable value: {}. Using default (2).", aoeLootMode);
LOG_WARN("module.aoe_loot", "Invalid AoeLoot.EnableAOELoot value: {}. Using default (2).", AoeLootMode);
return true;
}
}
bool AoeLootServer::CanPacketReceive(WorldSession* session, WorldPacket& packet)
{
if (!IsAoeLootModuleEnabled())
return true;
if (packet.GetOpcode() == CMSG_LOOT)
{
Player* player = session->GetPlayer();
@@ -133,7 +160,7 @@ bool AoeLootServer::CanPacketReceive(WorldSession* session, WorldPacket& packet)
// Check if player has explicitly disabled AOE loot (thread-safe)
{
std::lock_guard<std::mutex> lock(aoeLootPreferencesMutex);
std::lock_guard<std::mutex> lock(AoeLootPreferencesMutex);
auto it = playerAoeLootPreferences.find(guid);
if (it != playerAoeLootPreferences.end() && !it->second)
{
@@ -146,7 +173,7 @@ bool AoeLootServer::CanPacketReceive(WorldSession* session, WorldPacket& packet)
if (player->GetLootGUID().IsEmpty())
{
ChatHandler handler(player->GetSession());
handler.ParseCommands(".aoeloot lootall");
handler.ParseCommands(".AoeLoot lootall");
}
}
return true;
@@ -154,23 +181,26 @@ bool AoeLootServer::CanPacketReceive(WorldSession* session, WorldPacket& packet)
ChatCommandTable AoeLootCommandScript::GetCommands() const
{
static ChatCommandTable aoeLootSubCommandTable =
static ChatCommandTable AoeLootSubCommandTable =
{
{ "lootall", TriggerAoeLootCommand, SEC_PLAYER, Console::No },
{ "on", EnableAoeLootCommand, SEC_PLAYER, Console::No },
{ "off", DisableAoeLootCommand, SEC_PLAYER, Console::No }
};
static ChatCommandTable aoeLootCommandTable =
static ChatCommandTable AoeLootCommandTable =
{
{ "aoeloot", aoeLootSubCommandTable }
{ "AoeLoot", AoeLootSubCommandTable }
};
return aoeLootCommandTable;
return AoeLootCommandTable;
}
bool AoeLootCommandScript::EnableAoeLootCommand(ChatHandler* handler, Optional<std::string> /*args*/)
bool AoeLootCommandScript::EnableAoeLootCommand(ChatHandler* handler, Optional<std::string>)
{
if (!IsAoeLootModuleEnabled())
return true;
Player* player = handler->GetSession()->GetPlayer();
if (!player)
return true;
@@ -180,8 +210,8 @@ bool AoeLootCommandScript::EnableAoeLootCommand(ChatHandler* handler, Optional<s
// Check if AOE loot is enabled server-side
if (!IsAoeLootEnabledForPlayer(player))
{
uint32 aoeLootMode = sConfigMgr->GetOption<uint32>("AOELoot.Enable", 2);
switch (aoeLootMode)
uint32 AoeLootMode = sConfigMgr->GetOption<uint32>("AoeLoot.EnableAOELoot", 2);
switch (AoeLootMode)
{
case 0:
handler->PSendSysMessage("AOE looting is completely disabled on this server.");
@@ -198,16 +228,19 @@ bool AoeLootCommandScript::EnableAoeLootCommand(ChatHandler* handler, Optional<s
// Thread-safe update
{
std::lock_guard<std::mutex> lock(aoeLootPreferencesMutex);
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.");
handler->PSendSysMessage("AOE looting has been enabled for your character. Type: '.AoeLoot off' to turn AoE Looting Off.");
return true;
}
bool AoeLootCommandScript::DisableAoeLootCommand(ChatHandler* handler, Optional<std::string> /*args*/)
bool AoeLootCommandScript::DisableAoeLootCommand(ChatHandler* handler, Optional<std::string>)
{
if (!IsAoeLootModuleEnabled())
return true;
Player* player = handler->GetSession()->GetPlayer();
if (!player)
return true;
@@ -216,15 +249,14 @@ bool AoeLootCommandScript::DisableAoeLootCommand(ChatHandler* handler, Optional<
// Thread-safe update
{
std::lock_guard<std::mutex> lock(aoeLootPreferencesMutex);
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.");
handler->PSendSysMessage("AOE looting has been disabled for your character. Type: '.AoeLoot on' to turn AoE Looting on.");
return true;
}
// Rename ValidateLootDistance to ValidateLootingDistance
bool AoeLootCommandScript::ValidateLootingDistance(Player* player, ObjectGuid lguid, float maxDistance)
{
if (!player)
@@ -232,7 +264,7 @@ bool AoeLootCommandScript::ValidateLootingDistance(Player* player, ObjectGuid lg
// Use configured AOE distance if no specific distance provided
if (maxDistance <= 0.0f)
maxDistance = sConfigMgr->GetOption<float>("AOELoot.Range", 55.0f);
maxDistance = sConfigMgr->GetOption<float>("AoeLoot.Range", 55.0f);
if (lguid.IsGameObject())
{
@@ -277,7 +309,6 @@ bool AoeLootCommandScript::ValidateLootingDistance(Player* player, ObjectGuid lg
}
}
// Rename ProcessLootMoney to ProcessCreatureGold
bool AoeLootCommandScript::ProcessCreatureGold(Player* player, Creature* creature)
{
if (!player || !creature)
@@ -341,8 +372,7 @@ bool AoeLootCommandScript::ProcessCreatureGold(Player* player, Creature* creatur
return true;
}
// Rename ProcessLootRelease to ReleaseAndCleanupLoot
void AoeLootCommandScript::ReleaseAndCleanupLoot(ObjectGuid lguid, Player* player, Loot* /*originalLoot*/)
void AoeLootCommandScript::ReleaseAndCleanupLoot(ObjectGuid lguid, Player* player, Loot*)
{
player->SetLootGUID(ObjectGuid::Empty);
player->SendLootRelease(lguid);
@@ -441,9 +471,11 @@ void AoeLootCommandScript::ReleaseAndCleanupLoot(ObjectGuid lguid, Player* playe
}
}
// Rename ProcessLootSlot to ProcessSingleLootSlot
bool AoeLootCommandScript::ProcessSingleLootSlot(Player* player, ObjectGuid lguid, uint8 lootSlot)
{
if (!IsAoeLootModuleEnabled())
return true;
if (!player)
return false;
@@ -600,6 +632,9 @@ bool AoeLootCommandScript::ProcessSingleLootSlot(Player* player, ObjectGuid lgui
bool AoeLootCommandScript::TriggerAoeLootCommand(ChatHandler* handler, Optional<std::string> /*args*/)
{
if (!IsAoeLootModuleEnabled())
return true;
Player* player = handler->GetSession()->GetPlayer();
if (!player)
return true;
@@ -611,8 +646,8 @@ bool AoeLootCommandScript::TriggerAoeLootCommand(ChatHandler* handler, Optional<
return true;
}
float range = sConfigMgr->GetOption<float>("AOELoot.Range", 55.0);
bool debugMode = sConfigMgr->GetOption<bool>("AOELoot.Debug", false);
float range = sConfigMgr->GetOption<float>("AoeLoot.Range", 55.0);
bool debugMode = sConfigMgr->GetOption<bool>("AoeLoot.Debug", false);
std::list<Creature*> nearbyCorpses;
player->GetDeadCreatureListInGrid(nearbyCorpses, range);
@@ -715,25 +750,8 @@ bool AoeLootCommandScript::TriggerAoeLootCommand(ChatHandler* handler, Optional<
}
}
// Handle quest items based on configuration
if (ShouldProcessQuestItemsSeparately())
{
// Process quest items separately (default behavior)
ProcessQuestItemsForPlayer(player, lguid, loot);
}
else
{
// Treat quest items as regular loot (like quest loot party mod)
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 (as regular) in slot {}", questLootSlot);
}
}
}
// Always process quest items using Blizzard logic (only for players who need them)
ProcessQuestItemsForPlayer(player, lguid, loot);
// Handle money
if (loot->gold > 0)
@@ -757,14 +775,22 @@ bool AoeLootCommandScript::TriggerAoeLootCommand(ChatHandler* handler, Optional<
// Display login message to player
void AoeLootPlayer::OnPlayerLogin(Player* player)
{
// 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))
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)
switch (AoeLootMode)
{
case 1:
message += " (solo play only)";
@@ -774,246 +800,58 @@ void AoeLootPlayer::OnPlayerLogin(Player* player)
break;
}
message += ". Type: '.aoeloot off' to turn AoE Looting Off.";
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)
if (!IsAoeLootModuleEnabled())
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
if (!group || !leader)
return;
}
// Apply default loot settings for real player leaders
// Apply default loot settings to the group
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
}
}
// Quest item processing functions
bool AoeLootCommandScript::ShouldProcessQuestItemsSeparately()
{
return !sConfigMgr->GetOption<bool>("AOELoot.QuestItemsAsRegular", false);
}
bool AoeLootCommandScript::IsQuestItemForPlayer(Player* player, uint32 itemId)
{
if (!player)
return false;
const ItemTemplate* itemTemplate = sObjectMgr->GetItemTemplate(itemId);
if (!itemTemplate)
return false;
// Check if this item starts a quest or is a quest item
// Check if this item starts a quest
if (itemTemplate->StartQuest != 0)
return true;
{
uint32 questId = itemTemplate->StartQuest;
// Player must NOT have the quest, must NOT have completed it, and must NOT already have the item
if (!player->HasQuest(questId) &&
player->GetQuestStatus(questId) != QUEST_STATUS_COMPLETE &&
player->GetItemCount(itemId, true) == 0) // true = include bank
{
return true;
}
return false;
}
// Check if player has quests requiring this item
for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
{
uint32 questId = player->GetQuestSlotQuestId(slot);
if (questId == 0)
continue;
Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
if (!quest)
continue;
// Check quest objectives for this item
for (uint8 i = 0; i < QUEST_OBJECTIVES_COUNT; ++i)
{
@@ -1022,7 +860,7 @@ bool AoeLootCommandScript::IsQuestItemForPlayer(Player* player, uint32 itemId)
// Check if player still needs this item for the quest
uint32 currentCount = player->GetItemCount(itemId, true);
uint32 requiredCount = quest->RequiredItemCount[i];
if (currentCount < requiredCount)
{
return true;
@@ -1030,7 +868,7 @@ bool AoeLootCommandScript::IsQuestItemForPlayer(Player* player, uint32 itemId)
}
}
}
return false;
}
@@ -1040,7 +878,7 @@ bool AoeLootCommandScript::ProcessQuestItemsForPlayer(Player* player, ObjectGuid
return false;
bool processedAny = false;
bool debugMode = sConfigMgr->GetOption<bool>("AOELoot.Debug", false);
bool debugMode = sConfigMgr->GetOption<bool>("AoeLoot.Debug", false);
for (uint8 i = 0; i < loot->quest_items.size(); ++i)
{

View File

@@ -1,5 +1,5 @@
#ifndef MODULE_AOELOOT_H
#define MODULE_AOELOOT_H
#ifndef MODULE_AoeLoot_H
#define MODULE_AoeLoot_H
#include "ScriptMgr.h"
#include "Config.h"
@@ -41,22 +41,20 @@ public:
AoeLootCommandScript() : CommandScript("AoeLootCommandScript") {}
ChatCommandTable GetCommands() const override;
// Renamed command handlers (removed "Handle" prefix)
static bool EnableAoeLootCommand(ChatHandler* handler, Optional<std::string> args);
static bool DisableAoeLootCommand(ChatHandler* handler, Optional<std::string> args);
static bool TriggerAoeLootCommand(ChatHandler* handler, Optional<std::string> args);
// Renamed core processing functions
// Core processing functions
static bool ProcessSingleLootSlot(Player* player, ObjectGuid lguid, uint8 lootSlot);
static bool ProcessCreatureGold(Player* player, Creature* creature);
static void ReleaseAndCleanupLoot(ObjectGuid lguid, Player* player, Loot* loot);
// Quest item processing functions
static bool IsQuestItemForPlayer(Player* player, uint32 itemId);
static bool ShouldProcessQuestItemsSeparately();
static bool ProcessQuestItemsForPlayer(Player* player, ObjectGuid lguid, Loot* loot);
// Renamed validation functions
// Validation functions
static bool ValidateLootingDistance(Player* player, ObjectGuid lguid, float maxDistance = 0.0f);
static bool IsAoeLootEnabledForPlayer(Player* player);
};
@@ -66,20 +64,12 @@ class AoeLootGroupScript : public GroupScript
public:
AoeLootGroupScript() : GroupScript("AoeLootGroupScript") {}
void OnCreate(Group* group, Player* leader) override;
void OnChangeLeader(Group* group, ObjectGuid newLeaderGuid, ObjectGuid oldLeaderGuid) override;
void OnDisband(Group* group) override;
private:
// Helper function to convert config value to LootMethod enum
LootMethod GetLootMethodFromConfig(uint32 configValue);
// Helper function to apply default loot settings
void ApplyDefaultLootSettings(Group* group, Player* leader);
// Helper function to check if loot method appears to be manually set
bool IsLootMethodManuallySet(Group* group);
};
void AddSC_AoeLoot();
#endif //MODULE_AOELOOT_H
#endif