1111CNEOBotLadderApproach::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+ }
0 commit comments