fix(Core/Travel): Audit fixes and probe correctness in MoveFarTo

This commit is contained in:
bash 2026-05-10 03:02:43 +02:00
parent 3710c35a41
commit 753248510a
6 changed files with 66 additions and 56 deletions

View File

@ -9,23 +9,6 @@
LastMovement::LastMovement() { clear(); } LastMovement::LastMovement() { clear(); }
LastMovement::LastMovement(LastMovement& other)
: taxiNodes(other.taxiNodes),
taxiMaster(other.taxiMaster),
lastFollow(other.lastFollow),
lastAreaTrigger(other.lastAreaTrigger),
lastMoveToX(other.lastMoveToX),
lastMoveToY(other.lastMoveToY),
lastMoveToZ(other.lastMoveToZ),
lastMoveToOri(other.lastMoveToOri),
lastFlee(other.lastFlee)
{
lastMoveShort = other.lastMoveShort;
nextTeleport = other.nextTeleport;
lastPath = other.lastPath;
priority = other.priority;
}
void LastMovement::clear() void LastMovement::clear()
{ {
lastMoveShort = WorldPosition(); lastMoveShort = WorldPosition();

View File

@ -27,20 +27,11 @@ class LastMovement
{ {
public: public:
LastMovement(); LastMovement();
LastMovement(LastMovement& other);
LastMovement& operator=(LastMovement const& other) // Non-copyable: holds a std::future and several state fields the
{ // historical copy-helpers omitted. Always pass by reference.
taxiNodes = other.taxiNodes; LastMovement(LastMovement const&) = delete;
taxiMaster = other.taxiMaster; LastMovement& operator=(LastMovement const&) = delete;
lastFollow = other.lastFollow;
lastAreaTrigger = other.lastAreaTrigger;
lastMoveShort = other.lastMoveShort;
lastPath = other.lastPath;
nextTeleport = other.nextTeleport;
priority = other.priority;
return *this;
};
void clear(); void clear();
@ -75,7 +66,7 @@ public:
LastMovementValue(PlayerbotAI* botAI) : ManualSetValue<LastMovement&>(botAI, data) {} LastMovementValue(PlayerbotAI* botAI) : ManualSetValue<LastMovement&>(botAI, data) {}
private: private:
LastMovement data = LastMovement(); LastMovement data{};
}; };
class StayTimeValue : public ManualSetValue<time_t> class StayTimeValue : public ManualSetValue<time_t>

View File

@ -137,11 +137,15 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
bool const needsLongPath = (bot->GetMapId() != dest.GetMapId()) || bool const needsLongPath = (bot->GetMapId() != dest.GetMapId()) ||
(dis > sPlayerbotAIConfig.sightDistance); (dis > sPlayerbotAIConfig.sightDistance);
// Skip travel-node planning inside battlegrounds — the graph is // needsLongPath branch — graph routing only. cmangos's getFullPath
// built for the open world and yields nonsense routes inside BGs. // already attempts a 40-step mmap probe internally before falling
// Mirrors cmangos MovementActions.cpp:705. // back to A* node routing, so we don't need a second probe here.
if (needsLongPath && sPlayerbotAIConfig.enableTravelNodes && // If the graph yields no plan, fall straight to the single-point
!bot->InBattleground()) // fallback (cmangos addPoint(endPosition)). BG gate per
// MovementActions.cpp:705.
if (needsLongPath)
{
if (sPlayerbotAIConfig.enableTravelNodes && !bot->InBattleground())
{ {
StartTravelPlan(dest); StartTravelPlan(dest);
if (botAI->rpgInfo.HasActiveTravelPlan()) if (botAI->rpgInfo.HasActiveTravelPlan())
@ -154,7 +158,19 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
} }
} }
// 40-step chained mmap probe. // Long-path fallback. Cross-map can't be served by a same-map
// straight-line spline — bail rather than splining toward
// unreachable coords.
if (bot->GetMapId() != dest.GetMapId())
return false;
Movement::PointsArray fallback;
fallback.emplace_back(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
fallback.emplace_back(dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
return DispatchPathPoints(dest, fallback, "spline-long");
}
// Short-path branch — 40-step chained mmap probe.
WorldPosition botPos(bot); WorldPosition botPos(bot);
std::vector<WorldPosition> probe = botPos.getPathTo(dest, bot); std::vector<WorldPosition> probe = botPos.getPathTo(dest, bot);
@ -245,14 +261,12 @@ bool NewRpgBaseAction::DispatchPathPoints(WorldPosition const& dest,
lm.setPath(TravelPath(wpts)); lm.setPath(TravelPath(wpts));
} }
// Item 5 — underwater fixup. Push waypoints submerged below the // Push submerged waypoints to the water surface unless dest is itself underwater.
// water surface up to the surface itself, unless the destination if (Map* map = bot->GetMap())
// is itself underwater (e.g. fishing-loot quest GO, sunken ruins).
{ {
WorldPosition destWp = dest; WorldPosition destWp = dest;
if (!destWp.isUnderWater()) if (!destWp.isUnderWater())
{ {
Map* map = bot->GetMap();
for (auto& pt : points) for (auto& pt : points)
{ {
WorldPosition wp(dest.GetMapId(), pt.x, pt.y, pt.z); WorldPosition wp(dest.GetMapId(), pt.x, pt.y, pt.z);
@ -307,11 +321,21 @@ bool NewRpgBaseAction::DispatchPathPoints(WorldPosition const& dest,
break; break;
} }
} }
if (clipAt > 0 && clipAt + 1 < points.size()) // Truncate after the first hostile waypoint. cmangos's
// analog at TravelNode.cpp:1158 sets endP=path.begin() if
// i=0 (mob right at start) and erases everything after,
// collapsing to a 1-point path. Our caller then sees
// points.size() < 2 and bails out — the bot just stops.
if (clipAt < points.size() && clipAt + 1 < points.size())
points.erase(points.begin() + clipAt + 1, points.end()); points.erase(points.begin() + clipAt + 1, points.end());
} }
} }
// After clip, points may have collapsed to one element (mob right
// at start). Bail rather than dispatching a degenerate spline.
if (points.size() < 2)
return false;
G3D::Vector3 const& last = points.back(); G3D::Vector3 const& last = points.back();
float totalDist = 0.f; float totalDist = 0.f;

