Skip to content

Commit 2050ad9

Browse files
committed
Enhance bot ladder climbing reliability
- Leverage ladder climbing swim controls to decouple climbing with look direction - Movement helpers to help bots align with ladder and dismount
1 parent af9a685 commit 2050ad9

File tree

10 files changed

+614
-97
lines changed

10 files changed

+614
-97
lines changed

src/game/server/NextBot/Player/NextBotPlayer.h

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,12 @@ class INextBotPlayerInput
136136
virtual void ReleaseWalkButton( void ) = 0;
137137

138138
#ifdef NEO
139+
virtual void PressMoveUpButton( float duration = -1.0f ) = 0;
140+
virtual void ReleaseMoveUpButton( void ) = 0;
141+
142+
virtual void PressMoveDownButton( float duration = -1.0f ) = 0;
143+
virtual void ReleaseMoveDownButton( void ) = 0;
144+
139145
// This is just an "alias" to PressWalkButton
140146
virtual void PressRunButton( float duration = -1.0f ) = 0;
141147
virtual void ReleaseRunButton( void ) = 0;
@@ -240,6 +246,12 @@ class NextBotPlayer : public PlayerType, public INextBot, public INextBotPlayerI
240246
virtual void ReleaseWalkButton( void );
241247

242248
#ifdef NEO
249+
virtual void PressMoveUpButton( float duration = -1.0f );
250+
virtual void ReleaseMoveUpButton( void );
251+
252+
virtual void PressMoveDownButton( float duration = -1.0f );
253+
virtual void ReleaseMoveDownButton( void );
254+
243255
// This is just an "alias" to PressWalkButton
244256
virtual void PressRunButton( float duration = -1.0f );
245257
virtual void ReleaseRunButton( void );
@@ -292,6 +304,8 @@ class NextBotPlayer : public PlayerType, public INextBot, public INextBotPlayerI
292304
CountdownTimer m_walkButtonTimer;
293305
CountdownTimer m_buttonScaleTimer;
294306
#ifdef NEO
307+
CountdownTimer m_moveUpButtonTimer;
308+
CountdownTimer m_moveDownButtonTimer;
295309
CountdownTimer m_dropButtonTimer;
296310
CountdownTimer m_thermopticButtonTimer;
297311
CountdownTimer m_leanLeftButtonTimer;
@@ -515,6 +529,30 @@ inline void NextBotPlayer< PlayerType >::ReleaseWalkButton( void )
515529
}
516530

