From 238b062a3d510c5d2439de94f7343ff187c2497f Mon Sep 17 00:00:00 2001 From: TerraByte Date: Fri, 23 May 2025 17:56:42 -0500 Subject: [PATCH] Add files via upload --- conf/mod_aoe_loot.conf.dist | 48 ++++ src/aoe_loot.cpp | 546 ++++++++++++++++++++++++++++++++++++ src/aoe_loot.h | 59 ++++ src/aoe_loot_loader.cpp | 9 + 4 files changed, 662 insertions(+) create mode 100644 conf/mod_aoe_loot.conf.dist create mode 100644 src/aoe_loot.cpp create mode 100644 src/aoe_loot.h create mode 100644 src/aoe_loot_loader.cpp diff --git a/conf/mod_aoe_loot.conf.dist b/conf/mod_aoe_loot.conf.dist new file mode 100644 index 0000000..1e6f72d --- /dev/null +++ b/conf/mod_aoe_loot.conf.dist @@ -0,0 +1,48 @@ +######################################## +# AoeLoot module configuration +######################################## +# +# AOELoot.Enable +# Description: Enables Module +# Default: 0 - (Disabled) +# 1 - (Enabled) +# + +AOELoot.Enable = 1 + +# +# AOELoot.Message +# Description: Enable message on login +# Default: 0 - (Disabled) +# 1 - (Enabled) +# + +AOELoot.Message = 0 + +# +# AOELoot.Range +# Description: Maximum reach range search loot. +# Default: 55.0 +# + +AOELoot.Range = 55.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.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: 0 (Disabled) +# Possible values: 0 - (Disabled) +# 1 - (Enabled) +# + +AOELoot.Debug = 0 diff --git a/src/aoe_loot.cpp b/src/aoe_loot.cpp new file mode 100644 index 0000000..f8d4987 --- /dev/null +++ b/src/aoe_loot.cpp @@ -0,0 +1,546 @@ +#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" + +using namespace Acore::ChatCommands; +using namespace WorldPackets; + +bool AoeLootServer::CanPacketReceive(WorldSession* session, WorldPacket& packet) +{ + if (packet.GetOpcode() == CMSG_LOOT) + { + Player* player = session->GetPlayer(); + if (player) + { + // Trigger AOE loot when a player attempts to loot a corpse + ChatHandler handler(player->GetSession()); + handler.ParseCommands(".startaoeloot"); + } + } + return true; +} + +ChatCommandTable AoeLootCommandScript::GetCommands() const +{ + static ChatCommandTable playerAoeLootCommandTable = + { + { "startaoeloot", HandleStartAoeLootCommand, SEC_PLAYER, Console::No } + }; + return playerAoeLootCommandTable; +} + +void AoeLootCommandScript::ProcessLootRelease(ObjectGuid lguid, Player* player, Loot* loot) +{ + player->SetLootGUID(ObjectGuid::Empty); + player->SendLootRelease(lguid); + player->RemoveUnitFlag(UNIT_FLAG_LOOTING); + + if (!player->IsInWorld()) + return; + + if (lguid.IsGameObject()) + { + GameObject* go = player->GetMap()->GetGameObject(lguid); + + // not check distance for GO in case owned GO (fishing bobber case, for example) or Fishing hole GO + if (!go || ((go->GetOwnerGUID() != player->GetGUID() && go->GetGoType() != GAMEOBJECT_TYPE_FISHINGHOLE) && !go->IsWithinDistInMap(player))) + { + player->SendLootRelease(lguid); + return; + } + + loot = &go->loot; + + } + else if (lguid.IsCorpse()) // ONLY remove insignia at BG + { + Corpse* corpse = ObjectAccessor::GetCorpse(*player, lguid); + + if (!corpse) + return; + + loot = &corpse->loot; + } + else if (lguid.IsItem()) + { + Item* pItem = player->GetItemByGuid(lguid); + if (!pItem) + return; + + } + else // Must be a creature + { + Creature* creature = player->GetMap()->GetCreature(lguid); + + // Skip distance check for dead creatures (corpses) + // Keep distance check for pickpocketing (live creatures) + bool isPickpocketing = creature && creature->IsAlive() && player->IsClass(CLASS_ROGUE, CLASS_CONTEXT_ABILITY) && creature->loot.loot_type == LOOT_PICKPOCKETING; + + // Only check distance for pickpocketing, not for corpse looting + if (isPickpocketing && !creature->IsWithinDistInMap(player, INTERACTION_DISTANCE)) + { + player->SendLootError(lguid, LOOT_ERROR_TOO_FAR); + return; + } + + loot = &creature->loot; + + + if (loot->isLooted()) + { + // skip pickpocketing loot for speed, skinning timer reduction is no-op in fact + if (!creature->IsAlive()) + creature->AllLootRemovedFromCorpse(); + + creature->RemoveDynamicFlag(UNIT_DYNFLAG_LOOTABLE); + loot->clear(); + } + else + { + // if the round robin player release, reset it. + if (player->GetGUID() == loot->roundRobinPlayer) + { + loot->roundRobinPlayer.Clear(); + + if (Group* group = player->GetGroup()) + group->SendLooter(creature, nullptr); + } + // force dynflag update to update looter and lootable info + creature->ForceValuesUpdateAtIndex(UNIT_DYNAMIC_FLAGS); + } + } + + // Player is not looking at loot list, he doesn't need to see updates on the loot list + if (!lguid.IsItem()) + { + loot->RemoveLooter(player->GetGUID()); + } +} + +// Handle gold looting without distance checks +bool AoeLootCommandScript::ProcessLootMoney(Player* player, Creature* creature) +{ + if (!player || !creature) + return false; + + Loot* loot = &creature->loot; + if (!loot || loot->gold == 0) + return false; + + uint32 goldAmount = loot->gold; + bool shareMoney = true; // Share by default for creature corpses + + if (shareMoney && player->GetGroup()) + { + Group* group = player->GetGroup(); + std::vector playersNear; + + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (member) + playersNear.push_back(member); + } + + uint32 goldPerPlayer = uint32((loot->gold) / (playersNear.size())); + + for (Player* groupMember : playersNear) + { + groupMember->ModifyMoney(goldPerPlayer); + groupMember->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_MONEY, goldPerPlayer); + + WorldPacket data(SMSG_LOOT_MONEY_NOTIFY, 4 + 1); + data << uint32(goldPerPlayer); + data << uint8(playersNear.size() > 1 ? 0 : 1); // 0 is "Your share is..." and 1 is "You loot..." + groupMember->GetSession()->SendPacket(&data); + } + } + else + { + // No group - give all gold to the player + player->ModifyMoney(loot->gold); + player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_MONEY, loot->gold); + + WorldPacket data(SMSG_LOOT_MONEY_NOTIFY, 4 + 1); + data << uint32(loot->gold); + data << uint8(1); // "You loot..." + player->GetSession()->SendPacket(&data); + } + + // Mark the money as looted + loot->gold = 0; + loot->NotifyMoneyRemoved(); + + return true; +} + +bool AoeLootCommandScript::ProcessLootSlot(Player* player, ObjectGuid lguid, uint8 lootSlot) +{ + if (!player) + return false; + + Loot* loot = nullptr; + float aoeDistance = sConfigMgr->GetOption("AOELoot.Range", 55.0f); + + // Get the loot object based on the GUID type + if (lguid.IsGameObject()) + { + GameObject* go = player->GetMap()->GetGameObject(lguid); + + if (!go || ((go->GetOwnerGUID() != player->GetGUID() && go->GetGoType() != GAMEOBJECT_TYPE_FISHINGHOLE) && !go->IsWithinDistInMap(player, aoeDistance))) + { + player->SendLootRelease(lguid); + return false; + } + + loot = &go->loot; + } + else if (lguid.IsItem()) + { + Item* pItem = player->GetItemByGuid(lguid); + if (!pItem) + { + + player->SendLootRelease(lguid); + return false; + } + + loot = &pItem->loot; + } + else if (lguid.IsCorpse()) + { + Corpse* bones = ObjectAccessor::GetCorpse(*player, lguid); + if (!bones) + { + player->SendLootRelease(lguid); + return false; + } + loot = &bones->loot; + } + else + { + Creature* creature = player->GetMap()->GetCreature(lguid); + + // Skip distance check for dead creatures (corpses) + // Keep distance check for pickpocketing (live creatures) + bool isPickpocketing = creature && creature->IsAlive() && player->IsClass(CLASS_ROGUE, CLASS_CONTEXT_ABILITY) && creature->loot.loot_type == LOOT_PICKPOCKETING; + + // Only check distance for pickpocketing, not for corpse looting + if (isPickpocketing && !creature->IsWithinDistInMap(player, INTERACTION_DISTANCE)) + { + player->SendLootError(lguid, LOOT_ERROR_TOO_FAR); + return false; + } + + loot = &creature->loot; + + } + + sScriptMgr->OnPlayerAfterCreatureLoot(player); + if (!loot) + return false; + + InventoryResult msg; + LootItem* lootItem = player->StoreLootItem(lootSlot, loot, msg); + if (msg != EQUIP_ERR_OK && lguid.IsItem() && loot->loot_type != LOOT_CORPSE) + { + lootItem->is_looted = true; + loot->NotifyItemRemoved(lootItem->itemIndex); + loot->unlootedCount--; + + player->SendItemRetrievalMail(lootItem->itemid, lootItem->count); + } + + // If player is removing the last LootItem, delete the empty container + if (loot->isLooted() && lguid.IsItem()) + { + ProcessLootRelease(lguid, player, loot); + } + return 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; + + float range = sConfigMgr->GetOption("AOELoot.Range", 55.0); + bool debugMode = sConfigMgr->GetOption("AOELoot.Debug", false); + + std::list nearbyCorpses; + player->GetDeadCreatureListInGrid(nearbyCorpses, range); + + if (debugMode) + { + LOG_DEBUG("module.aoe_loot", "AOE Loot: Found {} nearby corpses within range {}.", nearbyCorpses.size(), range); + } + + // Filter valid corpses + std::list validCorpses; + for (auto* creature : nearbyCorpses) + { + if (!player || !creature) + continue; + + // Check if creature is valid for looting by this player + if (!player->isAllowedToLoot(creature)) + { + if (debugMode) + LOG_DEBUG("module.aoe_loot", "AOE Loot: Skipping creature {} - not your loot", creature->GetGUID().ToString()); + continue; + } + + if (!creature->hasLootRecipient() || !creature->isTappedBy(player)) + continue; + + // Get player's group and check loot permissions based on group loot method + Group* group = player->GetGroup(); + if (group) + { + Loot* loot = &creature->loot; + LootMethod lootMethod = group->GetLootMethod(); + // For Round Robin loot, check if this player is the designated looter + if (lootMethod == ROUND_ROBIN) + { + if (loot->roundRobinPlayer && loot->roundRobinPlayer != player->GetGUID()) + { + if (debugMode) + LOG_DEBUG("module.aoe_loot", "AOE Loot: Skipping creature {} - not your turn (Round Robin)", creature->GetGUID().ToString()); + + continue; + } + } + // For Master Loot, check if this player is the master looter + else if (lootMethod == MASTER_LOOT) + { + if (group->GetMasterLooterGuid() != player->GetGUID()) + { + if (debugMode) + LOG_DEBUG("module.aoe_loot", "AOE Loot: Skipping creature {} - not master looter", creature->GetGUID().ToString()); + + continue; + } + } + } + + // Skip if corpse is not lootable + if (!creature->HasDynamicFlag(UNIT_DYNFLAG_LOOTABLE)) + { + if (debugMode) + LOG_DEBUG("module.aoe_loot", "AOE Loot: Skipping creature {} - not lootable", creature->GetGUID().ToString()); + continue; + } + + validCorpses.push_back(creature); + } + + if (debugMode) + { + LOG_DEBUG("module.aoe_loot", "AOE Loot: Found {} valid nearby corpses within range {}.", validCorpses.size(), range); + } + + // Process all valid corpses + for (auto* creature : validCorpses) + { + ObjectGuid lguid = creature->GetGUID(); + Loot* loot = &creature->loot; + if (validCorpses.size() <= 1) + { + break; + } + if (!loot) + continue; + + player->SetLootGUID(lguid); + /* + // Add processing for PlayerQuestItems (player-specific quest items) + QuestItemMap const& playerQuestItems = loot->GetPlayerQuestItems(); + if (!playerQuestItems.empty()) + { + QuestItemMap::const_iterator pqItr = playerQuestItems.find(player->GetGUID()); + if (pqItr != playerQuestItems.end()) + { + QuestItemList* pql = pqItr->second; + if (pql) + { + for (QuestItemList::const_iterator iter = pql->begin(); iter != pql->end(); ++iter) + { + ProcessLootSlot(player, lguid, iter->index); + if (debugMode) + LOG_DEBUG("module.aoe_loot", "AOE Loot: looted player quest item in slot {}", iter->index); + } + } + } + } + + // Add processing for PlayerFFAItems (Free-For-All quest items) + QuestItemMap const& playerFFAItems = loot->GetPlayerFFAItems(); + if (!playerFFAItems.empty()) + { + QuestItemMap::const_iterator pqItr = playerFFAItems.find(player->GetGUID()); + if (pqItr != playerFFAItems.end()) + { + QuestItemList* pql = pqItr->second; + if (pql) + { + for (QuestItemList::const_iterator iter = pql->begin(); iter != pql->end(); ++iter) + { + ProcessLootSlot(player, lguid, iter->index); + if (debugMode) + LOG_DEBUG("module.aoe_loot", "AOE Loot: looted player FFA item in slot {}", iter->index); + } + } + } + } + + // Add processing for PlayerNonQuestNonFFAConditionalItems + QuestItemMap const& playerNonQuestNonFFAConditionalItems = loot->GetPlayerNonQuestNonFFAConditionalItems(); + if (!playerNonQuestNonFFAConditionalItems.empty()) + { + QuestItemMap::const_iterator pqItr = playerNonQuestNonFFAConditionalItems.find(player->GetGUID()); + if (pqItr != playerNonQuestNonFFAConditionalItems.end()) + { + QuestItemList* pql = pqItr->second; + if (pql) + { + for (QuestItemList::const_iterator iter = pql->begin(); iter != pql->end(); ++iter) + { + ProcessLootSlot(player, lguid, iter->index); + if (debugMode) + LOG_DEBUG("module.aoe_loot", "AOE Loot: looted conditional item in slot {}", iter->index); + } + } + } + } + */ + // Process quest items + QuestItemMap const& playerNonQuestNonFFAConditionalItems = loot->GetPlayerNonQuestNonFFAConditionalItems(); + if (!playerNonQuestNonFFAConditionalItems.empty()) + { + for (uint8 i = 0; i < playerNonQuestNonFFAConditionalItems.size(); ++i) + { + uint8 lootSlot = playerNonQuestNonFFAConditionalItems.size() + i; + ProcessLootSlot(player, lguid, lootSlot); + if (debugMode) + LOG_DEBUG("module.aoe_loot", "AOE Loot: looted quest item in slot {}", lootSlot); + } + } + + // Process quest items + QuestItemMap const& playerFFAItems = loot->GetPlayerFFAItems(); + if (!playerFFAItems.empty()) + { + for (uint8 i = 0; i < playerFFAItems.size(); ++i) + { + uint8 lootSlot = playerFFAItems.size() + i; + ProcessLootSlot(player, lguid, lootSlot); + if (debugMode) + LOG_DEBUG("module.aoe_loot", "AOE Loot: looted quest item in slot {}", lootSlot); + } + } + + // Process quest items + QuestItemMap const& playerQuestItems = loot->GetPlayerQuestItems(); + if (!playerQuestItems.empty()) + { + for (uint8 i = 0; i < playerQuestItems.size(); ++i) + { + uint8 lootSlot = playerQuestItems.size() + i; + ProcessLootSlot(player, lguid, lootSlot); + if (debugMode) + LOG_DEBUG("module.aoe_loot", "AOE Loot: looted quest item in slot {}", lootSlot); + } + } + + // Process quest items + if (!loot->quest_items.empty()) + { + for (uint8 i = 0; i < loot->quest_items.size(); ++i) + { + uint8 lootSlot = loot->items.size() + i; + ProcessLootSlot(player, lguid, lootSlot); + if (debugMode) + LOG_DEBUG("module.aoe_loot", "AOE Loot: looted quest item in slot {}", lootSlot); + } + } + + // Process quest items + if (!loot->quest_items.empty()) + { + for (uint8 i = 0; i < loot->quest_items.size(); ++i) + { + uint8 lootSlot = loot->items.size() + i; + ProcessLootSlot(player, lguid, lootSlot); + if (debugMode) + LOG_DEBUG("module.aoe_loot", "AOE Loot: looted quest item in slot {}", lootSlot); + } + } + + // Process regular items + for (uint8 lootSlot = 0; lootSlot < loot->items.size(); ++lootSlot) + { + player->SetLootGUID(lguid); + ProcessLootSlot(player, lguid, lootSlot); + 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()); + } + } + + // Handle money + if (loot->gold > 0) + { + uint32 goldAmount = loot->gold; + ProcessLootMoney(player, creature); + if (debugMode) + { + LOG_DEBUG("module.aoe_loot", "AOE Loot: Looted {} copper from {}", goldAmount, lguid.ToString()); + } + } + + if (loot->isLooted()) + { + ProcessLootRelease(lguid, player, loot); + } + } + return true; +} + +// Display login message to player +void AoeLootPlayer::OnPlayerLogin(Player* player) +{ + if (sConfigMgr->GetOption("AOELoot.Enable", true) && + sConfigMgr->GetOption("AOELoot.Message", true)) + { + ChatHandler(player->GetSession()).PSendSysMessage(AOE_ACORE_STRING_MESSAGE); + } +} + +// Add script registrations +void AddSC_AoeLoot() +{ + new AoeLootPlayer(); + new AoeLootServer(); + new AoeLootCommandScript(); +} diff --git a/src/aoe_loot.h b/src/aoe_loot.h new file mode 100644 index 0000000..c2a081e --- /dev/null +++ b/src/aoe_loot.h @@ -0,0 +1,59 @@ +#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; + +enum AoeLootString +{ + AOE_ACORE_STRING_MESSAGE = 50000 +}; + +class AoeLootServer : public ServerScript +{ +public: + AoeLootServer() : ServerScript("AoeLootServer") {} + + bool CanPacketReceive(WorldSession* session, WorldPacket& packet) override; +}; + +class AoeLootPlayer : public PlayerScript +{ +public: + AoeLootPlayer() : PlayerScript("AoeLootPlayer") {} + + void OnPlayerLogin(Player* player) override; + +}; + +class AoeLootCommandScript : public CommandScript +{ +public: + AoeLootCommandScript() : CommandScript("AoeLootCommandScript") {} + ChatCommandTable GetCommands() const override; + + static bool HandleStartAoeLootCommand(ChatHandler* handler, Optional args); + 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); + +}; + +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(); +}