feat(Core/Loot): Loot wild quest game objects

This commit is contained in:
bash 2026-05-01 14:49:03 +02:00
parent 7318396256
commit d35e0da5d9
3 changed files with 38 additions and 11 deletions

View File

@ -5,6 +5,7 @@
#include "LootAction.h" #include "LootAction.h"
#include "Bag.h"
#include "ChatHelper.h" #include "ChatHelper.h"
#include "Event.h" #include "Event.h"
#include "GuildMgr.h" #include "GuildMgr.h"
@ -139,8 +140,9 @@ bool OpenLootAction::DoLoot(LootObject& lootObject)
if (go && (go->GetGoState() != GO_STATE_READY)) if (go && (go->GetGoState() != GO_STATE_READY))
return false; return false;
// This prevents dungeon chests like Tribunal Chest (Halls of Stone) from being ninja'd by the bots // Block event-gated chests (Tribunal Chest, Gunship Armory) but allow
if (go && go->HasFlag(GAMEOBJECT_FLAGS, GO_FLAG_INTERACT_COND)) // 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; return false;
// This prevents raid chests like Gunship Armory (ICC) from being ninja'd by the bots // This prevents raid chests like Gunship Armory (ICC) from being ninja'd by the bots

View File

@ -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); Refresh(bot, guid);
} }
@ -55,6 +56,7 @@ void LootObject::Refresh(Player* bot, ObjectGuid lootGUID)
skillId = SKILL_NONE; skillId = SKILL_NONE;
reqSkillValue = 0; reqSkillValue = 0;
reqItem = 0; reqItem = 0;
isNeededQuestItem = false;
guid.Clear(); guid.Clear();
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
@ -101,6 +103,7 @@ void LootObject::Refresh(Player* bot, ObjectGuid lootGUID)
if (IsNeededForQuest(bot, itemId)) if (IsNeededForQuest(bot, itemId))
{ {
this->guid = lootGUID; this->guid = lootGUID;
this->isNeededQuestItem = true;
return; return;
} }
@ -135,10 +138,21 @@ void LootObject::Refresh(Player* bot, ObjectGuid lootGUID)
if (!proto) if (!proto)
continue; 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) if (proto->Class != ITEM_CLASS_QUEST)
{ {
onlyHasQuestItems = false; onlyHasQuestItems = false;
break; // keep scanning — a later item may be needed
continue;
} }
// If this item references another loot table, process it // If this item references another loot table, process it
@ -157,11 +171,15 @@ void LootObject::Refresh(Player* bot, ObjectGuid lootGUID)
if (!refProto) if (!refProto)
continue; continue;
if (refProto->Class != ITEM_CLASS_QUEST) if (IsNeededForQuest(bot, refItemId))
{ {
onlyHasQuestItems = false; this->guid = lootGUID;
break; this->isNeededQuestItem = true;
return;
} }
if (refProto->Class != ITEM_CLASS_QUEST)
onlyHasQuestItems = false;
} }
} }
} }
@ -270,6 +288,7 @@ LootObject::LootObject(LootObject const& other)
skillId = other.skillId; skillId = other.skillId;
reqSkillValue = other.reqSkillValue; reqSkillValue = other.reqSkillValue;
reqItem = other.reqItem; reqItem = other.reqItem;
isNeededQuestItem = other.isNeededQuestItem;
} }
bool LootObject::IsLootPossible(Player* bot) bool LootObject::IsLootPossible(Player* bot)
@ -299,10 +318,13 @@ bool LootObject::IsLootPossible(Player* bot)
return false; return false;
} }
// Prevent bot from running to chests that are unlootable (e.g. Gunship Armory before completing the event) or on // Block event-gated chests (Gunship Armory pre-event) and unspawned
// respawn time // 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); 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; return false;
if (skillId == SKILL_NONE) if (skillId == SKILL_NONE)

View File

@ -26,7 +26,7 @@ public:
class LootObject class LootObject
{ {
public: public:
LootObject() : skillId(0), reqSkillValue(0), reqItem(0) {} LootObject() : skillId(0), reqSkillValue(0), reqItem(0), isNeededQuestItem(false) {}
LootObject(Player* bot, ObjectGuid guid); LootObject(Player* bot, ObjectGuid guid);
LootObject(LootObject const& other); LootObject(LootObject const& other);
LootObject& operator=(LootObject const& other) = default; LootObject& operator=(LootObject const& other) = default;
@ -40,6 +40,9 @@ public:
uint32 skillId; uint32 skillId;
uint32 reqSkillValue; uint32 reqSkillValue;
uint32 reqItem; 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: private:
static bool IsNeededForQuest(Player* bot, uint32 itemId); static bool IsNeededForQuest(Player* bot, uint32 itemId);