From 59f82465e1c965e2ff38d4972f19adc6a083e3f2 Mon Sep 17 00:00:00 2001 From: bash Date: Sun, 31 May 2026 18:15:24 +0200 Subject: [PATCH] =?UTF-8?q?refactor(Core/Movement):=20DispatchPathPoints?= =?UTF-8?q?=20=E2=86=92=20DispatchMovement=20(TravelPath=20sig=20+=20trans?= =?UTF-8?q?port=20sandwich)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Ai/Base/Actions/MovementActions.cpp | 47 ++++++++++++++++--------- src/Ai/Base/Actions/MovementActions.h | 33 +++++++++++------ 2 files changed, 53 insertions(+), 27 deletions(-) diff --git a/src/Ai/Base/Actions/MovementActions.cpp b/src/Ai/Base/Actions/MovementActions.cpp index 19347e196..985c6619d 100644 --- a/src/Ai/Base/Actions/MovementActions.cpp +++ b/src/Ai/Base/Actions/MovementActions.cpp @@ -338,7 +338,7 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle, // Path-aware funnel: ResolveMovePath → makeShortCut → // UpcommingSpecialMovement/HandleSpecialMovement → ClipPath → - // DispatchPathPoints. Matches the reference's MoveTo2 flow. + // DispatchMovement. Matches the reference's MoveTo2 flow. return MoveTo2(WorldPosition(mapId, x, y, z), idle, react, false, ignoreEnemyTargets, priority, lessDelay); } @@ -2993,12 +2993,7 @@ bool MovementAction::MoveTo2(WorldPosition endPos, botAI->TellMasterNoFacing(tlog); } - std::vector const& pts = path.getPointPath(); - Movement::PointsArray points; - points.reserve(pts.size()); - for (auto const& wp : pts) - points.emplace_back(wp.GetPositionX(), wp.GetPositionY(), wp.GetPositionZ()); - if (points.empty()) + if (path.empty()) return false; if (!bot->IsMounted() && !bot->IsInCombat() && @@ -3006,7 +3001,7 @@ bool MovementAction::MoveTo2(WorldPosition endPos, botAI->DoSpecificAction("check mount state", Event(), true); bool const dispatched = - DispatchPathPoints(endPos, points, "walk", priority, lessDelay); + DispatchMovement(path, endPos, "walk", priority, lessDelay); if (dispatched && !idle) ClearIdleState(); @@ -3014,12 +3009,20 @@ bool MovementAction::MoveTo2(WorldPosition endPos, return dispatched; } -bool MovementAction::DispatchPathPoints(WorldPosition const& dest, - Movement::PointsArray& points, - char const* label, - MovementPriority priority, - bool lessDelay) +bool MovementAction::DispatchMovement(TravelPath const& path, + WorldPosition const& dest, + char const* label, + MovementPriority priority, + bool lessDelay) { + // Build the PointsArray from the TravelPath. Done here (not at the + // caller) so DispatchMovement can be invoked with a TravelPath + // directly, matching the reference's signature. + std::vector const& pts = path.getPointPath(); + Movement::PointsArray points; + points.reserve(pts.size()); + for (auto const& wp : pts) + points.emplace_back(wp.GetPositionX(), wp.GetPositionY(), wp.GetPositionZ()); if (points.empty()) return false; @@ -3080,11 +3083,22 @@ bool MovementAction::DispatchPathPoints(WorldPosition const& dest, if (bot->IsNonMeleeSpellCast(true)) bot->InterruptNonMeleeSpells(true); - // Per-point terrain clamp. + // Per-point terrain clamp with transport-passenger conversion + // sandwich: when on a transport, path coords are in transport-local + // space; UpdateAllowedPositionZ samples world terrain, so we convert + // local→world, snap, world→local. Without the sandwich, snapping a + // transport-relative point against world terrain produces garbage. + Transport* transport = bot->GetTransport(); for (auto& pt : points) + { + if (transport) + transport->CalculatePassengerPosition(pt.x, pt.y, pt.z); bot->UpdateAllowedPositionZ(pt.x, pt.y, pt.z); + if (transport) + transport->CalculatePassengerOffset(pt.x, pt.y, pt.z); + } - // mm.Clear → MovePoint(last) → MoveSplinePath → WaitForReach. + // mm.Clear → MovePoint(last) → MoveSplinePath. MotionMaster* mm = bot->GetMotionMaster(); mm->Clear(); @@ -3102,7 +3116,8 @@ bool MovementAction::DispatchPathPoints(WorldPosition const& dest, // WaitForReach equivalent: cache the dispatched target + duration on // lastMove. Leave ~10y headroom on long paths so we re-evaluate - // before arrival. + // before arrival. (Reference also calls WaitForReach here, which + // blocks the AI loop; we omit that — see header comment.) float waitDist = totalDist > sPlayerbotAIConfig.reactDistance ? std::max(totalDist - 10.0f, 0.0f) : totalDist; UnitMoveType const speedType = (moveMode == FORCED_MOVEMENT_WALK) ? MOVE_WALK : MOVE_RUN; diff --git a/src/Ai/Base/Actions/MovementActions.h b/src/Ai/Base/Actions/MovementActions.h index ac20be7ec..2f365c471 100644 --- a/src/Ai/Base/Actions/MovementActions.h +++ b/src/Ai/Base/Actions/MovementActions.h @@ -65,7 +65,7 @@ protected: // with makeShortCut, handles special head segments // (portal/area-trigger/transport/flight) via HandleSpecialMovement, // clips at hostile creatures via ClipPath (unless ignoreEnemyTargets), - // and dispatches the resulting walk via DispatchPathPoints. + // and dispatches the resulting walk via DispatchMovement. // MoveTo(mapId,...) delegates here unless an intentional bypass // (exact_waypoint / disableMoveSplinePath / flying / swimming / // backwards) routes the move straight to DoMovePoint. @@ -75,16 +75,27 @@ protected: MovementPriority priority = MovementPriority::MOVEMENT_NORMAL, bool lessDelay = false); - // Centralized walk dispatch. Applies inactive-bot teleport carve-out, - // masterWalking mode, pre-dispatch state cleanup (clear emote, stand, - // interrupt cast), per-point UpdateAllowedPositionZ, mm.Clear → - // MovePoint(last) → MoveSplinePath, and a WaitForReach equivalent - // that caches the destination + duration on lastMove. - bool DispatchPathPoints(WorldPosition const& dest, - Movement::PointsArray& points, - char const* label, - MovementPriority priority = MovementPriority::MOVEMENT_NORMAL, - bool lessDelay = false); + // Centralized walk dispatch. Mirrors the reference's DispatchMovement + // shape: takes a TravelPath, builds the PointsArray internally, + // applies inactive-bot teleport carve-out, masterWalking mode, + // pre-dispatch state cleanup (clear emote, stand, interrupt cast), + // transport-passenger coordinate sandwich + // (CalculatePassengerPosition → UpdateAllowedPositionZ → Offset) + // around the per-point Z snap, mm.Clear → MovePoint(last) → + // MoveSplinePath. Caches the destination + duration on lastMove. + // + // Divergence from reference: reference ends with WaitForReach(size) + // which blocks the AI loop until the move completes. AC's combat + // callers (ReachCombatTo) currently funnel through MoveTo → MoveTo2 + // → DispatchMovement; blocking the AI loop here would suspend combat + // re-evaluation for the full move duration. Until combat dispatch is + // restructured to bypass MoveTo2, the WaitForReach is deliberately + // omitted. + bool DispatchMovement(TravelPath const& path, + WorldPosition const& dest, + char const* label, + MovementPriority priority = MovementPriority::MOVEMENT_NORMAL, + bool lessDelay = false); bool MoveTo(WorldObject* target, float distance = 0.0f, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL); bool MoveNear(WorldObject* target, float distance = sPlayerbotAIConfig.contactDistance,