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
3 changes: 3 additions & 0 deletions src/game/server/neo/bot/behavior/neo_bot_attack.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#pragma once

#include "NextBotBehavior.h"
#include "Path/NextBotChasePath.h"

class CNEOBot;


//-------------------------------------------------------------------------------
class CNEOBotAttack : public Action< CNEOBot >
Expand Down
37 changes: 34 additions & 3 deletions src/game/server/neo/bot/behavior/neo_bot_ladder_approach.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ ActionResult<CNEOBot> CNEOBotLadderApproach::OnStart( CNEOBot *me, Action<CNEOBo
}

// Timeout for approach phase
m_timeoutTimer.Start( 3.0f );
m_timeoutTimer.Start( 5.0f );

if ( me->IsDebugging( NEXTBOT_PATH ) )
{
Expand All @@ -32,6 +32,9 @@ ActionResult<CNEOBot> CNEOBotLadderApproach::OnStart( CNEOBot *me, Action<CNEOBo
m_ladder->m_length );
}

me->StopLookingAroundForEnemies();
me->SetAttribute( CNEOBot::IGNORE_ENEMIES );

return Continue();
}

Expand All @@ -44,7 +47,7 @@ ActionResult<CNEOBot> CNEOBotLadderApproach::Update( CNEOBot *me, float interval
return Done( "Ladder approach timeout" );
}

