diff --git a/src/Ai/Base/Actions/MovementActions.cpp b/src/Ai/Base/Actions/MovementActions.cpp index 76ccc9405..631fd493b 100644 --- a/src/Ai/Base/Actions/MovementActions.cpp +++ b/src/Ai/Base/Actions/MovementActions.cpp @@ -3232,39 +3232,6 @@ bool MovementAction::LaunchWalkSpline(TravelPlan& state) for (auto& pt : state.walkPoints) bot->UpdateAllowedPositionZ(pt.x, pt.y, pt.z); - // Drop waypoints whose segment from the previous point crosses - // solid geometry. Z-snapping each point to ground is necessary - // but not sufficient — two ground-level waypoints A and B with a - // mountain between them produce a spline that linearly - // interpolates straight through the mountain. vmap LoS check on - // each segment catches that. We only drop the offending B - // (skipping it) — if A→C is also blocked, the loop drops C too, - // until either the path becomes contiguous or empties out. - if (Map* losMap = bot->GetMap()) - { - uint32 const phaseMask = bot->GetPhaseMask(); - for (size_t i = 1; i < state.walkPoints.size(); /* incremented in body */) - { - G3D::Vector3 const& a = state.walkPoints[i - 1]; - G3D::Vector3 const& b = state.walkPoints[i]; - // +2y on Z so the raycast starts/ends near the bot's - // chest level rather than ground (avoids false positives - // from sub-floor poly). - if (!losMap->isInLineOfSight(a.x, a.y, a.z + 2.0f, b.x, b.y, b.z + 2.0f, - phaseMask, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::Nothing)) - { - state.walkPoints.erase(state.walkPoints.begin() + i); - continue; - } - ++i; - } - if (state.walkPoints.size() < 2) - { - state.walkPoints.clear(); - return true; - } - } - // Mount up if (!bot->IsMounted() && !bot->IsInCombat() && bot->IsOutdoors() && bot->IsAlive()) botAI->DoSpecificAction("check mount state", Event(), true); diff --git a/src/Ai/World/Rpg/NewRpgInfo.cpp b/src/Ai/World/Rpg/NewRpgInfo.cpp index 5c5af71f8..e0bb3a29d 100644 --- a/src/Ai/World/Rpg/NewRpgInfo.cpp +++ b/src/Ai/World/Rpg/NewRpgInfo.cpp @@ -78,12 +78,25 @@ void NewRpgInfo::Reset() data = Idle{}; startT = getMSTime(); ClearTravel(); - recentMoveFarAttempts.clear(); + // recentMoveFarAttempts is intentionally NOT cleared. Reset() runs + // on every state change (ChangeToDoQuest, ChangeToIdle, etc.) and + // the do-quest action oscillates through transitions during a + // failure cycle — wiping the deque here would prevent the + // MoveFarTo loop-breaker (nF >= 3 AND mF >= 3 → bothExhausted) + // from converging. CountRecentAttempts already filters by + // destination (within 10y), so stale entries for previous quests + // don't affect new ones. } void NewRpgInfo::RecordMoveFarAttempt(WorldPosition const& dest, bool wasNodeTravel) { - if (recentMoveFarAttempts.size() >= 3) + // Cap at 6 (3 node + 3 mmap). The loop-breaker in MoveFarTo + // requires nF >= 3 AND mF >= 3 to declare bothExhausted. Each + // MoveFarTo failure cycle records BOTH a node attempt and a mmap + // attempt, so a single 3-cap deque would pop the older type + // before its count reached 3, structurally preventing + // bothExhausted from triggering. + if (recentMoveFarAttempts.size() >= 6) recentMoveFarAttempts.pop_front(); MoveFarAttempt a; a.dest = dest; diff --git a/src/Mgr/Travel/TravelMgr.cpp b/src/Mgr/Travel/TravelMgr.cpp index 38008346e..8fb48e925 100644 --- a/src/Mgr/Travel/TravelMgr.cpp +++ b/src/Mgr/Travel/TravelMgr.cpp @@ -739,7 +739,14 @@ std::vector WorldPosition::getPathStepFrom(WorldPosition startPos if (tempCreature) delete tempCreature; - if (type == PATHFIND_INCOMPLETE || type == PATHFIND_NORMAL) + // PathType is a bitmask (PathGenerator.h). Detour can return e.g. + // PATHFIND_INCOMPLETE | PATHFIND_FARFROMPOLY_END (0x84) when the + // destination is a few yards off the nearest polygon — a strict + // `== PATHFIND_INCOMPLETE` check would reject the perfectly usable + // partial path and the chained probe would terminate empty on the + // very first call. PathGenerator's own internal code uses bitwise + // tests like `!(_type & PATHFIND_INCOMPLETE)`. + if (type & (PATHFIND_NORMAL | PATHFIND_INCOMPLETE)) return fromPointsArray(points); return {};