commit c1d0a3cc648d0d72d20064fcece9391112fa49aa Author: Flerp Date: Sat Nov 8 14:06:05 2025 -0800 Initial Upload diff --git a/README.md b/README.md new file mode 100644 index 0000000..fb694df --- /dev/null +++ b/README.md @@ -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) diff --git a/conf/mod_aoe_loot.conf.dist b/conf/mod_aoe_loot.conf.dist new file mode 100644 index 0000000..488a9e3 --- /dev/null +++ b/conf/mod_aoe_loot.conf.dist @@ -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 diff --git a/src/aoe_loot.cpp b/src/aoe_loot.cpp new file mode 100644 index 0000000..570b260 --- /dev/null +++ b/src/aoe_loot.cpp @@ -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 +#include "Corpse.h" +#include "Group.h" +#include "ObjectMgr.h" + +using namespace Acore::ChatCommands; +using namespace WorldPackets; + +std::map AoeLootCommandScript::playerAoeLootEnabled; +std::map 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("AOELoot.Enable", true)); + } + if (!AoeLootCommandScript::hasPlayerAoeLootDebug(guid)) + { + AoeLootCommandScript::SetPlayerAoeLootDebug(guid, sConfigMgr->GetOption("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 /*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 /*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 /*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 /*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 /*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 /*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 AoeLootCommandScript::GetGroupMembers(Player* player) +{ + std::vector 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 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 /*args*/) +{ + if (!sConfigMgr->GetOption("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("AOELoot.Range", 55.0f); + + auto validCorpses = GetValidCorpses(player, range); + + uint32 CorpseThreshold = sConfigMgr->GetOption("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 AoeLootCommandScript::GetValidCorpses(Player* player, float range) +{ + std::list nearbyCorpses; + player->GetDeadCreatureListInGrid(nearbyCorpses, range); + + DebugMessage(player, fmt::format("Found {} nearby corpses within range {}", nearbyCorpses.size(), range)); + + std::vector 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(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("AOELoot.Group", true)) + { + std::vector eligibleMembers; + + // >>>>> For AoE loot, we allow money sharing within a larger range. <<<<< // + + float range = sConfigMgr->GetOption("AOELoot.Range", 55.0f); + float moneyShareMultiplier = sConfigMgr->GetOption("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("AOELoot.Enable", true) && + sConfigMgr->GetOption("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(); +} diff --git a/src/aoe_loot.h b/src/aoe_loot.h new file mode 100644 index 0000000..2e1ee30 --- /dev/null +++ b/src/aoe_loot.h @@ -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 +#include +#include +#include + +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 args); + static bool HandleAoeLootOffCommand(ChatHandler* handler, Optional args); + static bool HandleStartAoeLootCommand(ChatHandler* handler, Optional args); + static bool HandleAoeLootToggleCommand(ChatHandler* handler, Optional args); + static bool HandleAoeLootDebugOnCommand(ChatHandler* handler, Optional args); + static bool HandleAoeLootDebugOffCommand(ChatHandler* handler, Optional args); + static bool HandleAoeLootDebugToggleCommand(ChatHandler* handler, Optional 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 GetGroupMembers(Player* player); + static void ProcessQuestItems(Player* player, ObjectGuid lguid, Loot* loot); + static std::pair GetLootObject(Player* player, ObjectGuid lguid); + static std::vector 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 playerAoeLootEnabled; + static std::map playerAoeLootDebug; + +}; + +// AoeLootCommandScript Class End. >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> // + + +void AddSC_AoeLoot(); + +#endif //MODULE_AOELOOT_H diff --git a/src/aoe_loot_loader.cpp b/src/aoe_loot_loader.cpp new file mode 100644 index 0000000..ebf4411 --- /dev/null +++ b/src/aoe_loot_loader.cpp @@ -0,0 +1,9 @@ + +// From SC +void AddSC_AoeLoot(); + +// Add all +void Addmod_aoe_lootScripts() +{ + AddSC_AoeLoot(); +}