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
195 changes: 191 additions & 4 deletions src/game/server/NextBot/Player/NextBotPlayerLocomotion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,30 @@

ConVar NextBotPlayerMoveDirect( "nb_player_move_direct", "0" );

// NEO: CGameMovement::LadderMove() puts a player on any ladder their wish direction points at,
// while TraverseLadder()'s NO_LADDER branch drops the move type straight back to walking whenever
// the bot's own state machine did not ask for a climb. A path that merely brushes past a ladder
// leaves the two toggling every tick: the bot hangs on the ladder making no progress, and because
// it never stops moving, no stuck event fires either.
//
// Letting go harder does not fix it - the wish direction still points at the brush. What does is
// giving up the argument and riding the ladder out by its nearer end, but only for the contacts
// that actually persist; most sort themselves out within a second and adopting those buys a lot of
// climbing nobody asked for.

// How long a bot must be held against a ladder it did not ask for before taking it over. Short
// enough that a real snag is caught within half a second, long enough that the ordinary release
// still handles the many contacts that clear themselves immediately.
static const float LADDER_ADOPT_TIME = 0.5f;
// A ladder further than this from the bot is not the one it is stuck on.
static const float LADDER_TOUCH_RANGE = 64.0f;
// A gap longer than this means the bot walked away and came back, so the bout starts over.
static const float LADDER_CONTACT_RESET = 1.0f;

// NEO: going down a ladder, forward is pressed only once it moves the bot down at least this
// fraction of the climb speed. Slower than that, the view is still coming round.
static const float LADDER_MIN_DESCENT_RATE = 0.25f;

//-----------------------------------------------------------------------------------------------------
PlayerLocomotion::PlayerLocomotion( INextBot *bot ) : ILocomotion( bot )
{
Expand Down Expand Up @@ -45,13 +69,150 @@ void PlayerLocomotion::Reset( void )
m_ladderDismountGoal = NULL;
m_ladderTimer.Invalidate();

m_unwantedLadderSince = 0.0f;
m_unwantedLadderLastTouch = 0.0f;

m_minSpeedLimit = 0.0f;
m_maxSpeedLimit = 9999999.9f;

BaseClass::Reset();
}


//-----------------------------------------------------------------------------------------------------
/**
* NEO: the nav ladder the bot is standing in, or NULL. The engine knows which brush it grabbed but
* keeps that normal private, so match against the nav record instead - close in xy, and inside the
* ladder's own height span.
*/
const CNavLadder *PlayerLocomotion::FindTouchedLadder( void ) const
{
const Vector &feet = GetFeet();
const CNavLadder *best = NULL;
float bestRangeSq = LADDER_TOUCH_RANGE * LADDER_TOUCH_RANGE;

// The hull reaches a standing height above the feet, and the grab only needs the hull to touch
// the brush - so a bot standing on the floor well *below* a ladder whose foot hangs over a
// walkway is exactly the case to catch here, not one to filter out.
const float below = GetBot()->GetBodyInterface()->GetStandHullHeight();

for ( int i = 0; i < TheNavMesh->GetLadders().Count(); ++i )
{
const CNavLadder *ladder = TheNavMesh->GetLadders()[i];

if ( feet.z < ladder->m_bottom.z - below || feet.z > ladder->m_top.z + GetStepHeight() )
{
continue;
}

const float rangeSq = ( ladder->GetPosAtHeight( feet.z ) - feet ).AsVector2D().LengthSqr();
if ( rangeSq < bestRangeSq )
{
bestRangeSq = rangeSq;
best = ladder;
}
}

return best;
}


