diff --git a/src/game/server/neo/bot/behavior/neo_bot_retreat_to_cover.cpp b/src/game/server/neo/bot/behavior/neo_bot_retreat_to_cover.cpp index 218b61624f..e95668a1c1 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_retreat_to_cover.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_retreat_to_cover.cpp @@ -351,9 +351,18 @@ ActionResult< CNEOBot > CNEOBotRetreatToCover::Update( CNEOBot *me, float interv m_waitInCoverTimer.Reset(); - if ( !m_path.IsValid() ) + if ( !m_path.IsValid() && !CNEOBotPathCompute( me, m_path, m_coverArea->GetCenter(), RETREAT_ROUTE ) ) { - CNEOBotPathCompute( me, m_path, m_coverArea->GetCenter(), RETREAT_ROUTE ); + // cover we have no path to is no cover: pick another spot, or stop retreating when the pick repeats + CNavArea *pUnreachableArea = m_coverArea; + m_coverArea = FindCoverArea( me ); + + if ( m_coverArea == NULL || m_coverArea == pUnreachableArea ) + { + return Done( "No path to my cover" ); + } + + return Continue(); } m_path.Update( me ); diff --git a/src/game/server/neo/bot/neo_bot_path_compute.cpp b/src/game/server/neo/bot/neo_bot_path_compute.cpp index ff4e1457c3..2fb7d84db2 100644 --- a/src/game/server/neo/bot/neo_bot_path_compute.cpp +++ b/src/game/server/neo/bot/neo_bot_path_compute.cpp @@ -34,10 +34,38 @@ static void CNEOBotReservePath(CNEOBot* me, PathFollower& path) } } +// The last leg walks from the last area the path reached to the goal position, +// and the path follower takes a goal more than a jump above that area's floor as a fall (FAIL_FELL_OFF) +static bool IsGoalLegTooHigh(CNEOBot* bot, const PathFollower& path) +{ + const Path::Segment* goalSeg = path.LastSegment(); + const Path::Segment* legStart = path.PriorSegment(goalSeg); + if (!legStart || !legStart->area) + { + return false; + } + + // a planned jump-up, gap jump, drop or ladder carries the bot along this leg + if (legStart->type != Path::ON_GROUND || goalSeg->type != Path::ON_GROUND) + { + return false; + } + + const float rise = goalSeg->pos.z - legStart->area->GetZ(goalSeg->pos); + return rise > bot->GetLocomotionInterface()->GetMaxJumpHeight(); +} + // A path that stops short of the goal is only wanted if the caller asked for partial paths -static bool IsPathUsable(const PathFollower& path, bool reachedGoal, bool includeGoalIfPathFails) +static bool IsPathUsable(CNEOBot* bot, const PathFollower& path, bool reachedGoal, bool includeGoalIfPathFails) { - return path.IsValid() && (reachedGoal || includeGoalIfPathFails); + // Path::Compute can take an area below the goal for the goal's own, as CNavArea::Contains skips areas outside its height range, + // so a complete path's last leg is checked too + if (!path.IsValid() || IsGoalLegTooHigh(bot, path)) + { + return false; + } + + return reachedGoal || includeGoalIfPathFails; } // By default, assumes that we would prefer to have partial paths even if we can't make a full path @@ -49,7 +77,7 @@ bool CNEOBotPathCompute(CNEOBot* bot, PathFollower& path, const Vector& goal, Ro CNEOBotPathCost cost_with_reservations(bot, route); bool reachedGoal = path.Compute(bot, goal, cost_with_reservations, maxPathLength, includeGoalIfPathFails, requireGoalArea); - if (IsPathUsable(path, reachedGoal, includeGoalIfPathFails)) + if (IsPathUsable(bot, path, reachedGoal, includeGoalIfPathFails)) { CNEOBotReservePath(bot, path); return true; @@ -58,7 +86,7 @@ bool CNEOBotPathCompute(CNEOBot* bot, PathFollower& path, const Vector& goal, Ro CNEOBotPathCost cost_without_reservations(bot, FASTEST_ROUTE); cost_without_reservations.m_bIgnoreReservations = true; reachedGoal = path.Compute(bot, goal, cost_without_reservations, maxPathLength, includeGoalIfPathFails, requireGoalArea); - if (IsPathUsable(path, reachedGoal, includeGoalIfPathFails)) + if (IsPathUsable(bot, path, reachedGoal, includeGoalIfPathFails)) { CNEOBotReservePath(bot, path); return true;