Add files via upload

This commit is contained in:
TerraByte
2025-05-23 17:56:42 -05:00
committed by GitHub
parent 014bb8f2a7
commit 238b062a3d
4 changed files with 662 additions and 0 deletions

546
src/aoe_loot.cpp Normal file
View File

@@ -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 <fmt/format.h>
#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<Player*> 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<float>("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<std::string> /*args*/)
{
if (!sConfigMgr->GetOption<bool>("AOELoot.Enable", true))
return true;
Player* player = handler->GetSession()->GetPlayer();
if (!player)
return true;
float range = sConfigMgr->GetOption<float>("AOELoot.Range", 55.0);
bool debugMode = sConfigMgr->GetOption<bool>("AOELoot.Debug", false);
std::list<Creature*> nearbyCorpses;
player->GetDeadCreatureListInGrid(nearbyCorpses, range);
if (debugMode)
{
LOG_DEBUG("module.aoe_loot", "AOE Loot: Found {} nearby corpses within range {}.", nearbyCorpses.size(), range);
}
// Filter valid corpses
std::list<Creature*> 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<bool>("AOELoot.Enable", true) &&
sConfigMgr->GetOption<bool>("AOELoot.Message", true))
{
ChatHandler(player->GetSession()).PSendSysMessage(AOE_ACORE_STRING_MESSAGE);
}
}
// Add script registrations
void AddSC_AoeLoot()
{
new AoeLootPlayer();
new AoeLootServer();
new AoeLootCommandScript();
}