//-----------------------------------------------------------------------------------------------------
/**
* NEO: called when the engine has us on a ladder our own path never asked for. Returns true if the
* ladder was taken over, in which case the caller leaves the move type alone and the normal ladder
* state machine drives from here.
*/
bool PlayerLocomotion::HandleUnwantedLadder( void )
{
const CNavLadder *ladder = FindTouchedLadder();
if ( ladder == NULL )
{
return false;
}

// Most grabs sort themselves out within a second, and adopting every one of them buys a lot of
// climbing nobody asked for. Only take the ladder over once the bot has actually been held
// against it.
const float now = gpGlobals->curtime;

if ( now - m_unwantedLadderLastTouch > LADDER_CONTACT_RESET )
{
m_unwantedLadderSince = now;
}

m_unwantedLadderLastTouch = now;

if ( now - m_unwantedLadderSince < LADDER_ADOPT_TIME )
{
return false;
}

// Leave by the nearer end rather than argue about being here at all. Whichever end the bot is
// closer to is the one it can reach soonest, and the dismount goal has to be a real area or
// DismountLadderTop/Bottom has nothing to walk to.
//
// Climbing out upward only was measured and is worse: it sends the bot somewhere it then has to
// come back from, and costs captures even though it reads better on the ladder numbers.
const bool bGoUp = ( GetFeet().z - ladder->m_bottom.z ) > ( ladder->m_top.z - GetFeet().z );

// A ladder's top is recorded in whichever of the three slots the generator filled, and plenty
// of them leave m_topForwardArea empty - reading only that slot silently skips those ladders.
const CNavArea *dismount = ladder->m_bottomArea;

if ( bGoUp )
{
dismount = ladder->m_topForwardArea ? ladder->m_topForwardArea :
( ladder->m_topLeftArea ? ladder->m_topLeftArea : ladder->m_topRightArea );
}

if ( dismount == NULL )
{
return false;
}

m_ladderInfo = ladder;
m_ladderDismountGoal = dismount;
m_ladderState = bGoUp ? ASCENDING_LADDER : DESCENDING_LADDER;

return true;
}


//-----------------------------------------------------------------------------------------------------
// NEO: the horizontal direction into the face of the ladder brush we hold. LadderMove() takes whichever
// face we touched, so a bot that grabbed the ladder from behind holds its back face.
Vector PlayerLocomotion::GetIntoLadderFace( const CNavLadder *ladder ) const
{
const Vector &normal = ladder->GetNormal();
const bool bBehind = DotProduct( GetFeet() - ladder->m_top, normal ) < 0.0f;

return bBehind ? normal : -normal;
}


//-----------------------------------------------------------------------------------------------------
// NEO: would pressing forward move us down this ladder? LadderMove() turns a push into the face into
// climbing up, so forward descends only while the view points further down than into the face.
bool PlayerLocomotion::IsForwardDownLadder( const CNavLadder *ladder ) const
{
Vector view;
m_player->EyeVectors( &view );

// Facing away from the face is no good either: standing on a floor, LadderMove() pushes us off for it
const float intoFace = DotProduct( view, GetIntoLadderFace( ladder ) );
if ( intoFace < 0.0f )
{
return false;
}

// The vertical speed a forward press gives, as a fraction of the climb speed
const float climbRate = view.z + intoFace;

return climbRate < -LADDER_MIN_DESCENT_RATE;
}


//-----------------------------------------------------------------------------------------------------
bool PlayerLocomotion::TraverseLadder( void )
{
Expand Down Expand Up @@ -87,7 +248,13 @@ bool PlayerLocomotion::TraverseLadder( void )

if ( GetBot()->GetEntity()->GetMoveType() == MOVETYPE_LADDER )
{
// on ladder and don't want to be
// On a ladder and don't want to be. HandleUnwantedLadder() may take the ladder over
// instead, in which case the state machine drives from here and we are done.
if ( HandleUnwantedLadder() )
{
break;
}

GetBot()->GetEntity()->SetMoveType( MOVETYPE_WALK );
}
return false;
Expand Down Expand Up @@ -115,6 +282,13 @@ PlayerLocomotion::LadderState PlayerLocomotion::ApproachAscendingLadder( void )
return DISMOUNTING_LADDER_TOP;
}

// NEO: a bot already on the ladder is not too far below it, whatever the nav ladder's bottom says -
// on a ladder whose foot hangs above the floor the check below would drop a climb the bot has begun
if ( GetBot()->GetEntity()->GetMoveType() == MOVETYPE_LADDER )
{
return ASCENDING_LADDER;
}

// sanity check - are we too far below this ladder to reach it?
if ( GetFeet().z <= m_ladderInfo->m_bottom.z - GetMaxJumpHeight() )
{
Expand Down Expand Up @@ -149,6 +323,13 @@ PlayerLocomotion::LadderState PlayerLocomotion::ApproachDescendingLadder( void )
return NO_LADDER;
}

// NEO: a bot already on the ladder climbs down it. Steering on towards the mount point presses forward
// with the view still level, which climbs it up and off the top; letting go drops it from there.
if ( GetBot()->GetEntity()->GetMoveType() == MOVETYPE_LADDER )
{
return DESCENDING_LADDER;
}

