Skip to content
Closed
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
119 changes: 117 additions & 2 deletions src/game/server/neo/bot/behavior/neo_bot_ladder_climb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ static constexpr float LADDER_PLACE_SEEK = 16.0f;
//---------------------------------------------------------------------------------------------
CNEOBotLadderClimb::CNEOBotLadderClimb( const CNavLadder *ladder, bool goingUp )
: m_ladder( ladder ), m_bGoingUp( goingUp ), m_flLastZ( 0.0f ),
m_bDismountPhase( false ), m_bJumpedOffLadder( false ), m_pExitArea( nullptr ), m_nNudges( 0 )
m_bDismountPhase( false ), m_bJumpedOffLadder( false ), m_bCrawlDismount( false ), m_bCrawlPushed( false ), m_pExitArea( nullptr ), m_nNudges( 0 )
{
m_dismountPos = vec3_origin;
m_ladderForward = ladder ? -ladder->GetNormal() : vec3_origin;
Expand Down Expand Up @@ -355,7 +355,8 @@ ActionResult<CNEOBot> CNEOBotLadderClimb::Update( CNEOBot *me, float /*interval*

if ( !onLadder )
{
if ( mover->IsOnGround() )
// crawling off, the gap's sill is ground too, short of the landing
if ( mover->IsOnGround() && !m_bCrawlDismount )
{
return Done( "Reached the ground, ending climb" );
}
Expand Down Expand Up @@ -472,6 +473,20 @@ ActionResult<CNEOBot> CNEOBotLadderClimb::Update( CNEOBot *me, float /*interval*
bWantsDismount = true;
}

// A gap at the top only a crouched body fits through, such as a vent or a low window: climbing on never clears it,
// so duck on the way up, and step off crouched once the gap is in reach
if ( m_bGoingUp && onLadder && m_pExitArea && currentZ > GetCrawlFeetZ() - CRAWL_DUCK_RANGE && IsCrawlOnlyExit( me, toExit ) )
{
me->PressCrouchButton( CRAWL_CROUCH_HOLD );
if ( ( me->GetFlags() & FL_DUCKING ) && fabsf( GetCrawlFeetZ() - currentZ ) <= CRAWL_SNAP_DIST )
{
m_bCrawlDismount = true;
EnterDismountPhase( me );
UpdateCrawlDismount( me, toExit, onLadder );
return Continue();
}
}

// A parapet between the ladder and a roof exit blocks the step off at floor height:
// keep climbing until the hull clears it (stuck detection ends it if nothing does)
const bool bLipInTheWay = bWantsDismount && m_bGoingUp && onLadder
Expand Down Expand Up @@ -562,6 +577,12 @@ ActionResult<CNEOBot> CNEOBotLadderClimb::Update( CNEOBot *me, float /*interval*
return Done( "Dismount walk timed out" );
}

if ( m_bCrawlDismount && m_pExitArea )
{
UpdateCrawlDismount( me, toExit, onLadder );
return Continue();
}

// Build look target toward exit area center with vertical bias preserved
if ( m_pExitArea )
{
Expand Down Expand Up @@ -649,6 +670,100 @@ bool CNEOBotLadderClimb::IsDismountBlocked( CNEOBot *me, const Vector &toExit )

return tr.DidHit();
}
//---------------------------------------------------------------------------------------------
// Which way to crawl off toward the exit: along the face for an exit beside the ladder,
// since on the ladder a press with any part into the face climbs
Vector CNEOBotLadderClimb::GetCrawlDirection( const Vector &toExit ) const
{
if ( DotProduct( toExit, m_ladderForward ) >= 0.5f )
{
return toExit;
}

const Vector &normal = m_ladder->GetNormal();
Vector along = toExit - DotProduct( toExit, normal ) * normal;
along.z = 0.0f;
return ( along.NormalizeInPlace() > 0.0f ) ? along : toExit;
}

//---------------------------------------------------------------------------------------------
// Where the feet step off crawling: just above the ladder's top
float CNEOBotLadderClimb::GetCrawlFeetZ() const
{
return m_ladder->m_top.z + CRAWL_FEET_ABOVE_TOP;
}

//---------------------------------------------------------------------------------------------
// From the ladder's top toward the exit, the standing body is blocked but a crouched one is clear
bool CNEOBotLadderClimb::IsCrawlOnlyExit( CNEOBot *me, const Vector &toExit ) const
{
const IBody *body = me->GetBodyInterface();
const float halfWidth = body->GetHullWidth() * 0.5f;
const Vector mins( -halfWidth, -halfWidth, 0.0f );
const Vector standMaxs( halfWidth, halfWidth, body->GetStandHullHeight() );
const Vector crouchMaxs( halfWidth, halfWidth, body->GetCrouchHullHeight() );

Vector start = me->GetLocomotionInterface()->GetFeet();
start.z = GetCrawlFeetZ();
const Vector end = start + GetCrawlDirection( toExit ) * ( DISMOUNT_CLEARANCE_HULLS * body->GetHullWidth() );

trace_t tr;
UTIL_TraceHull( start, end, mins, standMaxs, MASK_NPCSOLID, me, COLLISION_GROUP_NONE, &tr );
if ( !tr.DidHit() )
{
return false;
}

UTIL_TraceHull( start, end, mins, crouchMaxs, MASK_NPCSOLID, me, COLLISION_GROUP_NONE, &tr );
return !tr.DidHit();
}

//---------------------------------------------------------------------------------------------
// Bot updates come a tenth of a second apart, too coarse to stop a climb inside the gap's few units,
// so the crouched bot is set just above the ladder's top, let go of the ladder and pushed into the gap:
//
// |#####| <- the gap's top
// ladder | o-> | landing
// || |_____|________ <- the gap's sill, the ladder's top
// || |#####|
void CNEOBotLadderClimb::UpdateCrawlDismount( CNEOBot *me, const Vector &toExit, bool bOnLadder )
{
me->PressCrouchButton( CRAWL_CROUCH_HOLD );
if ( bOnLadder && !m_bCrawlPushed )
{
m_bCrawlPushed = true;

Vector crawlPos = me->GetLocomotionInterface()->GetFeet();
crawlPos.z = GetCrawlFeetZ();
trace_t tr;
UTIL_TraceHull( crawlPos, crawlPos, me->WorldAlignMins(), me->WorldAlignMaxs(), MASK_PLAYERSOLID, me, COLLISION_GROUP_PLAYER_MOVEMENT, &tr );
if ( tr.startsolid )
{
return;
}

me->ReleaseMoveUpButton();
me->ReleaseForwardButton();
me->SetAbsOrigin( crawlPos );
me->SetMoveType( MOVETYPE_WALK );

// with a little rise, so the bot reaches the sill before it falls below it
Vector push = GetCrawlDirection( toExit ) * CRAWL_PUSH_SPEED;
push.z = CRAWL_PUSH_RISE_SPEED;
me->SetAbsVelocity( push );
return;
}

// Level, so the bot looks where it walks. In the air beside the ladder a press toward its face grabs it again,
// so the push alone carries the bot until it lands
me->GetBodyInterface()->AimHeadTowards( me->EyePosition() + toExit * 100.0f, IBody::MANDATORY, 0.1f, nullptr, "Crawling off the ladder" );
ILocomotion *mover = me->GetLocomotionInterface();
if ( mover->IsOnGround() )
{
mover->Approach( mover->GetFeet() + toExit * 100.0f );
}
}

//---------------------------------------------------------------------------------------------
void CNEOBotLadderClimb::EnterDismountPhase( CNEOBot *me )
{
Expand Down
12 changes: 12 additions & 0 deletions src/game/server/neo/bot/behavior/neo_bot_ladder_climb.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ class CNEOBotLadderClimb : public Action<CNEOBot>
bool IsNarrowLanding( CNEOBot *me ) const;
Vector GetDismountPos( CNEOBot *me ) const;
bool IsDismountBlocked( CNEOBot *me, const Vector &toExit ) const;
Vector GetCrawlDirection( const Vector &toExit ) const;
float GetCrawlFeetZ() const;
bool IsCrawlOnlyExit( CNEOBot *me, const Vector &toExit ) const;
void UpdateCrawlDismount( CNEOBot *me, const Vector &toExit, bool bOnLadder );
void ClaimLadder( CNEOBot *me ) const;

const CNavLadder *m_ladder;
Expand All @@ -43,6 +47,8 @@ class CNEOBotLadderClimb : public Action<CNEOBot>
bool m_bGoingUp;
bool m_bDismountPhase;
bool m_bJumpedOffLadder;
bool m_bCrawlDismount; // stepping off crouched, through a gap only a crouched body fits
bool m_bCrawlPushed; // set in the gap and pushed off the ladder

float m_flLastZ;
Vector m_dismountPos;
Expand All @@ -62,5 +68,11 @@ class CNEOBotLadderClimb : public Action<CNEOBot>
static constexpr float DISMOUNT_CLEARANCE_HULLS = 1.5f; // Hull widths toward the exit that must be clear to step off
static constexpr float NARROW_LANDING_HULLS = 0.5f; // Hull widths deep a landing must be to stand on (measured on dawn and dusk L1's 7 u wall cap)
static constexpr float LANDING_INSET = 16.0f; // How far inside a landing's near edge the dismount aims: half a hull, so the whole hull lands
static constexpr float CRAWL_FEET_ABOVE_TOP = 4.0f; // Crouched, the feet step off this far above the ladder's top
static constexpr float CRAWL_DUCK_RANGE = 64.0f; // How far below that height the climb ducks for a crawl exit: a few bot updates' climb
static constexpr float CRAWL_SNAP_DIST = 24.0f; // How far from that height the bot may be set there: a little over a bot update's climb
static constexpr float CRAWL_PUSH_SPEED = 150.0f; // How fast the bot is pushed off the ladder into the gap,
static constexpr float CRAWL_PUSH_RISE_SPEED = 40.0f; // and up, a unit's rise
static constexpr float CRAWL_CROUCH_HOLD = 0.3f; // Seconds each crouch press lasts, outliving a bot update
static constexpr int STALL_NUDGES = 3; // Sideways shimmies before a stalled climb jumps off (measured on subsurface L1 and saitama L2)
};
59 changes: 46 additions & 13 deletions src/game/server/neo/bot/neo_bot_path_obstacles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,26 @@ static void GetLineBounds( const CUtlVector< Vector > &line, float range, Vector
}


//----------------------------------------------------------------------------------------------------------------
// Return true if the body, walking the line up to the end of the given leg, meets the entity before anything else,
// passing through players, who move
static bool IsMetFirst( INextBot *bot, const BodyBox_t &body, const CUtlVector< Vector > &line, int lastLeg, CBaseEntity *entity )
{
CTraceFilterNoNPCsOrPlayer filter( bot->GetEntity(), COLLISION_GROUP_NONE );
for ( int leg = 0; leg <= lastLeg; ++leg )
{
trace_t result;
UTIL_TraceHull( line[ leg ], line[ leg + 1 ], body.mins, body.maxs, MASK_PLAYERSOLID, &filter, &result );
if ( result.DidHit() )
{
return result.m_pEnt == entity;
}
}

return false;
}


//----------------------------------------------------------------------------------------------------------------
// The breakable the body meets first walking the line, within PATH_OBSTACLE_BREAKABLE_RANGE,
// when nothing else stands in front of it
Expand Down Expand Up @@ -130,25 +150,32 @@ static CBaseEntity *FindBreakableInWay( INextBot *bot, const BodyBox_t &body, co
}

// what the body meets first on the way may be a crate, a door or a wall in front of the breakable:
// keep the breakable only if the body reaches it first, passing through players, who move
CTraceFilterNoNPCsOrPlayer filter( bot->GetEntity(), COLLISION_GROUP_NONE );
for ( int leg = 0; leg <= nearestLeg; ++leg )
// keep the breakable only if the body reaches it first
if ( IsMetFirst( bot, body, line, nearestLeg, nearest ) )
{
trace_t result;
UTIL_TraceHull( line[ leg ], line[ leg + 1 ], body.mins, body.maxs, MASK_PLAYERSOLID, &filter, &result );
if ( result.DidHit() )
{
return ( result.m_pEnt == nearest ) ? nearest : NULL;
}
return nearest;
}

return NULL;
// or reaches it crouched, since a vent cover in a crawl gap sits under a lintel the standing body meets first
BodyBox_t crouched = body;
crouched.maxs.z = bot->GetBodyInterface()->GetCrouchHullHeight();
return IsMetFirst( bot, crouched, line, nearestLeg, nearest ) ? nearest : NULL;
}

//----------------------------------------------------------------------------------------------------------------
// Where the path ahead ends: at the first segment not walked on the ground,
// or at the ledge of a drop, which is walked at the height of the floor above before the fall
enum PathAheadEnd_t
{
PATH_AHEAD_TO_GROUND_END,
PATH_AHEAD_TO_DROP_LEDGE,
};

//----------------------------------------------------------------------------------------------------------------
// The path ahead as a line from the bot's feet, over walked segments only, up to the range,
// and, given a list for them, the segment each point of the line belongs to
static void GetPathAhead( const PathFollower &path, const Vector &feet, float range, CUtlVector< Vector > *line, CUtlVector< const Path::Segment * > *segments = NULL )
static void GetPathAhead( const PathFollower &path, const Vector &feet, float range, CUtlVector< Vector > *line,
CUtlVector< const Path::Segment * > *segments = NULL, PathAheadEnd_t end = PATH_AHEAD_TO_GROUND_END )
{
line->AddToTail( feet );
if ( segments )
Expand All @@ -159,7 +186,8 @@ static void GetPathAhead( const PathFollower &path, const Vector &feet, float ra
float length = 0.0f;
for ( const Path::Segment *seg = path.GetCurrentGoal(); seg && length < range; seg = path.NextSegment( seg ) )
{
if ( seg->ladder || seg->type != Path::ON_GROUND )
const bool isDropLedge = ( end == PATH_AHEAD_TO_DROP_LEDGE && seg->type == Path::DROP_DOWN );
if ( seg->ladder || ( seg->type != Path::ON_GROUND && !isDropLedge ) )
{
break;
}
Expand All @@ -179,6 +207,10 @@ static void GetPathAhead( const PathFollower &path, const Vector &feet, float ra

length += legLength;
line->AddToTail( seg->pos );
if ( isDropLedge )
{
break;
}
}
}

Expand Down Expand Up @@ -358,7 +390,8 @@ void CNEOBotPathObstacles::LookForBreakable( INextBot *bot, const PathFollower &
m_breakable = NULL;

CUtlVector< Vector > line;
GetPathAhead( path, bot->GetLocomotionInterface()->GetFeet(), PATH_OBSTACLE_BREAKABLE_RANGE, &line );
// a vent cover can block the way to a drop's ledge, such as out of a crawl duct
GetPathAhead( path, bot->GetLocomotionInterface()->GetFeet(), PATH_OBSTACLE_BREAKABLE_RANGE, &line, NULL, PATH_AHEAD_TO_DROP_LEDGE );
if ( line.Count() < 2 )
{
return;
Expand Down
Loading