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:
8
.editorconfig
Normal file
8
.editorconfig
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
tab_width = 4
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
max_line_length = 80
|
||||||
62
.gitattributes
vendored
Normal file
62
.gitattributes
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
## AUTO-DETECT
|
||||||
|
## Handle line endings automatically for files detected as
|
||||||
|
## text and leave all files detected as binary untouched.
|
||||||
|
## This will handle all files NOT defined below.
|
||||||
|
* text=auto eol=lf
|
||||||
|
|
||||||
|
# Text
|
||||||
|
*.conf
|
||||||
|
*.conf.dist
|
||||||
|
*.txt
|
||||||
|
*.md
|
||||||
|
*.cmake
|
||||||
|
|
||||||
|
# Bash
|
||||||
|
*.sh text
|
||||||
|
|
||||||
|
# Lua if lua module?
|
||||||
|
*.lua
|
||||||
|
|
||||||
|
# SQL
|
||||||
|
*.sql
|
||||||
|
|
||||||
|
# C++
|
||||||
|
*.c text
|
||||||
|
*.cc text
|
||||||
|
*.cxx text
|
||||||
|
*.cpp text
|
||||||
|
*.c++ text
|
||||||
|
*.hpp text
|
||||||
|
*.h text
|
||||||
|
*.h++ text
|
||||||
|
*.hh text
|
||||||
|
|
||||||
|
|
||||||
|
## For documentation
|
||||||
|
|
||||||
|
# Documents
|
||||||
|
*.doc diff=astextplain
|
||||||
|
*.DOC diff=astextplain
|
||||||
|
*.docx diff=astextplain
|
||||||
|
*.DOCX diff=astextplain
|
||||||
|
*.dot diff=astextplain
|
||||||
|
*.DOT diff=astextplain
|
||||||
|
*.pdf diff=astextplain
|
||||||
|
*.PDF diff=astextplain
|
||||||
|
*.rtf diff=astextplain
|
||||||
|
*.RTF diff=astextplain
|
||||||
|
|
||||||
|
|
||||||
|
# Graphics
|
||||||
|
*.png binary
|
||||||
|
*.jpg binary
|
||||||
|
*.jpeg binary
|
||||||
|
*.gif binary
|
||||||
|
*.tif binary
|
||||||
|
*.tiff binary
|
||||||
|
*.ico binary
|
||||||
|
# SVG treated as an asset (binary) by default. If you want to treat it as text,
|
||||||
|
# comment-out the following line and uncomment the line after.
|
||||||
|
*.svg binary
|
||||||
|
#*.svg text
|
||||||
|
*.eps binary
|
||||||
49
.gitignore
vendored
Normal file
49
.gitignore
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
!.gitignore
|
||||||
|
|
||||||
|
#
|
||||||
|
#Generic
|
||||||
|
#
|
||||||
|
|
||||||
|
.directory
|
||||||
|
.mailmap
|
||||||
|
*.orig
|
||||||
|
*.rej
|
||||||
|
*~
|
||||||
|
.hg/
|
||||||
|
*.kdev*
|
||||||
|
.DS_Store
|
||||||
|
CMakeLists.txt.user
|
||||||
|
*.bak
|
||||||
|
*.patch
|
||||||
|
*.diff
|
||||||
|
*.REMOTE.*
|
||||||
|
*.BACKUP.*
|
||||||
|
*.BASE.*
|
||||||
|
*.LOCAL.*
|
||||||
|
|
||||||
|
#
|
||||||
|
# IDE & other softwares
|
||||||
|
#
|
||||||
|
/.settings/
|
||||||
|
/.externalToolBuilders/*
|
||||||
|
# exclude in all levels
|
||||||
|
nbproject/
|
||||||
|
.sync.ffs_db
|
||||||
|
*.kate-swp
|
||||||
|
|
||||||
|
#
|
||||||
|
# Eclipse
|
||||||
|
#
|
||||||
|
*.pydevproject
|
||||||
|
.metadata
|
||||||
|
.gradle
|
||||||
|
tmp/
|
||||||
|
*.tmp
|
||||||
|
*.swp
|
||||||
|
*~.nib
|
||||||
|
local.properties
|
||||||
|
.settings/
|
||||||
|
.loadpath
|
||||||
|
.project
|
||||||
|
.cproject
|
||||||
|
.vscode/c_cpp_properties.json
|
||||||
38
.vscode/settings.json
vendored
Normal file
38
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"files.associations": {
|
||||||
|
"list": "cpp",
|
||||||
|
"vector": "cpp",
|
||||||
|
"xstring": "cpp",
|
||||||
|
"xtree": "cpp",
|
||||||
|
"xutility": "cpp",
|
||||||
|
"atomic": "cpp",
|
||||||
|
"compare": "cpp",
|
||||||
|
"concepts": "cpp",
|
||||||
|
"cstddef": "cpp",
|
||||||
|
"cstdint": "cpp",
|
||||||
|
"cstdio": "cpp",
|
||||||
|
"cstdlib": "cpp",
|
||||||
|
"cstring": "cpp",
|
||||||
|
"ctime": "cpp",
|
||||||
|
"cwchar": "cpp",
|
||||||
|
"exception": "cpp",
|
||||||
|
"initializer_list": "cpp",
|
||||||
|
"iosfwd": "cpp",
|
||||||
|
"limits": "cpp",
|
||||||
|
"map": "cpp",
|
||||||
|
"memory": "cpp",
|
||||||
|
"mutex": "cpp",
|
||||||
|
"new": "cpp",
|
||||||
|
"ratio": "cpp",
|
||||||
|
"stdexcept": "cpp",
|
||||||
|
"stop_token": "cpp",
|
||||||
|
"system_error": "cpp",
|
||||||
|
"thread": "cpp",
|
||||||
|
"tuple": "cpp",
|
||||||
|
"type_traits": "cpp",
|
||||||
|
"typeinfo": "cpp",
|
||||||
|
"utility": "cpp",
|
||||||
|
"xmemory": "cpp",
|
||||||
|
"xtr1common": "cpp"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,46 +3,58 @@
|
|||||||
########################################
|
########################################
|
||||||
#
|
#
|
||||||
# AOELoot.Enable
|
# AOELoot.Enable
|
||||||
# Description: Enables Module
|
# Default: 2 - (Enabled for solo and group play)
|
||||||
# Default: 0 - (Disabled)
|
# 1 - (Enabled for solo play only)
|
||||||
# 1 - (Enabled)
|
# 0 - (Disabled)
|
||||||
#
|
|
||||||
|
|
||||||
AOELoot.Enable = 1
|
AOELoot.Enable = 2
|
||||||
|
|
||||||
#
|
|
||||||
# AOELoot.Message
|
# AOELoot.Message
|
||||||
# Description: Enable message on login
|
# Description: Enable module notification message on login
|
||||||
# Default: 0 - (Disabled)
|
# Default: 0 - (Disabled)
|
||||||
# 1 - (Enabled)
|
# 1 - (Enabled)
|
||||||
#
|
|
||||||
|
|
||||||
AOELoot.Message = 0
|
AOELoot.Message = 0
|
||||||
|
|
||||||
#
|
|
||||||
# AOELoot.Range
|
# AOELoot.Range
|
||||||
# Description: Maximum reach range search loot.
|
# Description: Radius for AoE looting area
|
||||||
# Default: 55.0
|
# Default: 55.0
|
||||||
#
|
|
||||||
|
|
||||||
AOELoot.Range = 55.0
|
AOELoot.Range = 55.0
|
||||||
|
|
||||||
#
|
# AOELoot.DefaultLootMethod
|
||||||
# AOELoot.Group
|
# Description: Sets the default loot method for newly created groups
|
||||||
# Description: Enables area loot if the player is in a group
|
# Values: 0 = Free For All (everyone can loot everything)
|
||||||
# Default: 1 (Enabled)
|
# 1 = Round Robin (items are distributed in turn)
|
||||||
# Possible values: 0 - (Disabled)
|
# 2 = Master Loot (only master looter can distribute items)
|
||||||
# 1 - (Enabled)
|
# 3 = Group Loot (group rolls on items above threshold)
|
||||||
#
|
# 4 = Need Before Greed (players roll Need/Greed/Pass on items above threshold)
|
||||||
|
# Default: 3 (Group Loot)
|
||||||
|
|
||||||
AOELoot.Group = 1
|
AOELoot.DefaultLootMethod = 3
|
||||||
|
|
||||||
|
#
|
||||||
|
# AOELoot.DefaultLootThreshold
|
||||||
|
# Description: Item quality threshold for group loot rolls (applies only to Group Loot and Need Before Greed)
|
||||||
|
# Values: 0 = Poor (Gray)
|
||||||
|
# 1 = Common (White)
|
||||||
|
# 2 = Uncommon (Green)
|
||||||
|
# 3 = Rare (Blue)
|
||||||
|
# 4 = Epic (Purple)
|
||||||
|
# 5 = Legendary (Orange)
|
||||||
|
# Default: 2 (Uncommon)
|
||||||
|
|
||||||
|
AOELoot.DefaultLootThreshold = 2
|
||||||
|
|
||||||
# AOELoot.Debug
|
# AOELoot.Debug
|
||||||
# Description: Enables debuging mode. This will print out the items Detected values of loot in the chat console. The values in the chat should match the looted values, give or take the main looted creature.
|
# Description: Enables debugging mode.
|
||||||
# Default: 0 (Disabled)
|
# Default: 0 (Disabled)
|
||||||
# Possible values: 0 - (Disabled)
|
# 1 - (Enabled)
|
||||||
# 1 - (Enabled)
|
|
||||||
#
|
|
||||||
|
|
||||||
AOELoot.Debug = 0
|
AOELoot.Debug = 0
|
||||||
|
|
||||||
|
# Chat Commands
|
||||||
|
# .aoeloot off If module is enabled, disables AoE looting for player
|
||||||
|
# .aoeloot on If module is enabled, reenables AoE looting for player
|
||||||
|
# .aoeloot lootall Loot all enemies within AOELoot.Range
|
||||||
|
#
|
||||||
|
|||||||
0
include.sh
Normal file
0
include.sh
Normal file
758
src/aoe_loot.cpp
758
src/aoe_loot.cpp
@@ -18,49 +18,147 @@
|
|||||||
#include "Corpse.h"
|
#include "Corpse.h"
|
||||||
#include "Group.h"
|
#include "Group.h"
|
||||||
#include "ObjectMgr.h"
|
#include "ObjectMgr.h"
|
||||||
|
#include <mutex>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
using namespace Acore::ChatCommands;
|
using namespace Acore::ChatCommands;
|
||||||
using namespace WorldPackets;
|
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
|
// Define the roll vote enum value - use the correct RollVote enum
|
||||||
#ifndef NOT_EMITED_YET
|
#ifndef NOT_EMITED_YET
|
||||||
#define NOT_EMITED_YET RollVote(0)
|
#define NOT_EMITED_YET RollVote(0)
|
||||||
#endif
|
#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)
|
bool AoeLootServer::CanPacketReceive(WorldSession* session, WorldPacket& packet)
|
||||||
{
|
{
|
||||||
if (packet.GetOpcode() == CMSG_LOOT)
|
if (packet.GetOpcode() == CMSG_LOOT)
|
||||||
{
|
{
|
||||||
Player* player = session->GetPlayer();
|
Player* player = session->GetPlayer();
|
||||||
if (player)
|
if (!player)
|
||||||
{
|
return true;
|
||||||
uint64 guid = player->GetGUID().GetRawValue();
|
|
||||||
|
|
||||||
// Check if player has explicitly disabled AOE loot
|
// Check if AOE loot is enabled for this player's context
|
||||||
if (playerAoeLootEnabled.find(guid) != playerAoeLootEnabled.end() &&
|
if (!AoeLootCommandScript::IsAoeLootEnabledForPlayer(player))
|
||||||
!playerAoeLootEnabled[guid])
|
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
|
// Let normal looting proceed
|
||||||
return true;
|
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());
|
ChatHandler handler(player->GetSession());
|
||||||
handler.ParseCommands(".aoeloot startaoeloot");
|
handler.ParseCommands(".aoeloot lootall");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ChatCommandTable AoeLootCommandScript::GetCommands() const
|
ChatCommandTable AoeLootCommandScript::GetCommands() const
|
||||||
{
|
{
|
||||||
static ChatCommandTable aoeLootSubCommandTable =
|
static ChatCommandTable aoeLootSubCommandTable =
|
||||||
{
|
{
|
||||||
{ "startaoeloot", HandleStartAoeLootCommand, SEC_PLAYER, Console::No },
|
{ "lootall", TriggerAoeLootCommand, SEC_PLAYER, Console::No },
|
||||||
{ "on", HandleAoeLootOnCommand, SEC_PLAYER, Console::No },
|
{ "on", EnableAoeLootCommand, SEC_PLAYER, Console::No },
|
||||||
{ "off", HandleAoeLootOffCommand, SEC_PLAYER, Console::No }
|
{ "off", DisableAoeLootCommand, SEC_PLAYER, Console::No }
|
||||||
};
|
};
|
||||||
|
|
||||||
static ChatCommandTable aoeLootCommandTable =
|
static ChatCommandTable aoeLootCommandTable =
|
||||||
@@ -71,126 +169,127 @@ ChatCommandTable AoeLootCommandScript::GetCommands() const
|
|||||||
return aoeLootCommandTable;
|
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();
|
Player* player = handler->GetSession()->GetPlayer();
|
||||||
if (!player)
|
if (!player)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
uint64 guid = player->GetGUID().GetRawValue();
|
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.");
|
handler->PSendSysMessage("AOE looting has been enabled for your character. Type: '.aoeloot off' to turn AoE Looting Off.");
|
||||||
return true;
|
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();
|
Player* player = handler->GetSession()->GetPlayer();
|
||||||
if (!player)
|
if (!player)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
uint64 guid = player->GetGUID().GetRawValue();
|
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.");
|
handler->PSendSysMessage("AOE looting has been disabled for your character. Type: '.aoeloot on' to turn AoE Looting on.");
|
||||||
return true;
|
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);
|
if (!player)
|
||||||
player->SendLootRelease(lguid);
|
return false;
|
||||||
player->RemoveUnitFlag(UNIT_FLAG_LOOTING);
|
|
||||||
|
|
||||||
if (!player->IsInWorld())
|
// Use configured AOE distance if no specific distance provided
|
||||||
return;
|
if (maxDistance <= 0.0f)
|
||||||
|
maxDistance = sConfigMgr->GetOption<float>("AOELoot.Range", 55.0f);
|
||||||
|
|
||||||
if (lguid.IsGameObject())
|
if (lguid.IsGameObject())
|
||||||
{
|
{
|
||||||
GameObject* go = player->GetMap()->GetGameObject(lguid);
|
GameObject* go = player->GetMap()->GetGameObject(lguid);
|
||||||
|
if (!go)
|
||||||
|
return false;
|
||||||
|
|
||||||
// not check distance for GO in case owned GO (fishing bobber case, for example) or Fishing hole GO
|
// Special cases for owned objects or fishing holes
|
||||||
if (!go || ((go->GetOwnerGUID() != player->GetGUID() && go->GetGoType() != GAMEOBJECT_TYPE_FISHINGHOLE) && !go->IsWithinDistInMap(player)))
|
if (go->GetOwnerGUID() == player->GetGUID() || go->GetGoType() == GAMEOBJECT_TYPE_FISHINGHOLE)
|
||||||
{
|
return true;
|
||||||
player->SendLootRelease(lguid);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
loot = &go->loot;
|
return go->IsWithinDistInMap(player, maxDistance);
|
||||||
|
|
||||||
}
|
|
||||||
else if (lguid.IsCorpse()) // ONLY remove insignia at BG
|
|
||||||
{
|
|
||||||
Corpse* corpse = ObjectAccessor::GetCorpse(*player, lguid);
|
|
||||||
|
|
||||||
if (!corpse)
|
|
||||||
return;
|
|
||||||
|
|
||||||
loot = &corpse->loot;
|
|
||||||
}
|
}
|
||||||
else if (lguid.IsItem())
|
else if (lguid.IsItem())
|
||||||
{
|
{
|
||||||
Item* pItem = player->GetItemByGuid(lguid);
|
Item* pItem = player->GetItemByGuid(lguid);
|
||||||
if (!pItem)
|
return (pItem != nullptr); // Items in inventory don't need distance check
|
||||||
return;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
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);
|
Creature* creature = player->GetMap()->GetCreature(lguid);
|
||||||
|
if (!creature)
|
||||||
|
return false;
|
||||||
|
|
||||||
// Skip distance check for dead creatures (corpses)
|
// For pickpocketing, use strict interaction distance
|
||||||
// Keep distance check for pickpocketing (live creatures)
|
if (creature->IsAlive() && player->IsClass(CLASS_ROGUE, CLASS_CONTEXT_ABILITY) &&
|
||||||
bool isPickpocketing = creature && creature->IsAlive() && player->IsClass(CLASS_ROGUE, CLASS_CONTEXT_ABILITY) && creature->loot.loot_type == LOOT_PICKPOCKETING;
|
creature->loot.loot_type == LOOT_PICKPOCKETING)
|
||||||
|
|
||||||
// Only check distance for pickpocketing, not for corpse looting
|
|
||||||
if (isPickpocketing && !creature->IsWithinDistInMap(player, INTERACTION_DISTANCE))
|
|
||||||
{
|
{
|
||||||
player->SendLootError(lguid, LOOT_ERROR_TOO_FAR);
|
return creature->IsWithinDistInMap(player, INTERACTION_DISTANCE);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loot = &creature->loot;
|
// For corpses, use AOE distance but still validate
|
||||||
|
return creature->IsWithinDistInMap(player, maxDistance);
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle gold looting without distance checks
|
// Rename ProcessLootMoney to ProcessCreatureGold
|
||||||
bool AoeLootCommandScript::ProcessLootMoney(Player* player, Creature* creature)
|
bool AoeLootCommandScript::ProcessCreatureGold(Player* player, Creature* creature)
|
||||||
{
|
{
|
||||||
if (!player || !creature)
|
if (!player || !creature)
|
||||||
return false;
|
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;
|
Loot* loot = &creature->loot;
|
||||||
if (!loot || loot->gold == 0)
|
if (!loot || loot->gold == 0)
|
||||||
return false;
|
return false;
|
||||||
@@ -219,7 +318,7 @@ bool AoeLootCommandScript::ProcessLootMoney(Player* player, Creature* creature)
|
|||||||
|
|
||||||
WorldPacket data(SMSG_LOOT_MONEY_NOTIFY, 4 + 1);
|
WorldPacket data(SMSG_LOOT_MONEY_NOTIFY, 4 + 1);
|
||||||
data << uint32(goldPerPlayer);
|
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);
|
groupMember->GetSession()->SendPacket(&data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,7 +330,7 @@ bool AoeLootCommandScript::ProcessLootMoney(Player* player, Creature* creature)
|
|||||||
|
|
||||||
WorldPacket data(SMSG_LOOT_MONEY_NOTIFY, 4 + 1);
|
WorldPacket data(SMSG_LOOT_MONEY_NOTIFY, 4 + 1);
|
||||||
data << uint32(loot->gold);
|
data << uint32(loot->gold);
|
||||||
data << uint8(1); // "You loot..."
|
data << uint8(1);
|
||||||
player->GetSession()->SendPacket(&data);
|
player->GetSession()->SendPacket(&data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,19 +341,126 @@ bool AoeLootCommandScript::ProcessLootMoney(Player* player, Creature* creature)
|
|||||||
return true;
|
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)
|
if (!player)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// Validate distance first
|
||||||
|
if (!ValidateLootingDistance(player, lguid))
|
||||||
|
{
|
||||||
|
player->SendLootError(lguid, LOOT_ERROR_TOO_FAR);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Loot* loot = nullptr;
|
Loot* loot = nullptr;
|
||||||
float aoeDistance = sConfigMgr->GetOption<float>("AOELoot.Range", 55.0f);
|
|
||||||
|
|
||||||
// Get the loot object based on the GUID type
|
// Get the loot object based on the GUID type
|
||||||
if (lguid.IsGameObject())
|
if (lguid.IsGameObject())
|
||||||
{
|
{
|
||||||
GameObject* go = player->GetMap()->GetGameObject(lguid);
|
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);
|
player->SendLootRelease(lguid);
|
||||||
return false;
|
return false;
|
||||||
@@ -284,13 +490,9 @@ bool AoeLootCommandScript::ProcessLootSlot(Player* player, ObjectGuid lguid, uin
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
Creature* creature = player->GetMap()->GetCreature(lguid);
|
Creature* creature = player->GetMap()->GetCreature(lguid);
|
||||||
// Skip distance check for dead creatures (corpses)
|
if (!creature)
|
||||||
// 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))
|
|
||||||
{
|
{
|
||||||
player->SendLootError(lguid, LOOT_ERROR_TOO_FAR);
|
player->SendLootRelease(lguid);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
loot = &creature->loot;
|
loot = &creature->loot;
|
||||||
@@ -305,12 +507,20 @@ bool AoeLootCommandScript::ProcessLootSlot(Player* player, ObjectGuid lguid, uin
|
|||||||
LootItem* lootItem = nullptr;
|
LootItem* lootItem = nullptr;
|
||||||
InventoryResult msg = EQUIP_ERR_OK;
|
InventoryResult msg = EQUIP_ERR_OK;
|
||||||
bool isGroupLoot = false;
|
bool isGroupLoot = false;
|
||||||
bool isFFA = loot->items[lootSlot].freeforall;
|
bool isFFA = false;
|
||||||
bool isMasterLooter = false;
|
bool isMasterLooter = false;
|
||||||
bool isRoundRobin = false;
|
bool isRoundRobin = false;
|
||||||
bool isThreshold = false;
|
bool isThreshold = false;
|
||||||
LootMethod lootMethod = GROUP_LOOT;
|
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)
|
if (group)
|
||||||
{
|
{
|
||||||
@@ -319,8 +529,13 @@ bool AoeLootCommandScript::ProcessLootSlot(Player* player, ObjectGuid lguid, uin
|
|||||||
isMasterLooter = (lootMethod == MASTER_LOOT);
|
isMasterLooter = (lootMethod == MASTER_LOOT);
|
||||||
isRoundRobin = (lootMethod == ROUND_ROBIN);
|
isRoundRobin = (lootMethod == ROUND_ROBIN);
|
||||||
isGroupLoot = (lootMethod == GROUP_LOOT || lootMethod == NEED_BEFORE_GREED);
|
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
|
// 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);
|
group->SendLootStartRoll(60, player->GetMapId(), roll);
|
||||||
}
|
}
|
||||||
return true;
|
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 player is removing the last LootItem, delete the empty container
|
||||||
if (loot->isLooted() && lguid.IsItem())
|
if (loot->isLooted() && lguid.IsItem())
|
||||||
{
|
{
|
||||||
ProcessLootRelease(lguid, player, loot);
|
ReleaseAndCleanupLoot(lguid, player, loot);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AoeLootCommandScript::TriggerAoeLootCommand(ChatHandler* handler, Optional<std::string> /*args*/)
|
||||||
|
|
||||||
bool AoeLootCommandScript::HandleStartAoeLootCommand(ChatHandler* handler, Optional<std::string> /*args*/)
|
|
||||||
{
|
{
|
||||||
if (!sConfigMgr->GetOption<bool>("AOELoot.Enable", true))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
Player* player = handler->GetSession()->GetPlayer();
|
Player* player = handler->GetSession()->GetPlayer();
|
||||||
if (!player)
|
if (!player)
|
||||||
return true;
|
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);
|
float range = sConfigMgr->GetOption<float>("AOELoot.Range", 55.0);
|
||||||
bool debugMode = sConfigMgr->GetOption<bool>("AOELoot.Debug", false);
|
bool debugMode = sConfigMgr->GetOption<bool>("AOELoot.Debug", false);
|
||||||
|
|
||||||
@@ -474,94 +691,46 @@ bool AoeLootCommandScript::HandleStartAoeLootCommand(ChatHandler* handler, Optio
|
|||||||
{
|
{
|
||||||
ObjectGuid lguid = creature->GetGUID();
|
ObjectGuid lguid = creature->GetGUID();
|
||||||
Loot* loot = &creature->loot;
|
Loot* loot = &creature->loot;
|
||||||
if (validCorpses.size() <= 1)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!loot)
|
if (!loot)
|
||||||
continue;
|
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);
|
player->SetLootGUID(lguid);
|
||||||
|
|
||||||
// Process quest items
|
// Process all regular items (includes quest items and FFA items automatically)
|
||||||
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
|
|
||||||
for (uint8 lootSlot = 0; lootSlot < loot->items.size(); ++lootSlot)
|
for (uint8 lootSlot = 0; lootSlot < loot->items.size(); ++lootSlot)
|
||||||
{
|
{
|
||||||
player->SetLootGUID(lguid);
|
ProcessSingleLootSlot(player, lguid, lootSlot);
|
||||||
ProcessLootSlot(player, lguid, lootSlot);
|
if (debugMode)
|
||||||
if (debugMode && lootSlot < loot->items.size())
|
|
||||||
{
|
{
|
||||||
LOG_DEBUG("module.aoe_loot", "AOE Loot: looted item from slot {} (ID: {}) from {}", lootSlot, loot->items[lootSlot].itemid, lguid.ToString());
|
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
|
// Handle money
|
||||||
if (loot->gold > 0)
|
if (loot->gold > 0)
|
||||||
{
|
{
|
||||||
uint32 goldAmount = loot->gold;
|
uint32 goldAmount = loot->gold;
|
||||||
ProcessLootMoney(player, creature);
|
ProcessCreatureGold(player, creature);
|
||||||
if (debugMode)
|
if (debugMode)
|
||||||
{
|
{
|
||||||
LOG_DEBUG("module.aoe_loot", "AOE Loot: Looted {} copper from {}", goldAmount, lguid.ToString());
|
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())
|
if (loot->isLooted())
|
||||||
{
|
{
|
||||||
ProcessLootRelease(lguid, player, loot);
|
ReleaseAndCleanupLoot(lguid, player, loot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -579,10 +748,230 @@ bool AoeLootCommandScript::HandleStartAoeLootCommand(ChatHandler* handler, Optio
|
|||||||
// Display login message to player
|
// Display login message to player
|
||||||
void AoeLootPlayer::OnPlayerLogin(Player* player)
|
void AoeLootPlayer::OnPlayerLogin(Player* player)
|
||||||
{
|
{
|
||||||
if (sConfigMgr->GetOption<bool>("AOELoot.Enable", true) &&
|
// Check if AOE loot is enabled at all (any mode > 0)
|
||||||
sConfigMgr->GetOption<bool>("AOELoot.Message", true))
|
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 AoeLootPlayer();
|
||||||
new AoeLootServer();
|
new AoeLootServer();
|
||||||
new AoeLootCommandScript();
|
new AoeLootCommandScript();
|
||||||
|
new AoeLootGroupScript();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#include <list>
|
#include <list>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <ObjectGuid.h>
|
#include <ObjectGuid.h>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
using namespace Acore::ChatCommands;
|
using namespace Acore::ChatCommands;
|
||||||
|
|
||||||
@@ -22,7 +23,6 @@ class AoeLootServer : public ServerScript
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
AoeLootServer() : ServerScript("AoeLootServer") {}
|
AoeLootServer() : ServerScript("AoeLootServer") {}
|
||||||
|
|
||||||
bool CanPacketReceive(WorldSession* session, WorldPacket& packet) override;
|
bool CanPacketReceive(WorldSession* session, WorldPacket& packet) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -30,9 +30,7 @@ class AoeLootPlayer : public PlayerScript
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
AoeLootPlayer() : PlayerScript("AoeLootPlayer") {}
|
AoeLootPlayer() : PlayerScript("AoeLootPlayer") {}
|
||||||
|
|
||||||
void OnPlayerLogin(Player* player) override;
|
void OnPlayerLogin(Player* player) override;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class AoeLootCommandScript : public CommandScript
|
class AoeLootCommandScript : public CommandScript
|
||||||
@@ -41,16 +39,40 @@ public:
|
|||||||
AoeLootCommandScript() : CommandScript("AoeLootCommandScript") {}
|
AoeLootCommandScript() : CommandScript("AoeLootCommandScript") {}
|
||||||
ChatCommandTable GetCommands() const override;
|
ChatCommandTable GetCommands() const override;
|
||||||
|
|
||||||
static bool HandleAoeLootOnCommand(ChatHandler* handler, Optional<std::string> args);
|
// Renamed command handlers (removed "Handle" prefix)
|
||||||
static bool HandleAoeLootOffCommand(ChatHandler* handler, Optional<std::string> args);
|
static bool EnableAoeLootCommand(ChatHandler* handler, Optional<std::string> args);
|
||||||
static bool HandleStartAoeLootCommand(ChatHandler* handler, Optional<std::string> args);
|
static bool DisableAoeLootCommand(ChatHandler* handler, Optional<std::string> args);
|
||||||
static bool ProcessLootSlot(Player* player, ObjectGuid lguid, uint8 lootSlot);
|
static bool TriggerAoeLootCommand(ChatHandler* handler, Optional<std::string> args);
|
||||||
static bool ProcessLootMoney(Player* player, Creature* creature);
|
|
||||||
static void ProcessLootRelease(ObjectGuid lguid, Player* player, Loot* loot);
|
|
||||||
|
|
||||||
|
// Renamed 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);
|
||||||
|
|
||||||
|
// Renamed validation functions
|
||||||
|
static bool ValidateLootingDistance(Player* player, ObjectGuid lguid, float maxDistance = 0.0f);
|
||||||
|
static bool IsAoeLootEnabledForPlayer(Player* player);
|
||||||
|
};
|
||||||
|
|
||||||
|
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();
|
void AddSC_AoeLoot();
|
||||||
|
|
||||||
|
|
||||||
#endif //MODULE_AOELOOT_H
|
#endif //MODULE_AOELOOT_H
|
||||||
|
|||||||
Reference in New Issue
Block a user