// sanity check - are we already at the end of this ladder?
if ( GetFeet().z <= m_ladderInfo->m_bottom.z + GetMaxJumpHeight() )
{
Expand Down Expand Up @@ -280,8 +461,9 @@ PlayerLocomotion::LadderState PlayerLocomotion::DescendLadder( void )
return DISMOUNTING_LADDER_BOTTOM;
}

// climb down this ladder - look down
Vector goal = GetFeet() + 100.0f * ( m_ladderInfo->GetNormal() + Vector( 0, 0, -2 ) );
// NEO: climb down this ladder - look down into its face (facing away, a bot still on the top floor is
// pushed off the ladder the moment it presses forward)
Vector goal = GetFeet() + 100.0f * ( GetIntoLadderFace( m_ladderInfo ) + Vector( 0, 0, -2 ) );

GetBot()->GetBodyInterface()->AimHeadTowards( goal, IBody::MANDATORY, 0.1f, NULL, "Ladder" );

Expand Down Expand Up @@ -489,7 +671,12 @@ void PlayerLocomotion::Approach( const Vector &pos, float goalWeight )
}
#endif

if ( m_player->IsOnLadder() && IsUsingLadder() && ( m_ladderState == ASCENDING_LADDER || m_ladderState == DESCENDING_LADDER ) )
if ( m_player->IsOnLadder() && m_ladderState == DESCENDING_LADDER && m_ladderInfo && !IsForwardDownLadder( m_ladderInfo ) )
{
// NEO: on the way down, press nothing until forward would move us down. With the view still
// coming round it climbs us up, and from the top of the ladder, off it.
}
else if ( m_player->IsOnLadder() && IsUsingLadder() && ( m_ladderState == ASCENDING_LADDER || m_ladderState == DESCENDING_LADDER ) )
{
// we are on a ladder and WANT to be on a ladder.
playerButtons->PressForwardButton();
Expand Down
10 changes: 10 additions & 0 deletions src/game/server/NextBot/Player/NextBotPlayerLocomotion.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class PlayerLocomotion : public ILocomotion
virtual bool IsUsingLadder( void ) const;
virtual bool IsAscendingOrDescendingLadder( void ) const; // we are actually on the ladder right now, either climbing up or down
virtual bool IsAbleToAutoCenterOnLadder( void ) const;
bool IsForwardDownLadder( const CNavLadder *ladder ) const; // NEO: would pressing forward move us down this ladder

virtual void FaceTowards( const Vector &target ); // rotate body to face towards "target"

Expand Down Expand Up @@ -127,6 +128,15 @@ class PlayerLocomotion : public ILocomotion
const CNavArea *m_ladderDismountGoal;
CountdownTimer m_ladderTimer; // a "give up" timer if things go awry

// NEO: a ladder the engine put us on that our own path never asked for. Releasing it is not
// enough on its own - CGameMovement::LadderMove() re-grabs from the bot's wish direction, so
// the two toggle each other until something breaks the loop. See TraverseLadder().
bool HandleUnwantedLadder( void );
const CNavLadder *FindTouchedLadder( void ) const;
Vector GetIntoLadderFace( const CNavLadder *ladder ) const; // NEO: into the face of the ladder brush we hold
float m_unwantedLadderSince; // when the current bout of unwanted contact began
float m_unwantedLadderLastTouch; // last tick we were on a ladder we did not ask for

bool IsClimbPossible( INextBot *me, const CBaseEntity *obstacle ) const;
};

Expand Down
77 changes: 49 additions & 28 deletions src/game/server/neo/bot/behavior/neo_bot_ladder_approach.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ ActionResult<CNEOBot> CNEOBotLadderApproach::Update( CNEOBot *me, float )
// Are we climbing up or down the ladder?
Vector targetPos = m_bGoingUp ? m_ladder->m_bottom : m_ladder->m_top;

// Going down, head for where the bot will hang on the ladder: in front of its face, over the drop
if ( !m_bGoingUp )
{
targetPos += m_ladder->GetNormal() * ( body->GetHullWidth() * 0.5f + HANG_CLEARANCE );
}

// Calculate 2D vector from bot to ladder mount point
Vector2D to = ( targetPos - myPos ).AsVector2D();
float range = to.NormalizeInPlace();
Expand All @@ -99,6 +105,14 @@ ActionResult<CNEOBot> CNEOBotLadderApproach::Update( CNEOBot *me, float )
// If bot was looking up or down, sometimes the fast climb movement causes a detachment
Vector lookTarget = m_ladderCenter;
lookTarget.z = me->EyePosition().z;