View File

@ -50,7 +50,7 @@ void NewRpgInfo::ChangeToTravelFlight(uint32 flightMasterEntry, WorldPosition fl
void NewRpgInfo::ChangeToOutdoorPvp(ObjectGuid::LowType capturePointSpawnId) void NewRpgInfo::ChangeToOutdoorPvp(ObjectGuid::LowType capturePointSpawnId)
{ {
startT = getMSTime(); Reset();
OutdoorPvP pvp; OutdoorPvP pvp;
pvp.capturePointSpawnId = capturePointSpawnId; pvp.capturePointSpawnId = capturePointSpawnId;
data = pvp; data = pvp;

View File

@ -873,14 +873,20 @@ std::vector<WorldPosition> WorldPosition::getPathFromPath(std::vector<WorldPosit
PathGenerator path(pathUnit); PathGenerator path(pathUnit);
path.AddExcludeFlag(NAV_GROUND_STEEP); path.AddExcludeFlag(NAV_GROUND_STEEP);
// Area-cost biases. Mirrors cmangos WorldPosition.cpp:1098-1100.
// NAV_WATER weighted 10x so A* prefers shore routes over wading
// through lakes when both are reachable.
path.SetAreaCost(NAV_WATER, 10.0f);
// Limit the pathfinding attempts // Limit the pathfinding attempts
for (uint32 i = 0; i < maxAttempt; i++) for (uint32 i = 0; i < maxAttempt; i++)
{ {
// Drop cached poly state from the previous step. AC's
// BuildPolyPath has a subpath-prefix optimization
// (PathGenerator.cpp:285-389) that keeps ~80% of the prior
// call's poly chain when the new startPoly is anywhere in
// it, then computes a suffix from there. For a chained
// walker this implicitly snaps the start to the previous
// corridor instead of using the explicit startPos we pass
// in, bending the route. Reset wipes the cache so each step
// is a fresh A*.
path.Clear();
// Try to pathfind to this position. // Try to pathfind to this position.
subPath = getPathStepFrom(currentPos, path); subPath = getPathStepFrom(currentPos, path);

View File

@ -1297,7 +1297,13 @@ bool TravelNodeMap::GetFullPath(TravelPlan& plan,
if (botPos.GetMapId() == destination.GetMapId()) if (botPos.GetMapId() == destination.GetMapId())
{ {
std::vector<WorldPosition> probe = destination.getPathFromPath({botPos}, bot, 40); std::vector<WorldPosition> probe = destination.getPathFromPath({botPos}, bot, 40);
if (destination.isPathTo(probe, sPlayerbotAIConfig.spellDistance)) // Guard the degenerate case: getPathFromPath always returns at
// least {botPos}, and isPathTo(probe, spellDistance) succeeds
// when the bot is already within spellDistance of dest.
// Without this guard we'd emit a 1-element plan [botPos as
// PREPATH] and ExecuteTravelPlan would silently no-op every
// tick.
if (probe.size() >= 2 && destination.isPathTo(probe, sPlayerbotAIConfig.spellDistance))
{ {
plan.steps.addPoint(botPos, PathNodeType::NODE_PREPATH); plan.steps.addPoint(botPos, PathNodeType::NODE_PREPATH);
for (size_t i = 1; i < probe.size(); ++i) for (size_t i = 1; i < probe.size(); ++i)