Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions src/game/server/NextBot/Path/NextBotPathFollow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@ static const float LANDING_NAV_AREA_RANGE = 50.0f;
// A climb onto a ladder's wall cap takes the mesh's link at most this many times a path segment,
// then is left to the ledge search: a bot that lands back at the launch point would otherwise jump on every landing
static const int LADDER_CAP_LAUNCHES = 3;

// A re-path from the goal recheck can lead back into the same prop, so once the recheck has acted,
// a goal within this range of the one it acted on,
static constexpr float RECHECK_SAME_GOAL_RANGE = 16.0f;
// or feet within this range of where the bot stood (a re-path can alternate between waypoints a few units apart),
static constexpr float RECHECK_SAME_FEET_RANGE = 32.0f;
// is left alone this long after each action,
static constexpr float RECHECK_HOLD_TIME = 3.0f;
// and after this many actions is left to the stuck monitor
static constexpr int RECHECK_MAX_ACTIONS = 2;
#endif


Expand All @@ -62,6 +72,11 @@ PathFollower::PathFollower( void )
m_wasOnGround = true;
m_capClimbGoal = NULL;
m_capLaunches = 0;

m_recheckGoalPos = vec3_invalid;
m_recheckFeetPos = vec3_invalid;
m_recheckCount = 0;
m_recheckHoldTimer.Invalidate();
#endif
}

Expand Down Expand Up @@ -666,6 +681,88 @@ static bool IsNearNavMesh( INextBot *bot )
{
return TheNavMesh->GetNearestNavArea( bot->GetEntity(), GETNAVAREA_CHECK_GROUND | GETNAVAREA_CHECK_LOS, LANDING_NAV_AREA_RANGE ) != NULL;
}


//--------------------------------------------------------------------------------------------------------------
/**
* Return the goal an off-path bot should steer at, or NULL to re-path. The path may have skipped ahead
* to a goal that was clear from where the bot was then, but a bot pushed off its path keeps steering at it
* from wherever it ends up, and the straight line from there can run into a wall or a prop
*/
static const Path::Segment *FindWalkableGoal( INextBot *bot, const Path *path, const Path::Segment *goal, float goalTolerance )
{
ILocomotion *mover = bot->GetLocomotionInterface();
if ( goal->type != Path::ON_GROUND || goal->ladder || !mover->IsOnGround() || mover->IsClimbingOrJumping() )
{
return goal;
}

// on the goal's area or the one before it, the bot is still on the line the path planned
const CNavArea *myArea = bot->GetEntity()->GetLastKnownArea();
const Path::Segment *prior = path->PriorSegment( goal );
if ( !myArea || myArea == goal->area || ( prior && myArea == prior->area ) )
{
return goal;
}

const Vector &feet = mover->GetFeet();
if ( mover->IsPotentiallyTraversable( feet, goal->pos ) )
{
return goal;
}

// back up to the nearest earlier waypoint still walkable from here, but not back over a ladder, climb or drop
for ( const Path::Segment *seg = prior; seg && seg->type == Path::ON_GROUND && !seg->ladder; seg = path->PriorSegment( seg ) )
{
if ( !mover->IsPotentiallyTraversable( feet, seg->pos ) )
{
continue;
}

// a waypoint the bot already stands at would hand it straight back the blocked goal, so re-path instead
const Vector2D toSeg = ( seg->pos - feet ).AsVector2D();
return toSeg.IsLengthLessThan( goalTolerance ) ? NULL : seg;
}

return NULL;
}


//--------------------------------------------------------------------------------------------------------------
/**
* Check that an off-path bot can still walk to its goal, and back the goal up or re-path when it cannot.
* Return false if the path was invalidated for a re-path
*/
bool PathFollower::RecheckGoal( INextBot *bot )
{
const Vector &feet = bot->GetLocomotionInterface()->GetFeet();
const bool isRepeat = ( m_goal->pos - m_recheckGoalPos ).IsLengthLessThan( RECHECK_SAME_GOAL_RANGE ) ||
( feet - m_recheckFeetPos ).IsLengthLessThan( RECHECK_SAME_FEET_RANGE );
if ( isRepeat && ( m_recheckCount >= RECHECK_MAX_ACTIONS || !m_recheckHoldTimer.IsElapsed() ) )
{
return true;
}

const Segment *walkableGoal = FindWalkableGoal( bot, this, m_goal, m_goalTolerance );
if ( walkableGoal == m_goal )
{
return true;
}

m_recheckCount = isRepeat ? m_recheckCount + 1 : 1;
m_recheckGoalPos = m_goal->pos;
m_recheckFeetPos = feet;
m_recheckHoldTimer.Start( RECHECK_HOLD_TIME );

if ( !walkableGoal )
{
Invalidate();
return false;
}

m_goal = walkableGoal;
return true;
}
#endif


Expand Down Expand Up @@ -876,6 +973,15 @@ void PathFollower::Update( INextBot *bot )
}
}

#ifdef NEO
// on Avoid()'s cadence, an off-path bot whose goal is blocked backs it up or re-paths,
// except while it detours around a prop: the detour's goal lies past the prop on purpose
if ( !isDetouring && m_avoidTimer.IsElapsed() && !RecheckGoal( bot ) )
{
return;
}
#endif


Vector goalPos = m_goal->pos;

Expand Down
6 changes: 6 additions & 0 deletions src/game/server/NextBot/Path/NextBotPathFollow.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ class PathFollower : public Path
CNEOBotPathObstacles m_pathObstacles; // looks ahead for props to steer around and breakables in the way
const Path::Segment *m_capClimbGoal; // the climb onto a ladder's wall cap m_capLaunches counts
int m_capLaunches;

bool RecheckGoal( INextBot *bot ); // an off-path bot whose goal is blocked backs it up or re-paths
Vector m_recheckGoalPos; // the goal the recheck last acted on,
Vector m_recheckFeetPos; // where the bot stood then,
int m_recheckCount; // how many times in a row it acted there,
CountdownTimer m_recheckHoldTimer; // and when it may act there again
#endif

float m_goalTolerance;
Expand Down
Loading