const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat(true);
if ( threat && threat->IsVisibleRecently() )
{
if ( me->IsDebugging( NEXTBOT_PATH ) )
Expand Down Expand Up @@ -129,7 +132,11 @@ ActionResult<CNEOBot> CNEOBotLadderApproach::Update( CNEOBot *me, float interval
else
{
// Within mount range - check if aligned to start climbing
if ( dot < ALIGN_DOT_THRESHOLD )
bool onLadder = ( me->GetMoveType() == MOVETYPE_LADDER ) ||
mover->IsUsingLadder() ||
mover->IsAscendingOrDescendingLadder();

if ( onLadder || dot < ALIGN_DOT_THRESHOLD )
{
if ( me->IsDebugging( NEXTBOT_PATH ) )
{
Expand All @@ -149,3 +156,27 @@ ActionResult<CNEOBot> CNEOBotLadderApproach::Update( CNEOBot *me, float interval

return Continue();
}

//---------------------------------------------------------------------------------------------
void CNEOBotLadderApproach::OnEnd( CNEOBot *me, Action<CNEOBot> *nextAction )
{
me->StartLookingAroundForEnemies();
me->ClearAttribute( CNEOBot::IGNORE_ENEMIES );

if ( me->IsDebugging( NEXTBOT_PATH ) )
{
DevMsg( "%s: Finished ladder approach\n", me->GetDebugIdentifier() );
}
}

//---------------------------------------------------------------------------------------------
ActionResult<CNEOBot> CNEOBotLadderApproach::OnSuspend( CNEOBot *me, Action<CNEOBot> *interruptingAction )
{
return Done( "OnSuspend: Cancel out of ladder approach, situation will likely become stale." );
}

//---------------------------------------------------------------------------------------------
ActionResult<CNEOBot> CNEOBotLadderApproach::OnResume( CNEOBot *me, Action<CNEOBot> *interruptingAction )
{
return Done( "OnResume: Cancel out of ladder approach, situation is likely stale." );
}
3 changes: 3 additions & 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 @@ -22,6 +22,9 @@ class CNEOBotLadderApproach : public Action<CNEOBot>

virtual ActionResult<CNEOBot> OnStart( CNEOBot *me, Action<CNEOBot> *priorAction ) override;
virtual ActionResult<CNEOBot> Update( CNEOBot *me, float interval ) override;
virtual void OnEnd( CNEOBot *me, Action<CNEOBot> *nextAction ) override;
virtual ActionResult<CNEOBot> OnSuspend( CNEOBot *me, Action<CNEOBot> *interruptingAction ) override;
virtual ActionResult<CNEOBot> OnResume( CNEOBot *me, Action<CNEOBot> *interruptingAction ) override;

private:
const CNavLadder *m_ladder;
Expand Down
228 changes: 207 additions & 21 deletions src/game/server/neo/bot/behavior/neo_bot_ladder_climb.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
#include "cbase.h"
#include "bot/behavior/neo_bot_attack.h"
#include "bot/behavior/neo_bot_ladder_climb.h"
#include "nav_ladder.h"
#include "NextBot/Path/NextBotPathFollow.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

//---------------------------------------------------------------------------------------------
CNEOBotLadderClimb::CNEOBotLadderClimb( const CNavLadder *ladder, bool goingUp )
: m_ladder( ladder ), m_bGoingUp( goingUp ), m_bHasBeenOnLadder( false )
: m_ladder( ladder ), m_bGoingUp( goingUp ), m_bHasBeenOnLadder( false ),
m_flLastZ( 0.0f ), m_flHighestZ( -FLT_MAX ), m_bHasLeftGround( false ),
m_bDismountPhase( false ), m_pExitArea( nullptr )
{
}

Expand All @@ -21,13 +25,20 @@ ActionResult<CNEOBot> CNEOBotLadderClimb::OnStart( CNEOBot *me, Action<CNEOBot>

// Ignore enemies while climbing
me->StopLookingAroundForEnemies();
me->SetAttribute( CNEOBot::IGNORE_ENEMIES );

// Timeout based on ladder length
float estimatedClimbTime = m_ladder->m_length / MAX_CLIMB_SPEED + 1.0f;
float estimatedClimbTime = m_ladder->m_length / MAX_CLIMB_SPEED + 2.0f;
m_timeoutTimer.Start( estimatedClimbTime );

m_bHasBeenOnLadder = false;

ILocomotion *mover = me->GetLocomotionInterface();
m_flLastZ = mover->GetFeet().z;
m_flHighestZ = m_flLastZ;
m_bHasLeftGround = !mover->GetGround();
m_stuckTimer.Start( STUCK_CHECK_INTERVAL );

if ( me->IsDebugging( NEXTBOT_PATH ) )
{
DevMsg( "%s: Starting ladder climb (%s), length %.1f\n",
Expand All @@ -39,6 +50,36 @@ ActionResult<CNEOBot> CNEOBotLadderClimb::OnStart( CNEOBot *me, Action<CNEOBot>
return Continue();
}

//---------------------------------------------------------------------------------------------
void CNEOBotLadderClimb::EnterDismountPhase( CNEOBot *me )
{
m_bDismountPhase = true;
m_dismountTimer.Start( DISMOUNT_TIMEOUT );

// Try to resolve the exit area from the current path
const PathFollower *path = me->GetCurrentPath();
if ( path && path->IsValid() )
{
const Path::Segment *seg = path->GetCurrentGoal();
// Walk forward past any ladder segments to find the ground exit
while ( seg && seg->ladder )
{
seg = path->NextSegment( seg );
}
if ( seg && seg->area )
{
m_pExitArea = seg->area;
}
}

if ( me->IsDebugging( NEXTBOT_PATH ) )
{
DevMsg( "%s: Entering dismount phase (exit area %s)\n",
me->GetDebugIdentifier(),
m_pExitArea ? "found" : "NOT found" );
}
}

//---------------------------------------------------------------------------------------------
// Implementation based on ladder climbing logic in https://github.com/Dragoteryx/drgbase/
ActionResult<CNEOBot> CNEOBotLadderClimb::Update( CNEOBot *me, float interval )
Expand All @@ -48,35 +89,179 @@ ActionResult<CNEOBot> CNEOBotLadderClimb::Update( CNEOBot *me, float interval )
return Done( "Ladder climb timeout" );
}

const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat(true);
if ( threat && threat->IsVisibleRecently() )
{
if ( me->IsDebugging( NEXTBOT_PATH ) )
{
DevMsg( "%s: Threat detected during ladder approach - engaging\n", me->GetDebugIdentifier() );
}
// ChangeTo: We may move away from ladder when fighting, so don't want to get stuck in ladder climb behavior
return ChangeTo( new CNEOBotAttack, "Interrupting climb to engage enemy" );
}

ILocomotion *mover = me->GetLocomotionInterface();
IBody *body = me->GetBodyInterface();

// Check if we're on the ladder (MOVETYPE_LADDER or locomotion says so)
bool onLadder = ( me->GetMoveType() == MOVETYPE_LADDER ) ||
mover->IsUsingLadder() ||
mover->IsAscendingOrDescendingLadder();
// Check if we're on the ground
bool onGround = ( mover->GetGround() != nullptr );

//------------------------------------------------------------
// Dismount phase: we've left the ladder, walk to exit NavArea
//------------------------------------------------------------
if ( m_bDismountPhase )
{
// Done: reached the target NavArea
if ( m_pExitArea && me->GetLastKnownArea() == m_pExitArea )
{
return Done( "Reached next NavArea after dismount" );
}

// Safety timeout
if ( m_dismountTimer.IsElapsed() )
{
return Done( "Dismount walk timed out" );
}

// Build look target toward exit area center with vertical bias preserved
const Vector& myPos = mover->GetFeet();
if ( m_pExitArea )
{
Vector exitCenter = m_pExitArea->GetCenter();

// Flatten the direction to horizontal, then re-add vertical look bias
Vector dir = exitCenter - myPos;
dir.z = 0.0f;

Vector lookTarget = myPos + dir;
if ( m_bGoingUp )
{
lookTarget.z += 64.0f; // Look slightly upward while dismounting up
}
else
{
lookTarget.z -= 64.0f; // Look slightly downward while dismounting down
}

body->AimHeadTowards( lookTarget, IBody::MANDATORY, 0.1f, nullptr, "Walking to exit area" );
mover->Approach( exitCenter, 9999999.9f );

if ( me->IsDebugging( NEXTBOT_PATH ) )
{
NDebugOverlay::Line( myPos, exitCenter, 0, 255, 255, true, 0.1f );
}
}
else
{
// No exit area resolved - just push forward along the ladder normal
Vector pushDir = -m_ladder->GetNormal();
Vector pushTarget = myPos + 100.0f * pushDir;
body->AimHeadTowards( pushTarget, IBody::MANDATORY, 0.1f, nullptr, "Dismount push forward" );
mover->Approach( pushTarget, 9999999.9f );
}

return Continue();
}

//------------------------------------------------------------
// Normal ladder climbing phase
//------------------------------------------------------------
const Vector& myPos = mover->GetFeet();
float currentZ = myPos.z;
float targetZ = m_bGoingUp ? m_ladder->m_top.z : m_ladder->m_bottom.z;

bool inAir = !onGround;
if ( inAir )
{
m_bHasLeftGround = true;
}

bool onLadder = ( me->GetMoveType() == MOVETYPE_LADDER ) ||
mover->IsUsingLadder() ||
mover->IsAscendingOrDescendingLadder() ||
inAir;

if ( onLadder )
{
m_bHasBeenOnLadder = true;
}
else if ( m_bHasBeenOnLadder )

// Track peak height for progress detection
if ( currentZ > m_flHighestZ )
{
// We were on the ladder but got knocked off - return to reevaluate situation
// Since ladder approach should use ChangeTo climbing behavior, this Done will return to the state BEFORE ladder approach
return Done( "Knocked off ladder - reevaluating situation" );
m_flHighestZ = currentZ;
}

// Get current position and target
const Vector& myPos = mover->GetFeet();
float currentZ = myPos.z;
float targetZ = m_bGoingUp ? m_ladder->m_top.z : m_ladder->m_bottom.z;
// Stuck detection: if we haven't made vertical progress, bail out gracefully
if ( m_stuckTimer.IsElapsed() )
{
float verticalDelta = fabsf( currentZ - m_flLastZ );
if ( verticalDelta < STUCK_Z_TOLERANCE )
{
// No vertical progress - if we're close enough to the target, consider it done
float distToTarget = fabsf( currentZ - targetZ );
if ( distToTarget < mover->GetStepHeight() * 2.0f )
{
EnterDismountPhase( me );
}

if ( me->IsDebugging( NEXTBOT_PATH ) )
{
DevMsg( "%s: Ladder climb stuck - no vertical progress (delta %.1f)\n",
me->GetDebugIdentifier(), verticalDelta );
}
}
m_flLastZ = currentZ;
m_stuckTimer.Start( STUCK_CHECK_INTERVAL );
}

if ( m_bGoingUp )
{
if ( currentZ >= targetZ - mover->GetStepHeight() )
body->SetDesiredPosture( IBody::STAND );

// Check if we've reached the top - transition to dismount instead of exiting
if ( currentZ >= targetZ - mover->GetStepHeight() && onGround )
{
return Done( "Reached top of ladder" );
EnterDismountPhase( me );
return Continue();
}

// Near the top of the ladder: try to push toward the navmesh exit area
// This handles cases where the exit is behind or offset from the ladder normal
float distToTop = targetZ - currentZ;
if ( distToTop <= DISMOUNT_PUSH_DISTANCE && m_bHasBeenOnLadder )
{
// Try to find the dismount area from the current path
const PathFollower *path = me->GetCurrentPath();
if ( path && path->IsValid() )
{
const Path::Segment *goal = path->GetCurrentGoal();
if ( goal )
{
// Find the next non-ladder segment (the exit area)
const Path::Segment *exitSeg = goal;
while ( exitSeg && exitSeg->ladder )
{
exitSeg = path->NextSegment( exitSeg );
}

if ( exitSeg && exitSeg->area )
{
// Push toward the exit area center instead of just the ladder normal
Vector exitPos = exitSeg->area->GetCenter();
exitPos.z = Max( exitPos.z, currentZ );
body->AimHeadTowards( exitPos, IBody::MANDATORY, 0.1f, nullptr, "Dismounting ladder toward exit" );
mover->Approach( exitPos, 9999999.9f );

if ( me->IsDebugging( NEXTBOT_PATH ) )
{
NDebugOverlay::Line( myPos, exitPos, 0, 255, 0, true, 0.1f );
}

return Continue();
}
}
}
}

// Climb up: look up and push into ladder
Expand All @@ -86,9 +271,11 @@ ActionResult<CNEOBot> CNEOBotLadderClimb::Update( CNEOBot *me, float interval )
}
else
{
// Check if we've reached the bottom - transition to dismount instead of exiting
if ( currentZ <= targetZ + mover->GetStepHeight() )
{
return Done( "Reached bottom of ladder" );
EnterDismountPhase( me );
return Continue();
}

// Climb down: Stare at bottom of ladder while moving forward to it
Expand All @@ -104,6 +291,7 @@ ActionResult<CNEOBot> CNEOBotLadderClimb::Update( CNEOBot *me, float interval )
void CNEOBotLadderClimb::OnEnd( CNEOBot *me, Action<CNEOBot> *nextAction )
{
me->StartLookingAroundForEnemies();
me->ClearAttribute( CNEOBot::IGNORE_ENEMIES );

if ( me->IsDebugging( NEXTBOT_PATH ) )
{
Expand All @@ -114,13 +302,11 @@ void CNEOBotLadderClimb::OnEnd( CNEOBot *me, Action<CNEOBot> *nextAction )
//---------------------------------------------------------------------------------------------
ActionResult<CNEOBot> CNEOBotLadderClimb::OnSuspend( CNEOBot *me, Action<CNEOBot> *interruptingAction )
{
me->StartLookingAroundForEnemies();
return Continue();
return Done( "OnSuspend: Cancel out of ladder climb, situation will likely become stale." );
}

//---------------------------------------------------------------------------------------------
ActionResult<CNEOBot> CNEOBotLadderClimb::OnResume( CNEOBot *me, Action<CNEOBot> *interruptingAction )
{
me->StopLookingAroundForEnemies();
return Continue();
return Done( "OnResume: Cancel out of ladder climb, situation is likely stale." );
}
Loading