chore: Tighten inline comments

This commit is contained in:
bash 2026-05-02 18:20:40 +02:00
parent 3f9640334e
commit 1a593a6a3c
7 changed files with 84 additions and 102 deletions

View File

@ -3233,15 +3233,14 @@ bool MovementAction::LaunchWalkSpline(TravelPlan& state)
} }
// Re-clamp cached waypoints to current valid Z. Rows in // Re-clamp cached waypoints to current valid Z. Rows in
// playerbots_travelnode_path store absolute coords baked at offline // playerbots_travelnode_path store absolute coords baked at
// generation; if the live navmesh has shifted since (mmap regen, // offline generation; if the live navmesh has shifted since
// terrain change, vmap update), the stored z can be above ground — // (mmap regen, terrain change, vmap update), the stored z can
// MoveSplinePath plays back coords verbatim and the bot looks like // be above ground — MoveSplinePath plays back coords verbatim
// it's walking through the air. UpdateAllowedPositionZ is the same // and the bot looks like it's walking through the air.
// per-waypoint snap cmangos-playerbots applies in DispatchMovement // UpdateAllowedPositionZ factors mmap polygon Z, water surface,
// (MovementActions.cpp:1006); it factors mmap polygon Z, water // swimming, flying and transport state, so cave floors above
// surface, swimming, flying and transport state, so cave floors // the terrain plane snap correctly.
// above the terrain plane snap correctly.
for (auto& pt : state.walkPoints) for (auto& pt : state.walkPoints)
bot->UpdateAllowedPositionZ(pt.x, pt.y, pt.z); bot->UpdateAllowedPositionZ(pt.x, pt.y, pt.z);
@ -3308,8 +3307,7 @@ bool MovementAction::LaunchWalkSpline(TravelPlan& state)
bot->GetOrientation(), delay, MovementPriority::MOVEMENT_NORMAL); bot->GetOrientation(), delay, MovementPriority::MOVEMENT_NORMAL);
// Cache the dispatched waypoint chain so MoveFarTo's 10% // Cache the dispatched waypoint chain so MoveFarTo's 10%
// lastPath reuse (cmangos MovementActions.cpp:687) and the // lastPath reuse and "no worse" reuse can pick it up next tick.
// "no worse" reuse (line 716) can pick it up next tick.
std::vector<WorldPosition> wpts; std::vector<WorldPosition> wpts;
wpts.reserve(state.walkPoints.size()); wpts.reserve(state.walkPoints.size());
for (auto const& pt : state.walkPoints) for (auto const& pt : state.walkPoints)
@ -3477,9 +3475,8 @@ bool MovementAction::ExecuteTravelPlan(TravelPlan& state)
} }
// Too far from first point — abort the plan and let the // Too far from first point — abort the plan and let the
// caller's stuck-recovery decide what to do. (cmangos // caller's stuck-recovery decide what to do. An abandoned
// doesn't teleport here either; an abandoned plan is // plan is recovered by the next MoveFarTo cycle.
// recovered by the next MoveFarTo cycle.)
if (state.walkPoints.size() >= 2) if (state.walkPoints.size() >= 2)
{ {
G3D::Vector3 const& first = state.walkPoints.front(); G3D::Vector3 const& first = state.walkPoints.front();
@ -3528,8 +3525,7 @@ bool MovementAction::ExecuteTravelPlan(TravelPlan& state)
// At portal but didn't cross — natural collision missed. // At portal but didn't cross — natural collision missed.
// Abort the plan; stuck-recovery in MoveFarTo will decide // Abort the plan; stuck-recovery in MoveFarTo will decide
// whether to retry or teleport the bot. (cmangos doesn't // whether to retry or teleport the bot.
// teleport here either.)
state.Reset(); state.Reset();
return false; return false;
} }
@ -3665,10 +3661,10 @@ bool MovementAction::ExecuteTravelPlan(TravelPlan& state)
case PathNodeType::NODE_FLYING_MOUNT: case PathNodeType::NODE_FLYING_MOUNT:
{ {
// Flying-mount node not implemented — abort. cmangos // Flying-mount node not implemented — abort. The graph
// produces these nodes during graph generation but their // generator produces these but their execution is
// execution is server-specific; we treat them as // server-specific; we treat them as unreachable rather
// unreachable rather than papering over with a teleport. // than papering over with a teleport.
state.Reset(); state.Reset();
return false; return false;
} }

