Initial Upload

This commit is contained in:
Flerp 2025-11-08 14:06:05 -08:00
commit c1d0a3cc64
5 changed files with 1039 additions and 0 deletions

81
README.md Normal file
View File

@ -0,0 +1,81 @@
# AOE Loot Module for AzerothCore
![AzerothCore](https://img.shields.io/badge/azerothcore-mod-blue.svg)
## Description
This module enhances the looting experience in AzerothCore by implementing Area-of-Effect (AOE) looting functionality. It allows players to loot multiple corpses at once within a defined radius, significantly improving quality of life for players.
The module has been built from the ground up to provide better performance, stability, and integration with the latest AzerothCore versions.
This module does NOT condense, consolidate, or delete unlooted items from corpses. There is built-in safety measures to guarantee loot will never be lost. The module will automatically default to the preexisting in-game looting logic if only 1 lootable corpse is within the distance set in the config file.
If you wish to demo the AoE Looting feature, you can try it out on my AzerothCore private server project. The Best WoW Private Server: https://thebestwowprivateserver.com/
## Features
- Automatically iterate through all nearby corpses and loot all corpses that are assigned to the looting player with a single interaction.
- Configurable loot radius.
- Compatible with other loot-related modules and core functionality (That I know of. This module runs the same logic as the in-game looting logic minus the distance limitations and a few other very niche cases).
- Minimal performance impact
- Customizable messages and notifications
- The looting logic and execution is handed off to a worker thread and will not hold up your network thread.
## Requirements
- AzerothCore v3.0.0+
- CMake 3.13+ (compile-time only)
## Installation
### Source Code
You can clone the module directly into your AzerothCore modules directory:
```bash
cd path/to/azerothcore/modules
git clone https://github.com/TerraByte-tbwps/mod-aoe-loot.git
```
## Configuration
You can find the configuration file in the module's `conf` directory or folder. The configuration options can be set in `mod_aoe_loot.conf` or make a copy of the `mod_aoe_loot.conf.dist` and remove the `.dist` at the end of the file name.
Key terms for none coders:
A "directory" is the same thing as a "folder" if your using a Microsoft Windows computer/server.
## Usage
Once installed and enabled, the module works automatically. When a player loots a corpse, all eligible corpses within the configured radius will also be looted.
### Commands
| Command | Description | Access Level |
|-----------------------------|-----------------------------------------------|--------------|
| `.aoeloot on` | Enable AOE looting for your character. | Player |
| `.aoeloot off` | Disable AOE looting for your character. | Player |
| `.aoeloot debug` | Toggle the debugger for more details. | Player |
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
1. Fork it
2. Create your feature branch (`git checkout -b feature/yourfeature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin feature/yourfeature`)
5. Create a new Pull Request
## Credits
* [TerraByte-tbwps](https://github.com/TerraByte-tbwps) - Author and maintainer
* Original AzerothCore community and contributors
## License
This module is free for the AzerothCore community to use and modify as needed.
## Links
* [AzerothCore](https://github.com/azerothcore/azerothcore-wotlk)
* [Module Documentation](https://www.azerothcore.org/catalogue.html#terrabytetbwps-mod-aoe-loot)

View File

@ -0,0 +1,65 @@
########################################
# AoeLoot module configuration
########################################
#
# AOELoot.Enable
# Description: Enables Module
# Default: 1 - (Enabled)
# 0 - (Disabled)
#
AOELoot.Enable = 1
#
# AOELoot.Message
# Description: Enable message on login
# Default: 1 - (Enabled)
# 0 - (Disabled)
#
AOELoot.Message = 1
#
# AOELoot.Range
# Description: Maximum reach range search loot.
# Default: 55.0
#
AOELoot.Range = 55.0
#
# AOELoot.MoneyShareDistanceMultiplier
# Description: Distance multiplier for money sharing (multiplies AOELoot.Range)
# Default: 2.0
#
AOELoot.MoneyShareDistanceMultiplier = 2.0
#
# AOELoot.Group
# Description: Enables area loot if the player is in a group
# Default: 1 (Enabled)
# Possible values: 0 - (Disabled)
# 1 - (Enabled)
#
AOELoot.Group = 1
#
# AOELoot.CorpseThreshold
# Description: The number of corpses that need to be in range for AoE looting to initiate (If the number of corpses is lower than this number in the "AOELoot.Range" AoE looting will not initiate and the default in-game looting logic will run instead).
# Default: 2 (Default) If the number of corpses in your AOELoot.Range is 1, the default game looting will be used instead of AoE loot.
#
AOELoot.CorpseThreshold = 2
# 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.
# Default: 1 (Enabled)
# Possible values: 0 - (Disabled)
# 1 - (Enabled)
#
AOELoot.Debug = 1

782
src/aoe_loot.cpp Normal file
View File

@ -0,0 +1,782 @@
#include "aoe_loot.h"
#include "ScriptMgr.h"
#include "World.h"
#include "LootMgr.h"
#include "ServerScript.h"
#include "WorldSession.h"
#include "WorldPacket.h"
#include "Player.h"
#include "Chat.h"
#include "ChatCommand.h"
#include "ChatCommandArgs.h"
#include "WorldObjectScript.h"
#include "Creature.h"
#include "Config.h"
#include "Log.h"
#include "Map.h"
#include <fmt/format.h>
#include "Corpse.h"
#include "Group.h"
#include "ObjectMgr.h"
using namespace Acore::ChatCommands;
using namespace WorldPackets;
std::map<uint64, bool> AoeLootCommandScript::playerAoeLootEnabled;
std::map<uint64, bool> AoeLootCommandScript::playerAoeLootDebug;
// Server packet handler. >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //
// >>>>> This is the entry point. This packet triggers the AOE loot system. <<<<< //
bool AoeLootManager::CanPacketReceive(WorldSession* session, WorldPacket& packet)
{
if (packet.GetOpcode() == CMSG_LOOT)
{
Player* player = session->GetPlayer();
if (player)
{
uint64 guid = player->GetGUID().GetRawValue();
// >>>>> Do not remove the hardcoded value. It is here for crash & data protection. <<<<< //
if (!AoeLootCommandScript::hasPlayerAoeLootEnabled(guid))
{
AoeLootCommandScript::SetPlayerAoeLootEnabled(guid, sConfigMgr->GetOption<bool>("AOELoot.Enable", true));
}
if (!AoeLootCommandScript::hasPlayerAoeLootDebug(guid))
{
AoeLootCommandScript::SetPlayerAoeLootDebug(guid, sConfigMgr->GetOption<bool>("AOELoot.Debug", false));
}
// >>>>> Aoe looting enabled check. <<<<< //
if (AoeLootCommandScript::hasPlayerAoeLootEnabled(guid) && AoeLootCommandScript::GetPlayerAoeLootEnabled(guid))
{
// >>>>> Aoe loot start. <<<<< //
AoeLootCommandScript::DebugMessage(player, "AOE Looting started.");
ChatHandler handler(player->GetSession());
handler.ParseCommands(".aoeloot startaoeloot");
}
}
}
return true;
}
// Server packet handler end. <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< //
// Command table implementation. >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //
ChatCommandTable AoeLootCommandScript::GetCommands() const
{
static ChatCommandTable aoeLootSubCommandTable =
{
{ "startaoeloot", HandleStartAoeLootCommand, SEC_PLAYER, Console::No },
{ "toggle", HandleAoeLootToggleCommand, SEC_PLAYER, Console::No },
{ "on", HandleAoeLootOnCommand, SEC_PLAYER, Console::No },
{ "off", HandleAoeLootOffCommand, SEC_PLAYER, Console::No },
{ "debug on", HandleAoeLootDebugOnCommand, SEC_PLAYER, Console::No },
{ "debug", HandleAoeLootDebugToggleCommand, SEC_PLAYER, Console::No },
{ "debug off", HandleAoeLootDebugOffCommand, SEC_PLAYER, Console::No }
};
static ChatCommandTable aoeLootCommandTable =
{
{ "aoeloot", aoeLootSubCommandTable }
};
return aoeLootCommandTable;
}
// Command table implementation end. <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< //
// Getters and setters for player AOE loot settings. >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //
bool AoeLootCommandScript::GetPlayerAoeLootEnabled(uint64 guid)
{
auto it = playerAoeLootEnabled.find(guid);
if (it != playerAoeLootEnabled.end())
return it->second;
return false;
}
bool AoeLootCommandScript::GetPlayerAoeLootDebug(uint64 guid)
{
auto it = playerAoeLootDebug.find(guid);
if (it != playerAoeLootDebug.end())
return it->second;
return false;
}
void AoeLootCommandScript::SetPlayerAoeLootEnabled(uint64 guid, bool mode)
{
if (playerAoeLootEnabled.find(guid) == playerAoeLootEnabled.end())
{
playerAoeLootEnabled[guid] = mode;
AoeLootCommandScript::DebugMessage(nullptr, fmt::format("Set AOE loot enabled for GUID {}: {}", guid, mode));
}
else
{
playerAoeLootEnabled[guid] = mode;
AoeLootCommandScript::DebugMessage(nullptr, fmt::format("Updated AOE loot enabled for GUID {}: {}", guid, mode));
}
AoeLootCommandScript::DebugMessage(nullptr, fmt::format("Set AOE loot enabled for GUID {}: {}", guid, mode));
}
void AoeLootCommandScript::SetPlayerAoeLootDebug(uint64 guid, bool mode)
{
if (playerAoeLootDebug.find(guid) == playerAoeLootDebug.end())
{
playerAoeLootDebug[guid] = mode;
AoeLootCommandScript::DebugMessage(nullptr, fmt::format("Set AOE loot debug for GUID {}: {}", guid, mode));
}
else
{
playerAoeLootDebug[guid] = mode;
AoeLootCommandScript::DebugMessage(nullptr, fmt::format("Updated AOE loot debug for GUID {}: {}", guid, mode));
}
AoeLootCommandScript::DebugMessage(nullptr, fmt::format("Set AOE loot debug for GUID {}: {}", guid, mode));
}
void AoeLootCommandScript::RemovePlayerLootEnabled(uint64 guid)
{
if (playerAoeLootEnabled.find(guid) != playerAoeLootEnabled.end())
{
playerAoeLootEnabled.erase(guid);
AoeLootCommandScript::DebugMessage(nullptr, fmt::format("Removed AOE loot enabled for GUID {}", guid));
}
else
{
AoeLootCommandScript::DebugMessage(nullptr, fmt::format("No AOE loot enabled found for GUID {}", guid));
}
}
void AoeLootCommandScript::RemovePlayerLootDebug(uint64 guid)
{
if (playerAoeLootDebug.find(guid) != playerAoeLootDebug.end())
{
playerAoeLootDebug.erase(guid);
AoeLootCommandScript::DebugMessage(nullptr, fmt::format("Removed AOE loot debug for GUID {}", guid));
}
else
{
AoeLootCommandScript::DebugMessage(nullptr, fmt::format("No AOE loot debug found for GUID {}", guid));
}
}
bool AoeLootCommandScript::hasPlayerAoeLootEnabled(uint64 guid)
{
return playerAoeLootEnabled.count(guid) > 0;
}
bool AoeLootCommandScript::hasPlayerAoeLootDebug(uint64 guid)
{
return playerAoeLootDebug.count(guid) > 0;
}
// Getters and setters end. >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //
// Command handlers implementation. >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //
bool AoeLootCommandScript::HandleAoeLootOnCommand(ChatHandler* handler, Optional<std::string> /*args*/)
{
Player* player = handler->GetSession()->GetPlayer();
if (!player)
return true;
uint64 playerGuid = player->GetGUID().GetRawValue();
if (AoeLootCommandScript::hasPlayerAoeLootEnabled(playerGuid) &&
AoeLootCommandScript::GetPlayerAoeLootEnabled(playerGuid))
{
handler->PSendSysMessage("AOE Loot is already enabled for your character.");
return true;
}
if (AoeLootCommandScript::hasPlayerAoeLootEnabled(playerGuid) &&
!AoeLootCommandScript::GetPlayerAoeLootEnabled(playerGuid))
{
AoeLootCommandScript::SetPlayerAoeLootEnabled(playerGuid, true);
handler->PSendSysMessage("AOE Loot 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*/)
{
Player* player = handler->GetSession()->GetPlayer();
if (!player)
return true;
uint64 playerGuid = player->GetGUID().GetRawValue();
if (AoeLootCommandScript::hasPlayerAoeLootEnabled(playerGuid) &&
AoeLootCommandScript::GetPlayerAoeLootEnabled(playerGuid))
{
AoeLootCommandScript::SetPlayerAoeLootEnabled(playerGuid, false);
handler->PSendSysMessage("AOE Loot disabled for your character. Type: '.aoeloot on' to turn AoE Looting on.");
DebugMessage(player, "AOE Loot disabled for your character.");
}
else
{
handler->PSendSysMessage("AOE Loot is already disabled for your character.");
}
return true;
}
bool AoeLootCommandScript::HandleAoeLootToggleCommand(ChatHandler* handler, Optional<std::string> /*args*/)
{
Player* player = handler->GetSession()->GetPlayer();
if (!player)
return true;
uint64 playerGuid = player->GetGUID().GetRawValue();
if (!AoeLootCommandScript::hasPlayerAoeLootEnabled(playerGuid))
{
AoeLootCommandScript::SetPlayerAoeLootEnabled(playerGuid, true);
handler->PSendSysMessage("AOE Loot enabled for your character. Type: '.aoeloot off' to turn AoE Looting off.");
DebugMessage(player, "AOE Loot enabled for your character.");
}
else if (!AoeLootCommandScript::GetPlayerAoeLootEnabled(playerGuid))
{
AoeLootCommandScript::SetPlayerAoeLootEnabled(playerGuid, true);
handler->PSendSysMessage("AOE Loot is now enabled for your character. Type: '.aoeloot off' to turn AoE Looting off.");
DebugMessage(player, "AOE Loot is now enabled for your character.");
}
else if (AoeLootCommandScript::GetPlayerAoeLootEnabled(playerGuid))
{
AoeLootCommandScript::SetPlayerAoeLootEnabled(playerGuid, false);
handler->PSendSysMessage("AOE Loot is now disabled for your character. Type: '.aoeloot on' to turn AoE Looting on.");
DebugMessage(player, "AOE Loot is now disabled for your character.");
}
else
{
AoeLootCommandScript::SetPlayerAoeLootEnabled(playerGuid, false);
handler->PSendSysMessage("AOE Loot disabled for your character. Type: '.aoeloot on' to turn AoE Looting on.");
DebugMessage(player, "AOE Loot disabled for your character.");
}
return true;
}
bool AoeLootCommandScript::HandleAoeLootDebugOnCommand(ChatHandler* handler, Optional<std::string> /*args*/)
{
Player* player = handler->GetSession()->GetPlayer();
if (!player)
return true;
if (AoeLootCommandScript::hasPlayerAoeLootDebug(player->GetGUID().GetRawValue()) == 0)
{
AoeLootCommandScript::SetPlayerAoeLootDebug(player->GetGUID().GetRawValue(), true);
handler->PSendSysMessage("AOE Loot debug mode enabled for your character.");
DebugMessage(player, "AOE Loot debug mode enabled for your character.");
}
else if (!AoeLootCommandScript::GetPlayerAoeLootDebug(player->GetGUID().GetRawValue()))
{
AoeLootCommandScript::SetPlayerAoeLootDebug(player->GetGUID().GetRawValue(), true);
handler->PSendSysMessage("AOE Loot debug mode is now enabled for your character.");
DebugMessage(player, "AOE Loot debug mode is now enabled for your character.");
}
else
{
handler->PSendSysMessage("AOE Loot debug mode is already enabled for your character.");
}
DebugMessage(player, "AOE Loot debug mode enabled for your character.");
return true;
}
bool AoeLootCommandScript::HandleAoeLootDebugOffCommand(ChatHandler* handler, Optional<std::string> /*args*/)
{
Player* player = handler->GetSession()->GetPlayer();
if (!player)
return true;
if (AoeLootCommandScript::hasPlayerAoeLootDebug(player->GetGUID().GetRawValue()) > 0 &&
AoeLootCommandScript::GetPlayerAoeLootDebug(player->GetGUID().GetRawValue()) == true)
{
AoeLootCommandScript::SetPlayerAoeLootDebug(player->GetGUID().GetRawValue(), false);
handler->PSendSysMessage("AOE Loot debug mode disabled for your character.");
DebugMessage(player, "AOE Loot debug mode disabled for your character.");
}
else if (AoeLootCommandScript::hasPlayerAoeLootDebug(player->GetGUID().GetRawValue()) > 0 &&
AoeLootCommandScript::GetPlayerAoeLootDebug(player->GetGUID().GetRawValue()) == false)
{
handler->PSendSysMessage("AOE Loot debug mode is already disabled for your character.");
DebugMessage(player, "AOE Loot debug mode is already disabled for your character.");
}
else
{
handler->PSendSysMessage("AOE Loot debug mode is not enabled for your character.");
DebugMessage(player, "AOE Loot debug mode is not enabled for your character.");
}
return true;
}
bool AoeLootCommandScript::HandleAoeLootDebugToggleCommand(ChatHandler* handler, Optional<std::string> /*args*/)
{
Player* player = handler->GetSession()->GetPlayer();
if (!player)
return true;
if (AoeLootCommandScript::hasPlayerAoeLootDebug(player->GetGUID().GetRawValue()) == 0)
{
AoeLootCommandScript::SetPlayerAoeLootDebug(player->GetGUID().GetRawValue(), true);
handler->PSendSysMessage("AOE Loot debug mode enabled for your character.");
}
else if (AoeLootCommandScript::GetPlayerAoeLootDebug(player->GetGUID().GetRawValue()) == false)
{
AoeLootCommandScript::SetPlayerAoeLootDebug(player->GetGUID().GetRawValue(), true);
handler->PSendSysMessage("AOE Loot debug mode is now enabled for your character.");
}
else if (AoeLootCommandScript::GetPlayerAoeLootDebug(player->GetGUID().GetRawValue()) == true)
{
AoeLootCommandScript::SetPlayerAoeLootDebug(player->GetGUID().GetRawValue(), false);
handler->PSendSysMessage("AOE Loot debug mode is now disabled for your character.");
}
else
{
AoeLootCommandScript::SetPlayerAoeLootDebug(player->GetGUID().GetRawValue(), false);
handler->PSendSysMessage("AOE Loot debug mode disabled for your character.");
}
return true;
}
// Command handlers implementation End. <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< //
// Helper functions. >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //
// >>>>> Debugger message function. <<<<< //
void AoeLootCommandScript::DebugMessage(Player* player, const std::string& message)
{
if (!player)
return;
uint64 guid = player->GetGUID().GetRawValue();
if (AoeLootCommandScript::GetPlayerAoeLootDebug(guid) && AoeLootCommandScript::hasPlayerAoeLootDebug(guid))
{
// >>>>> This will send debug messages to the player. <<<<< //
ChatHandler(player->GetSession()).PSendSysMessage("AOE Loot: {}", message);
}
}
std::vector<Player*> AoeLootCommandScript::GetGroupMembers(Player* player)
{
std::vector<Player*> members;
Group* group = player->GetGroup();
if (!group)
return members;
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
{
Player* member = itr->GetSource();
if (member && member->IsInWorld() && !member->isDead())
members.push_back(member);
}
return members;
}
bool AoeLootCommandScript::IsValidLootTarget(Player* player, Creature* creature)
{
if (!creature || !player)
return false;
if (creature->IsAlive())
return false;
if (creature->loot.empty() || creature->loot.isLooted())
return false;
if (!creature->HasDynamicFlag(UNIT_DYNFLAG_LOOTABLE))
return false;
uint64 playerGuid = player->GetGUID().GetRawValue();
if (!AoeLootCommandScript::hasPlayerAoeLootEnabled(playerGuid))
{
DebugMessage(player, "Player AOE loot setting not found.");
return false;
}
if (!AoeLootCommandScript::GetPlayerAoeLootEnabled(playerGuid))
{
DebugMessage(player, "Player AOE loot is disabled.");
return false;
}
DebugMessage(player, fmt::format("Valid loot target found: {}", creature->GetName()));
return true;
}
void AoeLootCommandScript::ProcessQuestItems(Player* player, ObjectGuid lguid, Loot* loot)
{
if (!player || !loot)
return;
// >>>>> Process different quest item types. <<<<< //
const QuestItemMap& questItems = loot->GetPlayerQuestItems();
for (uint8 i = 0; i < questItems.size(); ++i)
{
uint8 lootSlot = loot->items.size() + i;
ProcessLootSlot(player, lguid, lootSlot);
DebugMessage(player, fmt::format("Looted quest item in slot {}", lootSlot));
}
const QuestItemMap& ffaItems = loot->GetPlayerFFAItems();
for (uint8 i = 0; i < ffaItems.size(); ++i)
{
ProcessLootSlot(player, lguid, i);
DebugMessage(player, fmt::format("Looted FFA item in slot {}", i));
}
}
std::pair<Loot*, bool> AoeLootCommandScript::GetLootObject(Player* player, ObjectGuid lguid)
{
if (lguid.IsGameObject())
{
// >>>>> This protects against looting Game Objects and Structures <<<<< //
DebugMessage(player, "Skipping GameObject - not supported for AOE loot");
return {nullptr, false};
}
else if (lguid.IsItem())
{
::Item* pItem = player->GetItemByGuid(lguid);
if (!pItem)
{
DebugMessage(player, fmt::format("Failed to find item {}", lguid.ToString()));
return {nullptr, false};
}
return {&pItem->loot, true};
}
else if (lguid.IsCorpse())
{
Corpse* bones = ObjectAccessor::GetCorpse(*player, lguid);
if (!bones)
{
DebugMessage(player, fmt::format("Failed to find corpse {}", lguid.ToString()));
return {nullptr, false};
}
return {&bones->loot, true};
}
else // >>>>> Creature <<<<< //
{
Creature* creature = player->GetMap()->GetCreature(lguid);
if (!creature)
{
DebugMessage(player, fmt::format("Failed to find creature {}", lguid.ToString()));
return {nullptr, false};
}
return {&creature->loot, true};
}
}
bool AoeLootCommandScript::HandleStartAoeLootCommand(ChatHandler* handler, Optional<std::string> /*args*/)
{
if (!sConfigMgr->GetOption<bool>("AOELoot.Enable", true))
return true;
Player* player = handler->GetSession()->GetPlayer();
if (!player)
return true;
// >>>>> Do not remove the hardcoded value. It is here for crash & data protection. <<<<< //
float range = sConfigMgr->GetOption<float>("AOELoot.Range", 55.0f);
auto validCorpses = GetValidCorpses(player, range);
uint32 CorpseThreshold = sConfigMgr->GetOption<uint32>("AOELoot.CorpseThreshold", 2);
if (validCorpses.size() < CorpseThreshold)
{
DebugMessage(player, "Not enough corpses for AOE loot. Defaulting to normal looting.");
return true;
}
for (auto* creature : validCorpses)
{
ProcessCreatureLoot(player, creature);
}
return true;
}
std::vector<Creature*> AoeLootCommandScript::GetValidCorpses(Player* player, float range)
{
std::list<Creature*> nearbyCorpses;
player->GetDeadCreatureListInGrid(nearbyCorpses, range);
DebugMessage(player, fmt::format("Found {} nearby corpses within range {}", nearbyCorpses.size(), range));
std::vector<Creature*> validCorpses;
for (auto* creature : nearbyCorpses)
{
if (IsValidLootTarget(player, creature))
validCorpses.push_back(creature);
}
DebugMessage(player, fmt::format("Found {} valid corpses", validCorpses.size()));
return validCorpses;
}
void AoeLootCommandScript::ProcessCreatureLoot(Player* player, Creature* creature)
{
ObjectGuid lguid = creature->GetGUID();
Loot* loot = &creature->loot;
if (!loot)
return;
// >>>>> Save original loot GUID to restore later <<<<< //
ObjectGuid originalLootGuid = player->GetLootGUID();
// >>>>> This ensures group roll system works correctly for each item <<<<< //
player->SetLootGUID(lguid);
ProcessQuestItems(player, lguid, loot);
for (uint8 lootSlot = 0; lootSlot < loot->items.size(); ++lootSlot)
{
// >>>>> Reset loot GUID for each item to ensure proper group roll handling <<<<< //
player->SetLootGUID(lguid);
ProcessLootSlot(player, lguid, lootSlot);
}
if (loot->gold > 0)
{
ProcessLootMoney(player, creature);
}
if (loot->isLooted())
{
ProcessLootRelease(lguid, player, loot);
}
// >>>>> Restore original loot GUID after processing <<<<< //
player->SetLootGUID(originalLootGuid);
}
bool AoeLootCommandScript::ProcessLootSlot(Player* player, ObjectGuid lguid, uint8 lootSlot)
{
if (!player)
return false;
auto [loot, isValid] = GetLootObject(player, lguid);
// >>>>> Basic validation checks <<<<< //
if (!player || !lguid || lguid.IsEmpty())
{
DebugMessage(player, fmt::format("Failed to loot slot {} of {}: invalid loot object", lootSlot, lguid.ToString()));
return false;
}
// >>>>> Check if loot has items <<<<< //
if (loot->items.empty() || lootSlot >= loot->items.size())
{
DebugMessage(player, fmt::format("Failed to loot slot {} of {}: invalid slot or no items", lootSlot, lguid.ToString()));
return false;
}
// >>>>> Check if the specific loot item exists <<<<< //
Group* group = player->GetGroup();
LootItem& lootItem = loot->items[lootSlot];
InventoryResult msg = EQUIP_ERR_OK;
if (lootItem.is_blocked || lootItem.is_looted)
{
DebugMessage(player, fmt::format("Failed to loot slot {} of {}: item is blocked", lootSlot, lguid.ToString()));
return false;
}
bool isGroupLoot = false;
bool isRoundRobin = false;
LootMethod lootMethod = GROUP_LOOT;
if (group)
{
lootMethod = group->GetLootMethod();
isGroupLoot = (lootMethod == GROUP_LOOT || lootMethod == NEED_BEFORE_GREED);
isRoundRobin = (lootMethod == ROUND_ROBIN);
}
if (group && !loot->items[lootSlot].is_underthreshold && isGroupLoot &&
lootMethod != FREE_FOR_ALL && lootMethod != MASTER_LOOT &&
!loot->items[lootSlot].is_blocked && !loot->items[lootSlot].is_looted)
{
if (lguid.IsCreature())
{
Creature* creature = player->GetMap()->GetCreature(lguid);
if (creature)
{
group->NeedBeforeGreed(loot, creature);
DebugMessage(player, fmt::format("Started group roll for above-threshold item in slot {} of {}", lootSlot, lguid.ToString()));
return true;
}
}
}
else if
(
group &&
lootMethod == MASTER_LOOT &&
lootMethod != FREE_FOR_ALL
)
{
if (group->GetMasterLooterGuid() != player->GetGUID())
{
player->SendLootError(lguid, LOOT_ERROR_MASTER_OTHER);
return false;
}
}
else if (group && isRoundRobin && loot->roundRobinPlayer && loot->roundRobinPlayer != player->GetGUID())
{
return false;
}
LootItem* storedItem = player->StoreLootItem(lootSlot, loot, msg);
if (!storedItem)
{
DebugMessage(player, fmt::format("Failed to loot slot {} of {}: inventory error {}", lootSlot, lguid.ToString(), static_cast<uint32>(msg)));
return false;
}
DebugMessage(player, fmt::format("Looted item from slot {} of {}", lootSlot, lguid.ToString()));
return true;
}
bool AoeLootCommandScript::ProcessLootMoney(Player* player, Creature* creature)
{
if (!player || !creature || creature->loot.gold == 0)
return false;
uint32 goldAmount = creature->loot.gold;
Group* group = player->GetGroup();
if (group && sConfigMgr->GetOption<bool>("AOELoot.Group", true))
{
std::vector<Player*> eligibleMembers;
// >>>>> For AoE loot, we allow money sharing within a larger range. <<<<< //
float range = sConfigMgr->GetOption<float>("AOELoot.Range", 55.0f);
float moneyShareMultiplier = sConfigMgr->GetOption<float>("AOELoot.MoneyShareDistanceMultiplier", 2.0f);
float moneyRange = range * moneyShareMultiplier;
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
{
Player* member = itr->GetSource();
if (member && member->IsInWorld() && !member->isDead())
{
// >>>>> Check if the member is eligible for money sharing <<<<< //
if (member->IsWithinDistInMap(player, moneyRange) ||
member->IsAtLootRewardDistance(player))
{
eligibleMembers.push_back(member);
}
}
}
if (!eligibleMembers.empty())
{
uint32 goldPerPlayer = goldAmount / eligibleMembers.size();
for (Player* member : eligibleMembers)
{
member->ModifyMoney(goldPerPlayer);
member->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_MONEY, goldPerPlayer);
DebugMessage(member, fmt::format("Received {} copper from AOE loot", goldPerPlayer));
}
}
else
{
// >>>>> Fallback: give all money to the looter if no eligible members found <<<<< //
player->ModifyMoney(goldAmount);
player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_MONEY, goldAmount);
}
}
else
{
player->ModifyMoney(goldAmount);
player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_MONEY, goldAmount);
}
creature->loot.gold = 0;
return true;
}
void AoeLootCommandScript::ProcessLootRelease(ObjectGuid lguid, Player* player, Loot* loot)
{
if (!player || !loot)
return;
player->SetLootGUID(ObjectGuid::Empty);
player->SendLootRelease(lguid);
if (lguid.IsCreature())
{
Creature* creature = player->GetMap()->GetCreature(lguid);
if (creature && loot->isLooted())
{
creature->RemoveDynamicFlag(UNIT_DYNFLAG_LOOTABLE);
creature->AllLootRemovedFromCorpse();
}
}
DebugMessage(player, fmt::format("Released loot for {}", lguid.ToString()));
}
void AoeLootPlayer::OnPlayerLogin(Player* player)
{
if (sConfigMgr->GetOption<bool>("AOELoot.Enable", true) &&
sConfigMgr->GetOption<bool>("AOELoot.Message", true))
{
ChatHandler(player->GetSession()).PSendSysMessage("AOE looting has been enabled for your character. Commands: .aoeloot debug | .aoeloot off | .aoeloot on");
}
}
void AoeLootPlayer::OnPlayerLogout(Player* player)
{
uint64 guid = player->GetGUID().GetRawValue();
// >>>>> Clean up player data <<<<< //
if (AoeLootCommandScript::hasPlayerAoeLootEnabled(guid))
AoeLootCommandScript::RemovePlayerLootEnabled(guid);
if (AoeLootCommandScript::hasPlayerAoeLootDebug(guid))
AoeLootCommandScript::RemovePlayerLootDebug(guid);
}
// Helper functions end. >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //
// >>>>> Add the scripts for registration as a module. <<<<< //
void AddSC_AoeLoot()
{
new AoeLootPlayer();
new AoeLootManager();
new AoeLootCommandScript();
}

102
src/aoe_loot.h Normal file
View File

@ -0,0 +1,102 @@
#ifndef MODULE_AOELOOT_H
#define MODULE_AOELOOT_H
#include "ScriptMgr.h"
#include "Config.h"
#include "ServerScript.h"
#include "Chat.h"
#include "Player.h"
#include "Item.h"
#include "ScriptedGossip.h"
#include "ChatCommand.h"
#include "ChatCommandArgs.h"
#include "AccountMgr.h"
#include <vector>
#include <list>
#include <map>
#include <ObjectGuid.h>
using namespace Acore::ChatCommands;
// AoeLootManager Class >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //
class AoeLootManager : public ServerScript
{
public:
AoeLootManager() : ServerScript("AoeLootManager") {}
bool CanPacketReceive(WorldSession* session, WorldPacket& packet) override;
};
// AoeLootManager Class End. >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //
// AoeLootPlayer Class >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //
class AoeLootPlayer : public PlayerScript
{
public:
AoeLootPlayer() : PlayerScript("AoeLootPlayer") {}
void OnPlayerLogin(Player* player) override;
void OnPlayerLogout(Player* player) override;
};
// AoeLootPlayer Class End. >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //
// AoeLootCommandScript Class >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //
class AoeLootCommandScript : public CommandScript
{
public:
AoeLootCommandScript() : CommandScript("AoeLootCommandScript") {}
ChatCommandTable GetCommands() const override;
// Command handlers
static bool HandleAoeLootOnCommand(ChatHandler* handler, Optional<std::string> args);
static bool HandleAoeLootOffCommand(ChatHandler* handler, Optional<std::string> args);
static bool HandleStartAoeLootCommand(ChatHandler* handler, Optional<std::string> args);
static bool HandleAoeLootToggleCommand(ChatHandler* handler, Optional<std::string> args);
static bool HandleAoeLootDebugOnCommand(ChatHandler* handler, Optional<std::string> args);
static bool HandleAoeLootDebugOffCommand(ChatHandler* handler, Optional<std::string> args);
static bool HandleAoeLootDebugToggleCommand(ChatHandler* handler, Optional<std::string> args);
// Core loot processing functions
static bool ProcessLootSlot(Player* player, ObjectGuid lguid, uint8 lootSlot);
static bool ProcessLootMoney(Player* player, Creature* creature);
static void ProcessLootRelease(ObjectGuid lguid, Player* player, Loot* loot);
// Helper functions
static void DebugMessage(Player* player, const std::string& message);
static std::vector<Player*> GetGroupMembers(Player* player);
static void ProcessQuestItems(Player* player, ObjectGuid lguid, Loot* loot);
static std::pair<Loot*, bool> GetLootObject(Player* player, ObjectGuid lguid);
static std::vector<Creature*> GetValidCorpses(Player* player, float range);
static void ProcessCreatureLoot(Player* player, Creature* creature);
static bool IsValidLootTarget(Player* player, Creature* creature);
// Getters and setters for player AOE loot settings
static bool GetPlayerAoeLootEnabled(uint64 guid);
static bool GetPlayerAoeLootDebug(uint64 guid);
static void SetPlayerAoeLootEnabled(uint64 guid, bool mode);
static void SetPlayerAoeLootDebug(uint64 guid, bool mode);
static void RemovePlayerLootEnabled(uint64 guid);
static void RemovePlayerLootDebug(uint64 guid);
static bool hasPlayerAoeLootEnabled(uint64 guid);
static bool hasPlayerAoeLootDebug(uint64 guid);
private:
static std::map<uint64, bool> playerAoeLootEnabled;
static std::map<uint64, bool> playerAoeLootDebug;
};
// AoeLootCommandScript Class End. >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //
void AddSC_AoeLoot();
#endif //MODULE_AOELOOT_H

9
src/aoe_loot_loader.cpp Normal file
View File

@ -0,0 +1,9 @@
// From SC
void AddSC_AoeLoot();
// Add all
void Addmod_aoe_lootScripts()
{
AddSC_AoeLoot();
}