From ddd4f5fa3d169d19b5a750f4edcf0478bf7daa9a Mon Sep 17 00:00:00 2001 From: Alan Shen Date: Wed, 14 Oct 2026 19:08:57 -0600 Subject: [PATCH] Re-path a bot that stands above its path A path follower steers by the path's line in two dimensions. A bot that walks onto a floor over its path, such as a grate above the corridor the path runs along, keeps walking the line on the upper floor, steps onto whatever lies ahead at that height, and pushes at a goal it cannot step down to until the stuck monitor fires. The landing check already re-paths a bot that lands off the path more than a step below it. The follower now does the same on any grounded update for a bot that stands off the path's areas more than a step above the nearest point of the path, leaving planned drops, gap jumps, climbs and ladders alone. The behavior's next path starts from the bot's own area. A bot on an area connected to either area of the leg it walks, such as a landing beside a ramp the path goes down, reaches the leg by walking on and is left alone. Without that exception, bots on such a landing on ntre_apparatus_dm were sent back through the ramp's link, where they stall. On ntre_apparatus_dm, bots crossing the grate over the blast-door corridor step onto the door frame's top at (-6616, 0, 805) and stand there with a path through the doorway 95 u below. Stuck episodes there, 24 pairs a roster: 1.71 -> 0.62 a match at 12 bots, 0.67 -> 0.25 at 30; whole-map stuck episodes x0.83 and x0.97, kills x1.02 and x1.00. --- .../server/NextBot/Path/NextBotPathFollow.cpp | 58 +++++++++++++++++-- .../server/NextBot/Path/NextBotPathFollow.h | 1 + 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/game/server/NextBot/Path/NextBotPathFollow.cpp b/src/game/server/NextBot/Path/NextBotPathFollow.cpp index a8efd479dd..687815113a 100644 --- a/src/game/server/NextBot/Path/NextBotPathFollow.cpp +++ b/src/game/server/NextBot/Path/NextBotPathFollow.cpp @@ -657,6 +657,54 @@ bool PathFollower::HasFallenBelowPath( INextBot *bot ) const } +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if we stand off the path, more than a step above it: + * we walked onto a floor over the path, such as a grate or a door frame's top, + * and walking on along the path's line does not lead down to it + */ +bool PathFollower::HasRisenAbovePath( INextBot *bot ) const +{ + // a planned drop, gap jump, climb or ladder moves us off the path line on purpose + const Segment *prior = PriorSegment( m_goal ); + if ( m_goal->type != ON_GROUND || ( prior && prior->type != ON_GROUND ) ) + { + return false; + } + + ILocomotion *mover = bot->GetLocomotionInterface(); + if ( mover->IsClimbingOrJumping() || mover->IsUsingLadder() ) + { + return false; + } + + // standing on an area of the path is on the path, the leg being walked checked first as it is the usual case + const CNavArea *area = bot->GetEntity()->GetLastKnownArea(); + if ( !area || area == m_goal->area || ( prior && area == prior->area ) ) + { + return false; + } + + // an area that leads onto the leg is beside it, not above it: walking on still reaches the leg + if ( area->IsConnected( m_goal->area, NUM_DIRECTIONS ) || ( prior && area->IsConnected( prior->area, NUM_DIRECTIONS ) ) ) + { + return false; + } + + for ( const Segment *s = FirstSegment(); s; s = NextSegment( s ) ) + { + if ( s->area == area ) + { + return false; + } + } + + const Vector &feet = mover->GetFeet(); + MoveCursorToClosestPosition( feet ); + return feet.z - GetCursorData().pos.z > mover->GetStepHeight(); +} + + //-------------------------------------------------------------------------------------------------------------- /** * Return true if a nav area lies near the bot's feet. Off the mesh its last known area is still @@ -810,12 +858,14 @@ void PathFollower::Update( INextBot *bot ) #ifdef NEO // a bot that walks or is pushed off a ledge keeps a path it cannot follow from below, - // so re-path on landing instead of pressing on until the stuck monitor fires + // so re-path on landing instead of pressing on until the stuck monitor fires, + // and a bot that walks onto a floor above its path re-paths the same way const bool isOnGround = mover->IsOnGround(); const bool hasJustLanded = isOnGround && !m_wasOnGround; m_wasOnGround = isOnGround; - if ( hasJustLanded && HasFallenBelowPath( bot ) && IsNearNavMesh( bot ) ) + const bool hasLeftPathHeight = ( hasJustLanded && HasFallenBelowPath( bot ) ) || ( isOnGround && HasRisenAbovePath( bot ) ); + if ( hasLeftPathHeight && IsNearNavMesh( bot ) ) { mover->GetBot()->OnMoveToFailure( this, FAIL_FELL_OFF ); @@ -827,10 +877,10 @@ void PathFollower::Update( INextBot *bot ) if ( bot->IsDebugging( NEXTBOT_PATH ) ) { - DevMsg( "PathFollower: OnMoveToFailure( FAIL_FELL_OFF ) because we landed below the path\n" ); + DevMsg( "PathFollower: OnMoveToFailure( FAIL_FELL_OFF ) because we stand off the path's height\n" ); } - mover->ClearStuckStatus( "Landed below path" ); + mover->ClearStuckStatus( "Off the path's height" ); return; } #endif diff --git a/src/game/server/NextBot/Path/NextBotPathFollow.h b/src/game/server/NextBot/Path/NextBotPathFollow.h index 9ea62157d0..b9b6253c22 100644 --- a/src/game/server/NextBot/Path/NextBotPathFollow.h +++ b/src/game/server/NextBot/Path/NextBotPathFollow.h @@ -89,6 +89,7 @@ class PathFollower : public Path #ifdef NEO bool HasFallenBelowPath( INextBot *bot ) const; // return true if we stand off the path, more than a step below it + bool HasRisenAbovePath( INextBot *bot ) const; // return true if we stand off the path, more than a step above it const CNavArea *LadderCapAhead( INextBot *bot ) const; // return the ladder top shallower than a hull the climb ahead is onto, or NULL bool m_wasOnGround; CNEOBotPathObstacles m_pathObstacles; // looks ahead for props to steer around and breakables in the way