View File

@ -112,12 +112,11 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
} }
} }
// 10% lastPath reuse (cmangos MovementActions.cpp:687-689). If // 10% lastPath reuse. If the cached path's endpoint is within
// the cached path's endpoint is within 10% of the new dest's // 10% of the new dest's distance AND the bot is still mid-flight
// distance AND the bot is still mid-flight toward it, skip the // toward it, skip the chained-probe recompute entirely. The 10y
// chained-probe recompute entirely. The 10y guard ensures we // guard ensures we don't reuse a finished path (where the bot
// don't reuse a finished path (where the bot has already // has already arrived at the cached endpoint).
// arrived at the cached endpoint).
{ {
LastMovement& lastMove = AI_VALUE(LastMovement&, "last movement"); LastMovement& lastMove = AI_VALUE(LastMovement&, "last movement");
if (!lastMove.lastPath.empty()) if (!lastMove.lastPath.empty())
@ -137,35 +136,31 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
float disToDest = bot->GetDistance(dest); float disToDest = bot->GetDistance(dest);
float dis = bot->GetExactDist(dest); float dis = bot->GetExactDist(dest);
// Mirrors cmangos-playerbots' ResolveMovePath / getFullPath flow: // Decision tree:
// //
// 1. Active node plan? Ride it. The plan executor owns its own // 1. Active node plan? Ride it. The plan executor owns its own
// per-step transitions (walk/flight/transport/teleport). // per-step transitions (walk/flight/transport/teleport).
// //
// 2. Otherwise, run the same 40-step chained mmap probe that // 2. Otherwise, run the 40-step chained mmap probe. It chains
// cmangos uses everywhere (TravelMgr.cpp:760, ported in PR // PathGenerator calls across navmesh tiles so it reaches
// #2312 onto WorldPosition). It chains PathGenerator calls // destinations far beyond a single PathGenerator's ~296y
// across navmesh tiles so it reaches destinations far beyond // smooth-path cap.
// a single PathGenerator's ~296y smooth-path cap.
// //
// 3. Probe lands within spellDistance of dest AND move is "long" // 3. Probe lands within spellDistance of dest AND move is
// (>= nodeFirstDis): use mmap, skip the node graph. This is // "long" (>= nodeFirstDis): use mmap, skip the node graph.
// cmangos's TravelNode.cpp:1894 short-circuit — and the fix // Fixes cases where mmap CAN route to the destination and
// for the cave-quest case (when mmap CAN route into the // we'd otherwise commit to a cached surface-node detour.
// cave from current position, prefer that over the cached
// surface-node detour).
// //
// 4. Probe didn't reach AND move is long: commit to the // 4. Probe didn't reach AND move is long: commit to the
// travel-node plan (graph A* + flights + transports). // travel-node plan (graph A* + flights + transports).
// //
// 5. Otherwise: walk to the probe's furthest reachable point. // 5. Otherwise: walk to the probe's furthest reachable point.
// Empty / non-progressing probe falls back to a best-effort // Empty / non-progressing probe falls back to a best-effort
// spline at the destination (cmangos line 720: addPoint). // spline at the destination.
// Stuck-recovery (above) handles oscillation.
// //
// No cone / random-direction sampling — cmangos doesn't do it and // No cone / random-direction sampling — it tends to walk bots
// it tends to walk bots into geometry on the way to "stepping // into geometry on the way to "stepping stones" that aren't on
// stones" that aren't on the actual route. // the actual route.
bool tryNodes = (dis >= nodeFirstDis && sPlayerbotAIConfig.enableTravelNodes); bool tryNodes = (dis >= nodeFirstDis && sPlayerbotAIConfig.enableTravelNodes);
// Loop-breaker: count recent attempts of each strategy to this // Loop-breaker: count recent attempts of each strategy to this
@ -206,12 +201,11 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
if (tryNodes && !forceMmapOverNodes && !bothExhausted && botAI->rpgInfo.HasActiveTravelPlan()) if (tryNodes && !forceMmapOverNodes && !bothExhausted && botAI->rpgInfo.HasActiveTravelPlan())
return UpdateTravelPlan(); return UpdateTravelPlan();
// 40-step chained mmap probe (cmangos getPathFromPath, ported in // 40-step chained mmap probe (WorldPosition::getPathFromPath in
// PR #2312 at TravelMgr.cpp:760). Heavier than a single // TravelMgr.cpp). Heavier than a single GeneratePath call but
// GeneratePath call but matches cmangos's "same effort" baseline // chains PathGenerator across multiple navmesh tiles so it can
// — chains PathGenerator calls across multiple navmesh tiles so // reach destinations far beyond one PathGenerator's ~296y
// it can reach destinations far beyond a single PathGenerator's // smooth-path cap.
// ~296y smooth-path cap.
WorldPosition botPos(bot); WorldPosition botPos(bot);
std::vector<WorldPosition> probe = botPos.getPathTo(dest, bot); std::vector<WorldPosition> probe = botPos.getPathTo(dest, bot);
bool probeReachesDest = dest.isPathTo(probe, sPlayerbotAIConfig.spellDistance); bool probeReachesDest = dest.isPathTo(probe, sPlayerbotAIConfig.spellDistance);
@ -222,8 +216,7 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
{ {
// Long-distance move and either mmap couldn't get within // Long-distance move and either mmap couldn't get within
// spellDistance OR we're forcing nodes after 3 failed mmap // spellDistance OR we're forcing nodes after 3 failed mmap
// loops — commit to the travel-node graph // loops — commit to the travel-node graph.
// (cmangos TravelNode.cpp:1907 buildPath branch).
StartTravelPlan(dest); StartTravelPlan(dest);
if (botAI->rpgInfo.HasActiveTravelPlan()) if (botAI->rpgInfo.HasActiveTravelPlan())
{ {
@ -252,13 +245,12 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
botAI->rpgInfo.ClearTravel(); botAI->rpgInfo.ClearTravel();
} }
// Walk the chained probe's full waypoint chain via MoveSplinePath // Walk the chained probe's full waypoint chain via MoveSplinePath.
// — cmangos's DispatchMovement pattern (MovementActions.cpp:1014: // Handing the full waypoint vector to the motion master removes
// mm.MovePath(pointPath, moveMode, false)). Handing the FULL // its discretion to introduce a straight-line shortcut between
// waypoint vector to the motion master removes its discretion // intermediate points — that shortcut produced the diagonal-
// to introduce a straight-line shortcut between intermediate // through-air bug when we used MoveTo(endpoint) and let the
// points (which is what produced the diagonal-through-air bug // motion master replan.
// when we used MoveTo(endpoint) and let the motion master replan).
// Skip when both routing strategies have failed 3 times each. // Skip when both routing strategies have failed 3 times each.
if (!probe.empty() && !bothExhausted && probe.size() >= 2) if (!probe.empty() && !bothExhausted && probe.size() >= 2)
{ {
@ -273,7 +265,7 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
for (auto const& wp : probe) for (auto const& wp : probe)
points.emplace_back(wp.GetPositionX(), wp.GetPositionY(), wp.GetPositionZ()); points.emplace_back(wp.GetPositionX(), wp.GetPositionY(), wp.GetPositionZ());
// Per-waypoint Z-snap (cmangos DispatchMovement:1006). // Per-waypoint Z-snap to current ground.
for (auto& pt : points) for (auto& pt : points)
bot->UpdateAllowedPositionZ(pt.x, pt.y, pt.z); bot->UpdateAllowedPositionZ(pt.x, pt.y, pt.z);
@ -301,12 +293,12 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
if (points.size() >= 2) if (points.size() >= 2)
{ {
// No-worse lastPath reuse (cmangos MovementActions.cpp:716-717). // No-worse lastPath reuse. If the cached path's
// If the cached path's endpoint is no further from // endpoint is no further from dest than this new
// dest than this new probe's, prefer cached to // probe's, prefer cached to prevent path-swapping
// prevent path-swapping mid-walk. Same 10y guard as // mid-walk. Same 10y guard as the top-of-MoveFarTo
// the top-of-MoveFarTo reuse to avoid reusing a // reuse to avoid reusing a finished path the bot
// finished path the bot already arrived at. // already arrived at.
{ {
LastMovement& lastMove = AI_VALUE(LastMovement&, "last movement"); LastMovement& lastMove = AI_VALUE(LastMovement&, "last movement");
if (!lastMove.lastPath.empty()) if (!lastMove.lastPath.empty())
@ -363,8 +355,7 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
MovementPriority::MOVEMENT_NORMAL); MovementPriority::MOVEMENT_NORMAL);
// Cache dispatched waypoints so the next MoveFarTo // Cache dispatched waypoints so the next MoveFarTo
// tick can satisfy the 10% / no-worse reuse checks // tick can satisfy the 10% / no-worse reuse checks.
// (cmangos MovementActions.cpp:687, 716).
std::vector<WorldPosition> wpts; std::vector<WorldPosition> wpts;
wpts.reserve(points.size()); wpts.reserve(points.size());
for (auto const& pt : points) for (auto const& pt : points)
@ -376,10 +367,10 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
} }
} }
// cmangos MovementActions.cpp:720 — empty / non-progressing path // Empty / non-progressing path falls back to dispatching the
// falls back to dispatching the destination as a single waypoint. // destination as a single waypoint. Best-effort spline;
// Best-effort spline; UnstuckAction (5/10 min) is the eventual // UnstuckAction (5/10 min) is the eventual catch if this loops
// catch if this loops forever. // forever.
LOG_INFO("playerbots", "[MoveFar] {} spline | dest=({:.0f},{:.0f},{:.0f}) | dis={:.0f} | probe.empty={} | mmapFails={} nodeFails={} | flags={}{}{}", LOG_INFO("playerbots", "[MoveFar] {} spline | dest=({:.0f},{:.0f},{:.0f}) | dis={:.0f} | probe.empty={} | mmapFails={} nodeFails={} | flags={}{}{}",
bot->GetName(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), dis, bot->GetName(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), dis,
probe.empty() ? "y" : "n", probe.empty() ? "y" : "n",
@ -445,13 +436,11 @@ bool NewRpgBaseAction::MoveWorldObjectTo(ObjectGuid guid, float distance)
y = object->GetPositionY(); y = object->GetPositionY();
z = object->GetPositionZ(); z = object->GetPositionZ();
} }
// Delegate to MoveFarTo so every approach gets the cmangos-aligned // Delegate to MoveFarTo so every approach gets the chained mmap
// chained mmap probe + spellDistance shortcut + travel-node // probe + spellDistance shortcut + travel-node fallback instead
// fallback, instead of a single direct MoveTo. The debug-move // of a single direct MoveTo. The debug-move trace then labels
// trace then labels the actual mechanism (spline / mmap / // the actual mechanism (spline / mmap / nodetravel) rather than
// nodetravel) rather than a generic "MoveWorldObjectTo:spline". // a generic "MoveWorldObjectTo:spline".
// (cmangos: refactor(Move): Route MoveWorldObjectTo through
// MoveFarTo — 4cb3abab.)
return MoveFarTo(WorldPosition(object->GetMapId(), x, y, z)); return MoveFarTo(WorldPosition(object->GetMapId(), x, y, z));
} }

View File

@ -81,10 +81,9 @@ protected:
// Distance at which MoveFarTo considers the travel-node graph as // Distance at which MoveFarTo considers the travel-node graph as
// a routing option. Below this, the move is short enough that // a routing option. Below this, the move is short enough that
// mmap handles it directly. Above this, mmap is *still probed // mmap handles it directly. Above this, mmap is *still probed
// first* via the 40-step chained pathfinder; the node graph only // first* via the 40-step chained pathfinder; the node graph
// takes over if mmap can't get within spellDistance of the // only takes over if mmap can't get within spellDistance of
// destination. Matches cmangos-playerbots' sightDistance gate // the destination.
// (75y) — the only long-path threshold they use.
const float nodeFirstDis = 75.0f; const float nodeFirstDis = 75.0f;
private: private:

View File

@ -83,13 +83,12 @@ struct NewRpgInfo
bool HasActiveTravelPlan() const { return travelPlan.IsActive(); } bool HasActiveTravelPlan() const { return travelPlan.IsActive(); }
void ClearTravel() { travelPlan.Reset(); } void ClearTravel() { travelPlan.Reset(); }
// MoveFar attempt history. Records the last 3 path commits (node // MoveFar attempt history. Records the last 3 path commits
// plan or mmap) so MoveFarTo can detect when the same dest + // (node plan or mmap) so MoveFarTo can detect when the same
// strategy has failed repeatedly and force the alternative // dest + strategy has failed repeatedly and force the
// routing this tick. Breaks deterministic-loop scenarios where // alternative routing this tick. Breaks deterministic-loop
// the chained probe (or node graph) keeps returning the same // scenarios where the chained probe (or node graph) keeps
// dead-end path. Cmangos doesn't do this — they wait 5+ minutes // returning the same dead-end path.
// for UnstuckAction. We're more aggressive here for UX.
struct MoveFarAttempt struct MoveFarAttempt
{ {
WorldPosition dest; // requested destination WorldPosition dest; // requested destination

View File

@ -824,7 +824,7 @@ void PlayerbotAI::HandleTeleportAck()
bot->StopMoving(); bot->StopMoving();
} }
// simulate far teleport latency (cmangos-style) // simulate far teleport latency
SetNextCheckDelay(urand(2000, 5000)); SetNextCheckDelay(urand(2000, 5000));
return; return;
} }

