refactor(Core/RPG): Retry counter + give-up state replaces MoveRandomNear nudge on MoveFarTo failure

This commit is contained in:
bash 2026-06-01 00:08:01 +02:00
parent e92af1cc06
commit 5ffd2ad89c
3 changed files with 56 additions and 12 deletions

View File

@ -155,11 +155,17 @@ bool NewRpgGoGrindAction::Execute(Event /*event*/)
if (auto* data = std::get_if<NewRpgInfo::GoGrind>(&botAI->rpgInfo.data)) if (auto* data = std::get_if<NewRpgInfo::GoGrind>(&botAI->rpgInfo.data))
{ {
if (MoveFarTo(data->pos)) if (MoveFarTo(data->pos))
{
botAI->rpgInfo.moveRetryCount = 0;
return true; return true;
// Small nudge so the next tick's MoveFarTo starts from a }
// slightly different position. Kept small so it doesn't look // Reference pattern (TravelTarget retry counter): count
// like the bot is abandoning its destination. // consecutive MoveFarTo failures, give up after N tries by
return MoveRandomNear(10.0f); // 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)
botAI->rpgInfo.ChangeToIdle();
return true; // consume tick, no nudge
} }
return false; return false;
@ -173,8 +179,13 @@ bool NewRpgGoCampAction::Execute(Event /*event*/)
if (auto* data = std::get_if<NewRpgInfo::GoCamp>(&botAI->rpgInfo.data)) if (auto* data = std::get_if<NewRpgInfo::GoCamp>(&botAI->rpgInfo.data))
{ {
if (MoveFarTo(data->pos)) if (MoveFarTo(data->pos))
{
botAI->rpgInfo.moveRetryCount = 0;
return true;
}
if (++botAI->rpgInfo.moveRetryCount >= NewRpgInfo::MAX_MOVE_RETRIES)
botAI->rpgInfo.ChangeToIdle();
return true; return true;
return MoveRandomNear(10.0f);
} }
return false; return false;
@ -230,11 +241,20 @@ bool NewRpgWanderNpcAction::Execute(Event /*event*/)
else else
{ {
if (MoveWorldObjectTo(data.npcOrGo)) if (MoveWorldObjectTo(data.npcOrGo))
{
botAI->rpgInfo.moveRetryCount = 0;
return true;
}
// Retry counter (reference pattern): give up after N failures
// by clearing the picked NPC so next tick picks a different
// one. No nudge — stand still until retry.
if (++botAI->rpgInfo.moveRetryCount >= NewRpgInfo::MAX_MOVE_RETRIES)
{
data.npcOrGo = ObjectGuid();
data.lastReach = 0;
botAI->rpgInfo.moveRetryCount = 0;
}
return true; return true;
// NPC pathing failed (random offset in a wall, mmap hiccup, etc).
// Take a small random step so the next tick retries from a
// different spot instead of staring at the NPC from afar.
return MoveRandomNear(15.0f);
} }
return true; return true;
@ -350,9 +370,16 @@ bool NewRpgDoQuestAction::DoIncompleteQuest(NewRpgInfo::DoQuest& data)
// class strategy handles it. // class strategy handles it.
if (MoveFarTo(data.pos)) if (MoveFarTo(data.pos))
{
botAI->rpgInfo.moveRetryCount = 0;
return true;
}
// Retry counter (reference pattern): on N consecutive
// failures, drop this objective and go idle so the picker can
// try another quest / state.
if (++botAI->rpgInfo.moveRetryCount >= NewRpgInfo::MAX_MOVE_RETRIES)
botAI->rpgInfo.ChangeToIdle();
return true; return true;
// sampler found nothing — nudge so next tick tries a new pos
return MoveRandomNear(10.0f);
} }
// Now we are near the quest objective // Now we are near the quest objective
// kill mobs and looting quest should be done automatically by grind strategy // kill mobs and looting quest should be done automatically by grind strategy
@ -551,8 +578,16 @@ bool NewRpgDoQuestAction::DoCompletedQuest(NewRpgInfo::DoQuest& data)
if (bot->GetDistance(data.pos) > 10.0f && !data.lastReachPOI) if (bot->GetDistance(data.pos) > 10.0f && !data.lastReachPOI)
{ {
if (MoveFarTo(data.pos)) if (MoveFarTo(data.pos))
{
botAI->rpgInfo.moveRetryCount = 0;
return true;
}
// Retry counter (reference pattern): mark quest as abandoned
// 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)
botAI->rpgInfo.ChangeToIdle();
return true; return true;
return MoveRandomNear(10.0f);
} }
// Now we are near the qoi of reward // Now we are near the qoi of reward

View File

@ -77,6 +77,7 @@ void NewRpgInfo::Reset()
{ {
data = Idle{}; data = Idle{};
startT = getMSTime(); startT = getMSTime();
moveRetryCount = 0;
} }
NewRpgStatus NewRpgInfo::GetStatus() NewRpgStatus NewRpgInfo::GetStatus()

View File

@ -78,6 +78,14 @@ struct NewRpgInfo
uint32 startT{0}; // start timestamp of the current status uint32 startT{0}; // start timestamp of the current status
// Counts consecutive MoveFarTo failures for the current state.
// Reset on every status change (via Reset) and on every successful
// MoveFarTo. When it crosses MAX_MOVE_RETRIES the failing action
// gives up and transitions out of the current state instead of
// sitting on a stuck objective forever.
uint8 moveRetryCount{0};
static constexpr uint8 MAX_MOVE_RETRIES = 10;
using RpgData = std::variant< using RpgData = std::variant<
Idle, Idle,
GoGrind, GoGrind,