diff --git a/src/game/server/NextBot/Path/NextBotChasePath.h b/src/game/server/NextBot/Path/NextBotChasePath.h index 1929648b63..9aff9e49c7 100644 --- a/src/game/server/NextBot/Path/NextBotChasePath.h +++ b/src/game/server/NextBot/Path/NextBotChasePath.h @@ -45,6 +45,13 @@ class ChasePath : public PathFollower private: void RefreshPath( INextBot *bot, CBaseEntity *subject, const IPathCost &cost, Vector *pPredictedSubjectPos ); +#ifdef NEO + bool HasReachableArea( void ) const; + bool IsLastLegWalkable( INextBot *bot ) const; + void BackOff( INextBot *bot, CBaseEntity *subject ); + + bool m_isPartialPath = false; // following a path the search could not complete +#endif CountdownTimer m_failTimer; // throttle re-pathing if last path attempt failed CountdownTimer m_throttleTimer; // require a minimum time between re-paths @@ -103,9 +110,81 @@ inline void ChasePath::Update( INextBot *bot, CBaseEntity *subject, const IPathC // maintain the path to the subject RefreshPath( bot, subject, cost, pPredictedSubjectPos ); +#ifdef NEO + // A partial path ends in a straight leg from the last area the search reached to the subject, + // through whatever stopped the search. Walk it only as far as that area goes, then wait there. + const bool onLastLeg = subject && IsValid() && m_isPartialPath && GetCurrentGoal() == LastSegment(); + if ( onLastLeg && !IsLastLegWalkable( bot ) ) + { + bot->OnMoveToFailure( this, FAIL_NO_PATH_EXISTS ); + BackOff( bot, subject ); + Invalidate(); + return; + } +#endif + // move along the path towards the subject PathFollower::Update( bot ); + +#ifdef NEO + // the follower dropped the path on the leg (fell off, stuck): wait instead of rebuilding it next tick + if ( onLastLeg && !IsValid() ) + { + BackOff( bot, subject ); + } +#endif +} + + +#ifdef NEO +//---------------------------------------------------------------------------------------------- +/** + * True if a partial path crosses an area before its last leg. Without one the bot already stands + * in the last area the search reached, and the path is only the unreachable leg. + */ +inline bool ChasePath::HasReachableArea( void ) const +{ + return PriorSegment( LastSegment() ) != FirstSegment(); +} + + +//---------------------------------------------------------------------------------------------- +/** + * True while the bot on the last leg of a partial path still has the last area the search reached + * just ahead of it, so it stops at that area's edge instead of walking into what stopped the search + */ +inline bool ChasePath::IsLastLegWalkable( INextBot *bot ) const +{ + const CNavArea *lastArea = LastSegment()->area; + if ( !lastArea || bot->GetLocomotionInterface()->IsStuck() ) + { + return false; + } + + // three quarters of a hull width, so the bot stops before its body crosses the edge + const float edgeLookAhead = 24.0f; + + Vector toEnd = LastSegment()->pos - bot->GetPosition(); + toEnd.z = 0.0f; + toEnd.NormalizeInPlace(); + + return lastArea->IsOverlapping( bot->GetPosition() + edgeLookAhead * toEnd ); +} + + +//---------------------------------------------------------------------------------------------- +// Seconds of re-path back-off per unit of range to the subject, +// the rate RefreshPath's failure branch uses +static constexpr float CHASE_RETRY_SECONDS_PER_UNIT = 0.005f; + +/** + * Throttle the next repath by range to the subject, as a failed path computation does + */ +inline void ChasePath::BackOff( INextBot *bot, CBaseEntity *subject ) +{ + m_failTimer.Start( CHASE_RETRY_SECONDS_PER_UNIT * ( bot->GetRangeTo( subject ) ) ); } +#endif //---------------------------------------------------------------------------------------------- @@ -218,6 +297,17 @@ inline void ChasePath::RefreshPath( INextBot *bot, CBaseEntity *subject, const I isPath = Compute( bot, pathTarget, cost, GetMaxPathLength() ); } +#ifdef NEO + // Even if the chase path only has a partial path, get as close to the target as possible + const bool isCompletePath = isPath; + if ( !isPath && IsValid() ) + { + isPath = HasReachableArea(); + } + + m_isPartialPath = isPath && !isCompletePath; +#endif + if ( isPath ) { if ( bot->IsDebugging( NEXTBOT_PATH ) )