diff --git a/src/Ai/Base/Actions/MovementActions.cpp b/src/Ai/Base/Actions/MovementActions.cpp index 4edfad778..e5396f78c 100644 --- a/src/Ai/Base/Actions/MovementActions.cpp +++ b/src/Ai/Base/Actions/MovementActions.cpp @@ -62,7 +62,9 @@ void MovementAction::EmitDebugMove(char const* method, char const* generator, fl NewRpgInfo& info = botAI->rpgInfo; NewRpgStatus status = info.GetStatus(); + bool const inCombat = botAI->GetState() == BOT_STATE_COMBAT; char const* statusName = + inCombat ? "combat" : status == RPG_IDLE ? "idle" : status == RPG_GO_GRIND ? "go-grind" : status == RPG_GO_CAMP ? "go-camp" : @@ -73,14 +75,22 @@ void MovementAction::EmitDebugMove(char const* method, char const* generator, fl status == RPG_TRAVEL_FLIGHT ? "travel-flight" : status == RPG_OUTDOOR_PVP ? "outdoor-pvp" : "?"; - // Resolve a human-readable target name from the RPG context. When - // we can name the target (quest objective, wander NPC, flight - // master, travel-node hop, etc.), it replaces the loc=(x,y,z) - // field — names are far more useful than coordinates. When no - // target can be named (combat moves, follow, flee, ad-hoc), we - // fall through to loc=(x,y,z). + // Resolve a human-readable target name. In combat, the bot is + // actively engaging an enemy that is unrelated to the RPG state's + // target — show that enemy instead of the now-stale RPG goal. + // Out of combat, fall back to the RPG context: quest objective, + // wander NPC, flight master, etc. Names are far more useful than + // coordinates; loc=(x,y,z) only when nothing nameable applies. std::string targetName; - switch (status) + if (inCombat) + { + Unit* current = *botAI->GetAiObjectContext()->GetValue("current target"); + Unit* enemyPlayer = *botAI->GetAiObjectContext()->GetValue("enemy player target"); + Unit* enemy = current ? current : enemyPlayer; + if (enemy) + targetName = std::string("vs:") + enemy->GetName(); + } + else switch (status) { case RPG_DO_QUEST: if (auto* data = std::get_if(&info.data)) @@ -164,6 +174,11 @@ void MovementAction::EmitDebugMove(char const* method, char const* generator, fl << " | " << statusName << " | " << std::fixed << std::setprecision(2) << dis << " yard" << " | " << (targetName.empty() ? "-" : targetName.c_str()); + // Surface the RPG MoveFarTo retry counter so when bots get stuck + // it's obvious from the whisper alone (retry=N/MAX) — and the + // "give-up" event emitters below show retry=MAX/MAX explicitly. + if (info.moveRetryCount > 0) + out << " | retry=" << uint32(info.moveRetryCount) << "/" << uint32(NewRpgInfo::MAX_MOVE_RETRIES); if (extra && *extra) out << " | " << extra; botAI->TellMasterNoFacing(out); diff --git a/src/Ai/World/Rpg/Action/NewRpgAction.cpp b/src/Ai/World/Rpg/Action/NewRpgAction.cpp index 59406274c..6c347686b 100644 --- a/src/Ai/World/Rpg/Action/NewRpgAction.cpp +++ b/src/Ai/World/Rpg/Action/NewRpgAction.cpp @@ -164,7 +164,12 @@ bool NewRpgGoGrindAction::Execute(Event /*event*/) // transitioning out of the stuck state instead of nudging in // place. Idle lets the status picker rotate to a new state. if (++botAI->rpgInfo.moveRetryCount >= NewRpgInfo::MAX_MOVE_RETRIES) + { + EmitDebugMove("MoveFar", "give-up", + data->pos.GetPositionX(), data->pos.GetPositionY(), data->pos.GetPositionZ(), + "idle"); botAI->rpgInfo.ChangeToIdle(); + } return true; // consume tick, no nudge } @@ -184,7 +189,12 @@ bool NewRpgGoCampAction::Execute(Event /*event*/) return true; } if (++botAI->rpgInfo.moveRetryCount >= NewRpgInfo::MAX_MOVE_RETRIES) + { + EmitDebugMove("MoveFar", "give-up", + data->pos.GetPositionX(), data->pos.GetPositionY(), data->pos.GetPositionZ(), + "idle"); botAI->rpgInfo.ChangeToIdle(); + } return true; } @@ -250,6 +260,9 @@ bool NewRpgWanderNpcAction::Execute(Event /*event*/) // one. No nudge — stand still until retry. if (++botAI->rpgInfo.moveRetryCount >= NewRpgInfo::MAX_MOVE_RETRIES) { + EmitDebugMove("MoveFar", "give-up", + bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), + "drop-npc"); data.npcOrGo = ObjectGuid(); data.lastReach = 0; botAI->rpgInfo.moveRetryCount = 0; @@ -386,6 +399,12 @@ bool NewRpgDoQuestAction::DoIncompleteQuest(NewRpgInfo::DoQuest& data) // catches it next tick. if (++botAI->rpgInfo.moveRetryCount >= NewRpgInfo::MAX_MOVE_RETRIES) { + std::ostringstream nx; + nx << "next-spawn(" << (data.currentSpawnIdx + 1) << "/" + << data.candidateSpawns.size() << ")"; + EmitDebugMove("MoveFar", "give-up", + target.GetPositionX(), target.GetPositionY(), target.GetPositionZ(), + nx.str().c_str()); ++data.currentSpawnIdx; data.lastReachPOI = 0; botAI->rpgInfo.moveRetryCount = 0; @@ -514,7 +533,12 @@ bool NewRpgDoQuestAction::DoCompletedQuest(NewRpgInfo::DoQuest& data) // if turn-in POI is unreachable repeatedly so the bot doesn't // sit on a broken handler. if (++botAI->rpgInfo.moveRetryCount >= NewRpgInfo::MAX_MOVE_RETRIES) + { + EmitDebugMove("MoveFar", "give-up", + data.pos.GetPositionX(), data.pos.GetPositionY(), data.pos.GetPositionZ(), + "idle(turn-in)"); botAI->rpgInfo.ChangeToIdle(); + } return true; }