517531
#ifdef NEO
532+
template < typename PlayerType >
533+
inline void NextBotPlayer< PlayerType >::PressMoveUpButton( float duration )
534+
{
535+
m_moveUpButtonTimer.Start( duration );
536+
}
537+
538+
template < typename PlayerType >
539+
inline void NextBotPlayer< PlayerType >::ReleaseMoveUpButton( void )
540+
{
541+
m_moveUpButtonTimer.Invalidate();
542+
}
543+
544+
template < typename PlayerType >
545+
inline void NextBotPlayer< PlayerType >::PressMoveDownButton( float duration )
546+
{
547+
m_moveDownButtonTimer.Start( duration );
548+
}
549+
550+
template < typename PlayerType >
551+
inline void NextBotPlayer< PlayerType >::ReleaseMoveDownButton( void )
552+
{
553+
m_moveDownButtonTimer.Invalidate();
554+
}
555+
518556
template < typename PlayerType >
519557
inline void NextBotPlayer< PlayerType >::PressRunButton( float duration )
520558
{
@@ -663,6 +701,8 @@ inline void NextBotPlayer< PlayerType >::Spawn( void )
663701
m_forwardScale = m_rightScale = 0.04;
664702
m_burningTimer.Invalidate();
665703
#ifdef NEO
704+
m_moveUpButtonTimer.Invalidate();
705+
m_moveDownButtonTimer.Invalidate();
666706
m_dropButtonTimer.Invalidate();
667707
m_thermopticButtonTimer.Invalidate();
668708
m_leanLeftButtonTimer.Invalidate();
@@ -841,7 +881,25 @@ inline void NextBotPlayer< PlayerType >::PhysicsSimulate( void )
841881

842882
float forwardSpeed = 0.0f;
843883
float strafeSpeed = 0.0f;
884+
#ifdef NEO
885+
float verticalSpeed = 0.0f;
886+
887+
if ( !m_moveUpButtonTimer.IsElapsed() )
888+
{
889+
verticalSpeed = mover->GetRunSpeed();
890+
}
891+
else if ( !m_moveDownButtonTimer.IsElapsed() )
892+
{
893+
verticalSpeed = -mover->GetRunSpeed();
894+
}
895+
896+
if ( m_inputButtons & IN_JUMP )
897+
{
898+
verticalSpeed = mover->GetRunSpeed();
899+
}
900+
#else
844901
float verticalSpeed = ( m_inputButtons & IN_JUMP ) ? mover->GetRunSpeed() : 0.0f;
902+
#endif
845903

846904
if ( inputButtons & IN_FORWARD )
847905
{

src/game/server/neo/bot/behavior/neo_bot_attack.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
#pragma once
22

3+
#include "NextBotBehavior.h"
34
#include "Path/NextBotChasePath.h"
45

6+
class CNEOBot;
7+
58

69
//-------------------------------------------------------------------------------
710
class CNEOBotAttack : public Action< CNEOBot >

src/game/server/neo/bot/behavior/neo_bot_ladder_approach.cpp

Lines changed: 87 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
CNEOBotLadderApproach::CNEOBotLadderApproach( const CNavLadder *ladder, bool goingUp )
1212
: m_ladder( ladder ), m_bGoingUp( goingUp )
1313
{
14+
m_ladderCenter = ladder ? ( ladder->m_top + ladder->m_bottom ) * 0.5f : vec3_origin;
1415
}
1516

1617
//---------------------------------------------------------------------------------------------
@@ -32,20 +33,30 @@ ActionResult<CNEOBot> CNEOBotLadderApproach::OnStart( CNEOBot *me, Action<CNEOBo
3233
m_ladder->m_length );
3334
}
3435

36+
// Don't interfere with look direction
37+
// Can exit out of behavior if threat is present
38+
me->StopLookingAroundForEnemies();
39+
me->SetAttribute( CNEOBot::IGNORE_ENEMIES );
40+
3541
return Continue();
3642
}
3743