// Going down, look down the ladder instead: the forward press that grabs it stays held for a few
// ticks, and with a level view it climbs the bot up and off the top
if ( !m_bGoingUp )
{
lookTarget = m_ladder->m_bottom;
}

body->AimHeadTowards( lookTarget, IBody::MANDATORY, 0.1f, nullptr, "Stare at ladder center" );

if ( me->IsDebugging( NEXTBOT_PATH ) )
Expand All @@ -107,8 +121,33 @@ ActionResult<CNEOBot> CNEOBotLadderApproach::Update( CNEOBot *me, float )
NDebugOverlay::Line( myPos, targetPos, 255, 255, 0, true, 0.1f );
}

// Are we aligned and close enough to mount the ladder?
if ( range >= MOUNT_RANGE )
// Going down, mount only once off the top: grabbed while still up there, on the landing or on the
// ladder's own top, the bot cannot press its way down, so it keeps walking out over the drop
const float flTopZ = m_ladder->m_top.z;
const bool bOnTop = !m_bGoingUp
&& ( myPos.z >= flTopZ || ( mover->IsOnGround() && myPos.z > flTopZ - mover->GetStepHeight() ) );

// Within mount range and on the ladder: start climbing
if ( range < MOUNT_RANGE && me->IsOnLadder() && !bOnTop )
{
if ( me->IsDebugging( NEXTBOT_PATH ) )
{
DevMsg( "%s: Starting ladder climb\n", me->GetDebugIdentifier() );
}

// Stop the bot before behavior transition to prevent falling off the ladder
// there can be a delay in the state change, so momentum can cause a fall
me->SetAbsVelocity( vec3_origin );
// ChangeTo: if something goes wrong during climb, reevaluate situation
return ChangeTo( new CNEOBotLadderClimb( m_ladder, m_bGoingUp ), "Mounting ladder" );
}

// Are we aligned and close enough to mount the ladder? Going down, line up only from in front of the
// face (at an angle the bot ends up on the floor beside the drop); from elsewhere walk straight on
const bool bAligned = dot < ALIGN_DOT_THRESHOLD;
const bool bLineUp = m_bGoingUp ? range >= MOUNT_RANGE
: ( dot < 0.0f && ( range >= MOUNT_RANGE || !bAligned ) );
if ( bLineUp )
{
// Perpendicular alignment line
Vector2D alignNormal = ladderNormal2D;
Expand All @@ -132,34 +171,16 @@ ActionResult<CNEOBot> CNEOBotLadderApproach::Update( CNEOBot *me, float )
NDebugOverlay::Cross3D( goal, 5.0f, 255, 0, 255, true, 0.1f );
}
}
else if ( m_bGoingUp ? bAligned : !bOnTop )
{
// Aligned (or going down and off the top), push forward to attach to the ladder
me->PressForwardButton();
mover->Approach( targetPos );
}
else
{
// Within mount range - check if aligned to start climbing
bool onLadder = me->IsOnLadder();
if ( onLadder )
{
if ( me->IsDebugging( NEXTBOT_PATH ) )
{
DevMsg( "%s: Starting ladder climb\n", me->GetDebugIdentifier() );
}

// Stop the bot before behavior transition to prevent falling off the ladder
// there can be a delay in the state change, so momentum can cause a fall
me->SetAbsVelocity( vec3_origin );
// ChangeTo: if something goes wrong during climb, reevaluate situation
return ChangeTo( new CNEOBotLadderClimb( m_ladder, m_bGoingUp ), "Mounting ladder" );
}
else if ( !m_bGoingUp || dot < ALIGN_DOT_THRESHOLD )
{
// Aligned (or going down), push forward to attach to the ladder
me->PressForwardButton();
mover->Approach( targetPos );
}
else
{
// Close but not aligned - continue approaching to align
mover->Approach( targetPos );
}
// Close but not aligned (or going down and still on the top) - continue approaching
mover->Approach( targetPos );
}

return Continue();
Expand Down
1 change: 1 addition & 0 deletions src/game/server/neo/bot/behavior/neo_bot_ladder_approach.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@ class CNEOBotLadderApproach : public Action<CNEOBot>
CountdownTimer m_timeoutTimer;

static constexpr float MOUNT_RANGE = 25.0f; // Distance to start climbing
static constexpr float HANG_CLEARANCE = 2.0f; // Gap between the face and a descending bot's hull as it mounts
static constexpr float ALIGN_DOT_THRESHOLD = -0.9f; // cos(~25 degrees) alignment tolerance
};
Loading
Loading