diff --git a/src/Ai/World/Rpg/Action/NewRpgAction.cpp b/src/Ai/World/Rpg/Action/NewRpgAction.cpp index 168bd6d62..c43011e36 100644 --- a/src/Ai/World/Rpg/Action/NewRpgAction.cpp +++ b/src/Ai/World/Rpg/Action/NewRpgAction.cpp @@ -331,8 +331,11 @@ bool NewRpgDoQuestAction::DoIncompleteQuest(NewRpgInfo::DoQuest& data) if (bot->GetDistance(data.pos) > 10.0f && !data.lastReachPOI) { - // yield to attack-anything if a quest mob is right next to us - if (HasNearbyQuestMob(15.0f)) + // Yield to attack-anything ONLY if a mob needed by this exact + // quest+objective is right next to us. The broad variant (any + // quest in the log) yielded for every nearby mob and derailed + // turn-ins / cross-zone travel through other quests' clusters. + if (HasNearbyQuestMobForObjective(15.0f, data.questId, data.objectiveIdx)) return false; // Note: previously yielded ~10%/tick when any hostile was diff --git a/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp b/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp index 926dc9c67..eed4ad0d9 100644 --- a/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp +++ b/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp @@ -1558,6 +1558,79 @@ bool NewRpgBaseAction::HasNearbyQuestMob(float range) return false; } +bool NewRpgBaseAction::HasNearbyQuestMobForObjective(float range, uint32 questId, int32 objectiveIdx) +{ + if (!questId) + return false; + + Quest const* quest = sObjectMgr->GetQuestTemplate(questId); + if (!quest) + return false; + + // Turn-in path: completed quest has no remaining mob objective. + if (bot->GetQuestStatus(questId) == QUEST_STATUS_COMPLETE) + return false; + + QuestStatusData const& qs = bot->getQuestStatusMap().at(questId); + + uint32 neededCreatureEntry = 0; + uint32 neededItemId = 0; + + if (objectiveIdx >= 0 && objectiveIdx < QUEST_OBJECTIVES_COUNT) + { + int32 entry = quest->RequiredNpcOrGo[objectiveIdx]; + if (entry > 0 && + qs.CreatureOrGOCount[objectiveIdx] < quest->RequiredNpcOrGoCount[objectiveIdx]) + { + neededCreatureEntry = uint32(entry); + } + // Item objective sometimes lives in the same slot range. + if (objectiveIdx < QUEST_ITEM_OBJECTIVES_COUNT && + quest->RequiredItemId[objectiveIdx] && + qs.ItemCount[objectiveIdx] < quest->RequiredItemCount[objectiveIdx]) + { + neededItemId = quest->RequiredItemId[objectiveIdx]; + } + } + else if (objectiveIdx >= QUEST_OBJECTIVES_COUNT && + objectiveIdx < QUEST_OBJECTIVES_COUNT + QUEST_ITEM_OBJECTIVES_COUNT) + { + int32 itemSlot = objectiveIdx - QUEST_OBJECTIVES_COUNT; + if (quest->RequiredItemId[itemSlot] && + qs.ItemCount[itemSlot] < quest->RequiredItemCount[itemSlot]) + { + neededItemId = quest->RequiredItemId[itemSlot]; + } + } + + if (!neededCreatureEntry && !neededItemId) + return false; + + GuidVector possibleTargets = AI_VALUE(GuidVector, "possible targets"); + for (ObjectGuid guid : possibleTargets) + { + Creature* c = botAI->GetCreature(guid); + if (!c || !c->IsInWorld() || !c->IsAlive()) + continue; + if (!(c->GetPhaseMask() & bot->GetPhaseMask())) + continue; + if (bot->GetDistance(c) > range) + continue; + + if (neededCreatureEntry && c->GetEntry() == neededCreatureEntry) + return true; + + if (neededItemId) + { + CreatureTemplate const* tmpl = c->GetCreatureTemplate(); + if (tmpl && tmpl->lootid && + LootTemplates_Creature.HaveQuestLootForPlayer(tmpl->lootid, bot)) + return true; + } + } + return false; +} + ObjectGuid NewRpgBaseAction::ChooseNpcOrGameObjectToInteract(bool questgiverOnly, float distanceLimit) { diff --git a/src/Ai/World/Rpg/Action/NewRpgBaseAction.h b/src/Ai/World/Rpg/Action/NewRpgBaseAction.h index 52eebae8a..d69b50fcc 100644 --- a/src/Ai/World/Rpg/Action/NewRpgBaseAction.h +++ b/src/Ai/World/Rpg/Action/NewRpgBaseAction.h @@ -68,6 +68,12 @@ protected: // travel so we yield to attack-anything instead of running past. bool HasNearbyQuestMob(float range = 20.0f); + // Narrower variant: only yields for mobs needed by the SPECIFIC + // quest+objective the bot is currently working on. Without this, + // do-quest yields for any quest in the log, derailing turn-ins + // and cross-zone travel through other quests' mob clusters. + bool HasNearbyQuestMobForObjective(float range, uint32 questId, int32 objectiveIdx); + protected: bool GetQuestPOIPosAndObjectiveIdx(uint32 questId, std::vector& poiInfo, bool toComplete = false); static WorldPosition SelectRandomGrindPos(Player* bot);