3844
//---------------------------------------------------------------------------------------------
3945
// Implementation based on ladder climbing implementation in https://github.com/Dragoteryx/drgbase/
40-
ActionResult<CNEOBot> CNEOBotLadderApproach::Update( CNEOBot *me, float interval )
46+
ActionResult<CNEOBot> CNEOBotLadderApproach::Update( CNEOBot *me, float )
4147
{
48+
if ( !m_ladder )
49+
{
50+
return Done( "Ladder is invalid" );
51+
}
52+
4253
if ( m_timeoutTimer.IsElapsed() )
4354
{
4455
return Done( "Ladder approach timeout" );
4556
}
4657

47-
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
48-
if ( threat && threat->IsVisibleRecently() )
58+
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat(true);
59+
if ( threat )
4960
{
5061
if ( me->IsDebugging( NEXTBOT_PATH ) )
5162
{
@@ -60,8 +71,20 @@ ActionResult<CNEOBot> CNEOBotLadderApproach::Update( CNEOBot *me, float interval
6071

6172
const Vector& myPos = mover->GetFeet();
6273

74+
// If we end up closer to exit point than entry point,
75+
// may not need to continue climb, exit to reevaluate situation
76+
Vector entryPos = m_bGoingUp ? m_ladder->m_bottom : m_ladder->m_top;
77+
Vector exitPos = m_bGoingUp ? m_ladder->m_top : m_ladder->m_bottom;
78+
float distToEntrySq = myPos.DistToSqr( entryPos );
79+
float distToExitSq = myPos.DistToSqr( exitPos );
80+
81+
if ( distToExitSq < distToEntrySq )
82+
{
83+
return Done( "Closer to ladder exit than entry, assuming goal reached accidentally" );
84+
}
85+
6386
// Are we climbing up or down the ladder?
64-
const Vector& targetPos = m_bGoingUp ? m_ladder->m_bottom : m_ladder->m_top;
87+
Vector targetPos = m_bGoingUp ? m_ladder->m_bottom : m_ladder->m_top;
6588

6689
// Calculate 2D vector from bot to ladder mount point
6790
Vector2D to = ( targetPos - myPos ).AsVector2D();
@@ -71,81 +94,97 @@ ActionResult<CNEOBot> CNEOBotLadderApproach::Update( CNEOBot *me, float interval
7194
Vector2D ladderNormal2D = m_ladder->GetNormal().AsVector2D();
7295
float dot = DotProduct2D( ladderNormal2D, to );
7396

97+
// Aim at the ladder center at eye level to carefully attach to ladder
98+
// Eye level in order to not accidentally move and detach between behavior transition
99+
// If bot was looking up or down, sometimes the fast climb movement causes a detachment
100+
Vector lookTarget = m_ladderCenter;
101+
lookTarget.z = me->EyePosition().z;
102+
body->AimHeadTowards( lookTarget, IBody::MANDATORY, 0.1f, nullptr, "Stare at ladder center" );
103+
74104
if ( me->IsDebugging( NEXTBOT_PATH ) )
75105
{
106+
NDebugOverlay::Cross3D( targetPos, 5.0f, 255, 255, 0, true, 0.1f );
76107
NDebugOverlay::Line( myPos, targetPos, 255, 255, 0, true, 0.1f );
77108
}
78109

79110
// Are we aligned and close enough to mount the ladder?
80-
if ( range >= ALIGN_RANGE )
81-
{
82-
// Far from ladder - just approach the target position
83-
body->AimHeadTowards( targetPos, IBody::CRITICAL, 0.5f, nullptr, "Moving toward ladder" );
84-
mover->Approach( targetPos );
85-
}
86-
else if ( range >= MOUNT_RANGE )
111+
if ( range >= MOUNT_RANGE )
87112
{
88-
// Within alignment range but not mount range - need to align with ladder
89-
if ( dot >= ALIGN_DOT_THRESHOLD )
113+
// Perpendicular alignment line
114+
Vector2D alignNormal = ladderNormal2D;
115+
if ( dot > 0.0f )
90116
{
91-
// Not aligned - rotate around the ladder to get aligned
92-
Vector2D myPerp( -to.y, to.x );
93-
Vector2D ladderPerp2D( -ladderNormal2D.y, ladderNormal2D.x );
94-
95-
Vector goal = targetPos;
96-
97-
// Calculate offset to circle around
98-
float alignRange = MOUNT_RANGE + (1.0f + dot) * (ALIGN_RANGE - MOUNT_RANGE);
99-
goal.x -= alignRange * to.x;
100-
goal.y -= alignRange * to.y;
101-
102-
// Choose direction to circle based on perpendicular alignment
103-
if ( DotProduct2D( to, ladderPerp2D ) < 0.0f )
104-
{
105-
goal.x += 10.0f * myPerp.x;
106-
goal.y += 10.0f * myPerp.y;
107-
}
108-
else
109-
{
110-
goal.x -= 10.0f * myPerp.x;
111-
goal.y -= 10.0f * myPerp.y;
112-
}
117+
alignNormal = -alignNormal; // Target behind ladder
118+
}
113119

114-
body->AimHeadTowards( goal, IBody::CRITICAL, 0.3f, nullptr, "Aligning with ladder" );
115-
mover->Approach( goal );
120+
Vector goal = targetPos;
121+
122+
// Pull the goal point outwards along the ladder's normal
123+
// to guide bot movement along approach
124+
float offsetDist = Clamp( range * 0.8f, 10.0f, ALIGN_RANGE );
125+
goal.x += alignNormal.x * offsetDist;
126+
goal.y += alignNormal.y * offsetDist;
116127

117-
if ( me->IsDebugging( NEXTBOT_PATH ) )
118-
{
119-
NDebugOverlay::Cross3D( goal, 5.0f, 255, 0, 255, true, 0.1f );
120-
}
121-
}
122-
else
128+
mover->Approach( goal );
129+
130+
if ( me->IsDebugging( NEXTBOT_PATH ) )
123131
{
124-
// Aligned - approach the ladder base directly
125-
body->AimHeadTowards( targetPos, IBody::CRITICAL, 0.3f, nullptr, "Approaching ladder" );
126-
mover->Approach( targetPos );
132+
NDebugOverlay::Cross3D( goal, 5.0f, 255, 0, 255, true, 0.1f );
127133
}
128134
}
129135
else
130136
{
131137
// Within mount range - check if aligned to start climbing
132-
if ( dot < ALIGN_DOT_THRESHOLD )
138+
bool onLadder = me->IsOnLadder();
139+
if ( onLadder )
133140
{
134141
if ( me->IsDebugging( NEXTBOT_PATH ) )
135142
{
136143
DevMsg( "%s: Starting ladder climb\n", me->GetDebugIdentifier() );
137144
}
138145

146+
// Stop the bot before behavior transition to prevent falling off the ladder
147+
// there can be a delay in the state change, so momentum can cause a fall
148+
me->SetAbsVelocity( vec3_origin );
139149
// ChangeTo: if something goes wrong during climb, reevaluate situation
140150
return ChangeTo( new CNEOBotLadderClimb( m_ladder, m_bGoingUp ), "Mounting ladder" );
141151
}
152+
else if ( !m_bGoingUp || dot < ALIGN_DOT_THRESHOLD )
153+
{
154+
// Aligned (or going down), push forward to attach to the ladder
155+
me->PressForwardButton();
156+
mover->Approach( targetPos );
157+
}
142158
else
143159
{
144160
// Close but not aligned - continue approaching to align
145-
body->AimHeadTowards( targetPos, IBody::CRITICAL, 0.3f, nullptr, "Approaching ladder" );
146161
mover->Approach( targetPos );
147162
}
148163
}
149164

150165
return Continue();
151166
}
167+
168+
//---------------------------------------------------------------------------------------------
169+
void CNEOBotLadderApproach::OnEnd( CNEOBot *me, Action<CNEOBot> *nextAction )
170+
{
171+
me->StartLookingAroundForEnemies();
172+
me->ClearAttribute( CNEOBot::IGNORE_ENEMIES );
173+
174+
if ( me->IsDebugging( NEXTBOT_PATH ) )
175+
{
176+
DevMsg( "%s: Finished ladder approach\n", me->GetDebugIdentifier() );
177+
}
178+
}
179+
180+
//---------------------------------------------------------------------------------------------
181+
ActionResult<CNEOBot> CNEOBotLadderApproach::OnSuspend( CNEOBot *me, Action<CNEOBot> *interruptingAction )
182+
{
183+
return Done( "OnSuspend: Cancel out of ladder approach, situation will likely become stale." );
184+
}
185+
186+
//---------------------------------------------------------------------------------------------
187+
ActionResult<CNEOBot> CNEOBotLadderApproach::OnResume( CNEOBot *me, Action<CNEOBot> *interruptingAction )
188+
{
189+
return Done( "OnResume: Cancel out of ladder approach, situation is likely stale." );
190+
}

src/game/server/neo/bot/behavior/neo_bot_ladder_approach.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,18 @@ class CNEOBotLadderApproach : public Action<CNEOBot>
2222

2323
virtual ActionResult<CNEOBot> OnStart( CNEOBot *me, Action<CNEOBot> *priorAction ) override;
2424
virtual ActionResult<CNEOBot> Update( CNEOBot *me, float interval ) override;
25+
virtual void OnEnd( CNEOBot *me, Action<CNEOBot> *nextAction ) override;
26+
virtual ActionResult<CNEOBot> OnSuspend( CNEOBot *me, Action<CNEOBot> *interruptingAction ) override;
27+
virtual ActionResult<CNEOBot> OnResume( CNEOBot *me, Action<CNEOBot> *interruptingAction ) override;
28+
29+
static constexpr float ALIGN_RANGE = 100.0f; // Distance to start alignment behavior
2530

2631
private:
2732
const CNavLadder *m_ladder;
2833
bool m_bGoingUp;
34+
Vector m_ladderCenter;
2935
CountdownTimer m_timeoutTimer;
3036

3137
static constexpr float MOUNT_RANGE = 25.0f; // Distance to start climbing
32-
static constexpr float ALIGN_RANGE = 50.0f; // Distance to start alignment behavior
3338
static constexpr float ALIGN_DOT_THRESHOLD = -0.9f; // cos(~25 degrees) alignment tolerance
3439
};

0 commit comments

Comments
 (0)