diff --git a/src/aoe_loot.cpp b/src/aoe_loot.cpp index 9a8c8a6..dfeccc8 100644 --- a/src/aoe_loot.cpp +++ b/src/aoe_loot.cpp @@ -31,6 +31,104 @@ bool IsAoeLootModuleEnabled() return sConfigMgr->GetOption("AoeLoot.EnableMod", true); } +bool IsPlayerEligibleForQuestItem(Player* player, LootItem const& item) +{ + ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(item.itemid); + LOG_INFO("module.aoe_loot", "[AOE LOOT] Eligibility check: player={} item={} class={} startQuest={} maxCount={} questStatus={} hasItem={} flags={} isQuestStarter={}", + player->GetName(), + item.itemid, + itemTemplate ? itemTemplate->Class : -1, + itemTemplate ? itemTemplate->StartQuest : 0, + itemTemplate ? itemTemplate->MaxCount : 0, + itemTemplate ? player->GetQuestStatus(itemTemplate->StartQuest) : -1, + itemTemplate ? player->HasItemCount(item.itemid, itemTemplate->MaxCount) : false, + itemTemplate ? itemTemplate->Flags : 0, + itemTemplate ? itemTemplate->StartQuest != 0 : false); + + if (!itemTemplate || itemTemplate->Class != ITEM_CLASS_QUEST) + { + LOG_INFO("module.aoe_loot", "[AOE LOOT] Not a quest item or missing template: item={}", item.itemid); + return false; + } + + // For quest starter items + if (itemTemplate->StartQuest) + { + uint32 prevQuestId = 0; + if (Quest const* startQuest = sObjectMgr->GetQuestTemplate(itemTemplate->StartQuest)) + prevQuestId = startQuest->GetPrevQuestId(); + + LOG_INFO("module.aoe_loot", "[AOE LOOT] Quest starter item: player={} item={} questStatus={} hasItem={} prevQuestId={} prevQuestReward={}", + player->GetName(), + item.itemid, + player->GetQuestStatus(itemTemplate->StartQuest), + player->HasItemCount(item.itemid, itemTemplate->MaxCount), + prevQuestId, + prevQuestId ? player->GetQuestRewardStatus(prevQuestId) : 1); + + // Already completed the quest + if (player->GetQuestStatus(itemTemplate->StartQuest) != QUEST_STATUS_NONE) + return false; + + // Already has the item and it's unique + if (itemTemplate->MaxCount && player->HasItemCount(item.itemid, itemTemplate->MaxCount)) + return false; + + // Has not completed prerequisite quest + if (prevQuestId && !player->GetQuestRewardStatus(prevQuestId)) + return false; + + return true; + } + + // For normal quest items (objectives) + std::vector questIds; + for (auto const& questPair : sObjectMgr->GetQuestTemplates()) + { + Quest const* quest = questPair.second; + if (!quest) + continue; + + for (uint32 i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; ++i) + { + if (quest->RequiredItemId[i] == item.itemid) + { + questIds.push_back(quest->GetQuestId()); + break; + } + } + } + + LOG_INFO("module.aoe_loot", "Objective quest item: player={} item={} foundQuests={}", player->GetName(), item.itemid, questIds.size()); + + for (uint32 questId : questIds) + { + LOG_INFO("module.aoe_loot", "Checking quest status: player={} item={} questId={} status={}", player->GetName(), item.itemid, questId, player->GetQuestStatus(questId)); + if (player->GetQuestStatus(questId) == QUEST_STATUS_INCOMPLETE) + { + uint32 requiredCount = 1; + Quest const* quest = sObjectMgr->GetQuestTemplate(questId); + if (quest) + { + for (uint32 i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; ++i) + { + if (quest->RequiredItemId[i] == item.itemid) + { + requiredCount = quest->RequiredItemCount[i]; + break; + } + } + } + LOG_INFO("module.aoe_loot", "Item count check: player={} item={} have={} required={}", player->GetName(), item.itemid, player->GetItemCount(item.itemid, true), requiredCount); + if (player->GetItemCount(item.itemid, true) < requiredCount) + return true; + } + } + + LOG_INFO("module.aoe_loot", "Player not eligible for quest item: player={} item={}", player->GetName(), item.itemid); + return false; +} + // Helper function to get configured loot method from config value LootMethod GetLootMethodFromConfig(uint32 configValue) { @@ -495,9 +593,59 @@ bool AoeLootCommandScript::ProcessSingleLootSlot(Player* player, ObjectGuid lgui loot = &creature->loot; } - sScriptMgr->OnPlayerAfterCreatureLoot(player); - if (!loot) + // Handle quest item slots + if (lootSlot >= loot->items.size()) + { + // Calculate quest and FFA quest item slot + uint8 questItemOffset = loot->items.size(); + const QuestItemMap& questItems = loot->GetPlayerQuestItems(); + auto q_itr = questItems.find(player->GetGUID()); + if (q_itr != questItems.end()) + { + const QuestItemList* qlist = q_itr->second; + uint8 questCount = qlist->size(); + if (lootSlot < questItemOffset + questCount) + { + uint8 questIndex = lootSlot - questItemOffset; + const QuestItem& qitem = (*qlist)[questIndex]; + if (!qitem.is_looted) + { + if (qitem.index < loot->quest_items.size()) { + LootItem& li = loot->quest_items[qitem.index]; + player->AddItem(li.itemid, li.count); + const_cast(qitem).is_looted = true; + LOG_INFO("module.aoe_loot", "[AOE LOOT] Player looted quest item: player={} item={} count={} slot={}", player->GetName(), li.itemid, uint32(li.count), uint32(questIndex)); + return true; + } + } + } + } + // FFA quest items + const QuestItemMap& ffaItems = loot->GetPlayerFFAItems(); + auto ffa_itr = ffaItems.find(player->GetGUID()); + if (ffa_itr != ffaItems.end()) + { + const QuestItemList* flist = ffa_itr->second; + uint8 ffaOffset = questItemOffset + (q_itr != questItems.end() ? q_itr->second->size() : 0); + uint8 ffaCount = flist->size(); + if (lootSlot < ffaOffset + ffaCount) + { + uint8 ffaIndex = lootSlot - ffaOffset; + const QuestItem& fitem = (*flist)[ffaIndex]; + if (!fitem.is_looted) + { + if (fitem.index < loot->quest_items.size()) { + LootItem& li = loot->quest_items[fitem.index]; + player->AddItem(li.itemid, li.count); + const_cast(fitem).is_looted = true; + LOG_INFO("module.aoe_loot", "[AOE LOOT] Player looted FFA quest item: player={} item={} count={} slot={}", player->GetName(), li.itemid, uint32(li.count), uint32(ffaIndex)); + return true; + } + } + } + } return false; + } // --- Begin standard loot logic for solo/group --- Group* group = player->GetGroup(); @@ -511,12 +659,6 @@ bool AoeLootCommandScript::ProcessSingleLootSlot(Player* player, ObjectGuid lgui LootMethod lootMethod = GROUP_LOOT; uint8 groupLootThreshold = 2; // Use group's configured threshold, fallback to Uncommon - // Bounds check for loot slot - if (lootSlot >= loot->items.size()) - { - return false; - } - isFFA = loot->items[lootSlot].freeforall; if (group) @@ -526,27 +668,18 @@ bool AoeLootCommandScript::ProcessSingleLootSlot(Player* player, ObjectGuid lgui isMasterLooter = (lootMethod == MASTER_LOOT); isRoundRobin = (lootMethod == ROUND_ROBIN); isGroupLoot = (lootMethod == GROUP_LOOT || lootMethod == NEED_BEFORE_GREED); - - // Bounds check before accessing loot item - if (lootSlot < loot->items.size()) - { - ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(loot->items[lootSlot].itemid); - isThreshold = (itemTemplate && itemTemplate->Quality >= groupLootThreshold); - } + ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(loot->items[lootSlot].itemid); + isThreshold = (itemTemplate && itemTemplate->Quality >= groupLootThreshold); } // If in group and item meets group loot threshold, trigger group roll if (group && isGroupLoot && isThreshold && !isFFA && !isMasterLooter) { - // Use the existing game's SendLootStartRoll - it doesn't have distance checks - if (lootSlot < loot->items.size() && !loot->items[lootSlot].is_blocked) + if (!loot->items[lootSlot].is_blocked) { - // Create Roll object using the constructor: Roll(ObjectGuid _guid, LootItem const &li) Roll roll(lguid, loot->items[lootSlot]); roll.itemSlot = lootSlot; roll.setLoot(loot); - - // Initialize player votes for group members for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) { Player* member = itr->GetSource(); @@ -556,15 +689,12 @@ bool AoeLootCommandScript::ProcessSingleLootSlot(Player* player, ObjectGuid lgui roll.totalPlayersRolling++; } } - - // Call SendLootStartRoll using the group's configured roll timeout group->SendLootStartRoll(60, player->GetMapId(), roll); } return true; } else if (group && isMasterLooter && !isFFA) { - // Master looter: only master looter can loot if (group->GetMasterLooterGuid() != player->GetGUID()) { player->SendLootError(lguid, LOOT_ERROR_MASTER_OTHER); @@ -573,12 +703,31 @@ bool AoeLootCommandScript::ProcessSingleLootSlot(Player* player, ObjectGuid lgui } else if (group && isRoundRobin && loot->roundRobinPlayer && loot->roundRobinPlayer != player->GetGUID()) { - // Round robin: only designated player can loot return false; } - // If not blocked by group loot, proceed to loot the item + sScriptMgr->OnPlayerAfterCreatureLoot(player); + if (!loot) + return false; + + bool isLootedBefore = loot->items[lootSlot].is_looted; + LOG_INFO("module.aoe_loot", "Before StoreLootItem: player={} item={} slot={} itemCount={} is_looted={}", + player->GetName(), + loot->items[lootSlot].itemid, + lootSlot, + player->GetItemCount(loot->items[lootSlot].itemid, true), + isLootedBefore); + lootItem = player->StoreLootItem(lootSlot, loot, msg); + + bool isLootedAfter = loot->items[lootSlot].is_looted; + LOG_INFO("module.aoe_loot", "After StoreLootItem: player={} item={} slot={} itemCount={} is_looted={} result={}", + player->GetName(), + loot->items[lootSlot].itemid, + lootSlot, + player->GetItemCount(loot->items[lootSlot].itemid, true), + isLootedAfter, + msg); if (msg != EQUIP_ERR_OK && lguid.IsItem() && loot->loot_type != LOOT_CORPSE) { lootItem->is_looted = true; @@ -587,7 +736,6 @@ bool AoeLootCommandScript::ProcessSingleLootSlot(Player* player, ObjectGuid lgui player->SendItemRetrievalMail(lootItem->itemid, lootItem->count); } - // If player is removing the last LootItem, delete the empty container if (loot->isLooted() && lguid.IsItem()) { ReleaseAndCleanupLoot(lguid, player, loot); @@ -597,10 +745,10 @@ bool AoeLootCommandScript::ProcessSingleLootSlot(Player* player, ObjectGuid lgui bool AoeLootCommandScript::TriggerAoeLootCommand(ChatHandler* handler, Optional /*args*/) { - if (!IsAoeLootModuleEnabled()) - return true; - Player* player = handler->GetSession()->GetPlayer(); + LOG_INFO("module.aoe_loot", "[AOE LOOT] TriggerAoeLootCommand: player={} starting AoE loot", player ? player->GetName() : "null"); + if (!IsAoeLootModuleEnabled()) + return true; if (!player) return true; @@ -677,13 +825,70 @@ bool AoeLootCommandScript::TriggerAoeLootCommand(ChatHandler* handler, Optional< } player->SetLootGUID(lguid); + LOG_INFO("module.aoe_loot", "[AOE LOOT] SetLootGUID: player={} corpse={} loot window should open", player->GetName(), creature->GetName()); - // Process all loot items + // Process all normal loot items for (uint8 lootSlot = 0; lootSlot < loot->items.size(); ++lootSlot) { ProcessSingleLootSlot(player, lguid, lootSlot); } + // Explicitly process per-player quest items + const QuestItemMap& questItems = loot->GetPlayerQuestItems(); + auto q_itr = questItems.find(player->GetGUID()); + if (q_itr != questItems.end()) + { + const QuestItemList* qlist = q_itr->second; + for (uint8 i = 0; i < qlist->size(); ++i) + { + // Quest item slots follow after normal loot slots + uint8 questSlot = loot->items.size() + i; + ProcessSingleLootSlot(player, lguid, questSlot); + LOG_INFO("module.aoe_loot", "[AOE LOOT] Processed quest item in slot {}", questSlot); + } + } + + // Explicitly process per-player FFA quest items + const QuestItemMap& ffaItems = loot->GetPlayerFFAItems(); + auto ffa_itr = ffaItems.find(player->GetGUID()); + if (ffa_itr != ffaItems.end()) + { + const QuestItemList* flist = ffa_itr->second; + for (uint8 i = 0; i < flist->size(); ++i) + { + // FFA item slots follow after normal loot slots and quest item slots + uint8 ffaSlot = loot->items.size() + (q_itr != questItems.end() ? q_itr->second->size() : 0) + i; + ProcessSingleLootSlot(player, lguid, ffaSlot); + LOG_INFO("module.aoe_loot", "[AOE LOOT] Processed FFA quest item in slot {}", ffaSlot); + } + } + + // After processing all slots, log how many lootable items remain + size_t lootableCount = 0; + for (const auto& item : loot->items) + { + if (!item.is_looted) + ++lootableCount; + } + // Also count quest items and FFA quest items + if (q_itr != questItems.end()) + { + for (const auto& item : *q_itr->second) + { + if (!item.is_looted) + ++lootableCount; + } + } + if (ffa_itr != ffaItems.end()) + { + for (const auto& item : *ffa_itr->second) + { + if (!item.is_looted) + ++lootableCount; + } + } + LOG_INFO("module.aoe_loot", "[AOE LOOT] After processing: player={} corpse={} lootable_items={}", player->GetName(), creature->GetName(), lootableCount); + // Handle money if (loot->gold > 0) { @@ -694,6 +899,7 @@ bool AoeLootCommandScript::TriggerAoeLootCommand(ChatHandler* handler, Optional< if (loot->isLooted()) { ReleaseAndCleanupLoot(lguid, player, loot); + LOG_INFO("module.aoe_loot", "[AOE LOOT] ReleaseAndCleanupLoot: player={} corpse={} loot released", player->GetName(), creature->GetName()); } } return true;