From d9bfea502db6c8b166545eb40d6f6c6b6aad2b59 Mon Sep 17 00:00:00 2001 From: bash Date: Fri, 1 May 2026 14:49:03 +0200 Subject: [PATCH] feat(Core/Loot): Loot wild quest game objects --- src/Ai/Base/Actions/LootAction.cpp | 6 +++-- src/Mgr/Item/LootObjectStack.cpp | 38 +++++++++++++++++++++++------- src/Mgr/Item/LootObjectStack.h | 5 +++- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/Ai/Base/Actions/LootAction.cpp b/src/Ai/Base/Actions/LootAction.cpp index 93368f524..9cd515fdc 100644 --- a/src/Ai/Base/Actions/LootAction.cpp +++ b/src/Ai/Base/Actions/LootAction.cpp @@ -5,6 +5,7 @@ #include "LootAction.h" +#include "Bag.h" #include "ChatHelper.h" #include "Event.h" #include "GuildMgr.h" @@ -139,8 +140,9 @@ bool OpenLootAction::DoLoot(LootObject& lootObject) if (go && (go->GetGoState() != GO_STATE_READY)) return false; - // This prevents dungeon chests like Tribunal Chest (Halls of Stone) from being ninja'd by the bots - if (go && go->HasFlag(GAMEOBJECT_FLAGS, GO_FLAG_INTERACT_COND)) + // Block event-gated chests (Tribunal Chest, Gunship Armory) but allow + // wild quest GOs (Moonpetal Lily etc.) when the bot is on the quest. + if (go && go->HasFlag(GAMEOBJECT_FLAGS, GO_FLAG_INTERACT_COND) && !lootObject.isNeededQuestItem) return false; // This prevents raid chests like Gunship Armory (ICC) from being ninja'd by the bots diff --git a/src/Mgr/Item/LootObjectStack.cpp b/src/Mgr/Item/LootObjectStack.cpp index a62eb8fe4..946c482b2 100644 --- a/src/Mgr/Item/LootObjectStack.cpp +++ b/src/Mgr/Item/LootObjectStack.cpp @@ -45,7 +45,8 @@ void LootTargetList::shrink(time_t fromTime) } } -LootObject::LootObject(Player* bot, ObjectGuid guid) : guid(), skillId(SKILL_NONE), reqSkillValue(0), reqItem(0) +LootObject::LootObject(Player* bot, ObjectGuid guid) + : guid(), skillId(SKILL_NONE), reqSkillValue(0), reqItem(0), isNeededQuestItem(false) { Refresh(bot, guid); } @@ -55,6 +56,7 @@ void LootObject::Refresh(Player* bot, ObjectGuid lootGUID) skillId = SKILL_NONE; reqSkillValue = 0; reqItem = 0; + isNeededQuestItem = false; guid.Clear(); PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); @@ -101,6 +103,7 @@ void LootObject::Refresh(Player* bot, ObjectGuid lootGUID) if (IsNeededForQuest(bot, itemId)) { this->guid = lootGUID; + this->isNeededQuestItem = true; return; } @@ -135,10 +138,21 @@ void LootObject::Refresh(Player* bot, ObjectGuid lootGUID) if (!proto) continue; + // Moonpetal Lily, Hyacinth Mushroom etc. expose quest + // drops here (not in gameobject_questitem). Flag it so + // the INTERACT_COND gate lets the bot through. + if (IsNeededForQuest(bot, itemId)) + { + this->guid = lootGUID; + this->isNeededQuestItem = true; + return; + } + if (proto->Class != ITEM_CLASS_QUEST) { onlyHasQuestItems = false; - break; + // keep scanning — a later item may be needed + continue; } // If this item references another loot table, process it @@ -157,11 +171,15 @@ void LootObject::Refresh(Player* bot, ObjectGuid lootGUID) if (!refProto) continue; - if (refProto->Class != ITEM_CLASS_QUEST) + if (IsNeededForQuest(bot, refItemId)) { - onlyHasQuestItems = false; - break; + this->guid = lootGUID; + this->isNeededQuestItem = true; + return; } + + if (refProto->Class != ITEM_CLASS_QUEST) + onlyHasQuestItems = false; } } } @@ -270,6 +288,7 @@ LootObject::LootObject(LootObject const& other) skillId = other.skillId; reqSkillValue = other.reqSkillValue; reqItem = other.reqItem; + isNeededQuestItem = other.isNeededQuestItem; } bool LootObject::IsLootPossible(Player* bot) @@ -299,10 +318,13 @@ bool LootObject::IsLootPossible(Player* bot) return false; } - // Prevent bot from running to chests that are unlootable (e.g. Gunship Armory before completing the event) or on - // respawn time + // Block event-gated chests (Gunship Armory pre-event) and unspawned + // GOs. INTERACT_COND alone is allowed when the GO holds a quest + // item we need — ConditionMgr already gates on quest state. GameObject* go = botAI->GetGameObject(guid); - if (go && (go->HasFlag(GAMEOBJECT_FLAGS, GO_FLAG_INTERACT_COND | GO_FLAG_NOT_SELECTABLE) || !go->isSpawned())) + if (go && (go->HasFlag(GAMEOBJECT_FLAGS, GO_FLAG_NOT_SELECTABLE) || !go->isSpawned())) + return false; + if (go && go->HasFlag(GAMEOBJECT_FLAGS, GO_FLAG_INTERACT_COND) && !isNeededQuestItem) return false; if (skillId == SKILL_NONE) diff --git a/src/Mgr/Item/LootObjectStack.h b/src/Mgr/Item/LootObjectStack.h index 0f87aed70..df1b5b570 100644 --- a/src/Mgr/Item/LootObjectStack.h +++ b/src/Mgr/Item/LootObjectStack.h @@ -26,7 +26,7 @@ public: class LootObject { public: - LootObject() : skillId(0), reqSkillValue(0), reqItem(0) {} + LootObject() : skillId(0), reqSkillValue(0), reqItem(0), isNeededQuestItem(false) {} LootObject(Player* bot, ObjectGuid guid); LootObject(LootObject const& other); LootObject& operator=(LootObject const& other) = default; @@ -40,6 +40,9 @@ public: uint32 skillId; uint32 reqSkillValue; uint32 reqItem; + // GO holds a quest item we still need; lets us bypass the + // INTERACT_COND blanket reject in the loot path + bool isNeededQuestItem; private: static bool IsNeededForQuest(Player* bot, uint32 itemId);