View File

@ -722,14 +722,13 @@ std::vector<WorldPosition> WorldPosition::getPathStepFrom(WorldPosition startPos
} }
// Explicit-start overload (PathGenerator.h:67). Without this, // Explicit-start overload (PathGenerator.h:67). Without this,
// CalculatePath(destX,destY,destZ) defaults to the unit's CURRENT // CalculatePath(destX,destY,destZ) defaults to the unit's
// position as start — which means every iteration of // current position as start — which means every iteration of
// getPathFromPath's "chain" begins from the bot's same real // getPathFromPath's "chain" begins from the bot's same real
// location and produces the same ~296y partial path. The chain // location and produces the same ~296y partial path. The chain
// never advances. With explicit start, each step extends from the // never advances. With explicit start, each step extends from
// previous step's endpoint, giving the 40-attempt walker its // the previous step's endpoint, giving the 40-attempt walker
// intended multi-tile reach. Cmangos passes start explicitly too // its intended multi-tile reach.
// (WorldPosition.cpp:962 — pathfinder->calculate(start, end)).
PathGenerator path(pathUnit); PathGenerator path(pathUnit);
path.CalculatePath(startPos.GetPositionX(), startPos.GetPositionY(), startPos.GetPositionZ(), path.CalculatePath(startPos.GetPositionX(), startPos.GetPositionY(), startPos.GetPositionZ(),
GetPositionX(), GetPositionY(), GetPositionZ(), false); GetPositionX(), GetPositionY(), GetPositionZ(), false);

View File

@ -292,12 +292,12 @@ public:
std::vector<WorldPosition> getPathTo(WorldPosition endPos, Unit* bot) { return endPos.getPathFrom(*this, bot); } std::vector<WorldPosition> getPathTo(WorldPosition endPos, Unit* bot) { return endPos.getPathFrom(*this, bot); }
// Cmangos-aligned (WorldPosition.h:317): the path "reaches" this // The path "reaches" this position when its last point is on
// position when its last point is on the same map, within // the same map, within maxDistance horizontally, and within
// maxDistance horizontally, and within maxZDistance vertically. // maxZDistance vertically. 3D Euclidean distance would falsely
// 3D Euclidean distance would falsely accept paths that end the // accept paths that end the right horizontal distance from us
// right horizontal distance from us but on a roof/floor below. // but on a roof/floor below. maxDistance == 0 falls back to
// maxDistance == 0 falls back to targetPosRecalcDistance (0.1y). // targetPosRecalcDistance (0.1y).
bool isPathTo(std::vector<WorldPosition> const& path, float const maxDistance = 0.0f, bool isPathTo(std::vector<WorldPosition> const& path, float const maxDistance = 0.0f,
float const maxZDistance = 2.0f) const float const maxZDistance = 2.0f) const
{ {