Skip to content
Merged
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)
};
Loading