From df82a5586326c3ae09a7273273bac7f9f181c1d7 Mon Sep 17 00:00:00 2001 From: Alan Shen Date: Sun, 27 Sep 2026 19:10:00 -0600 Subject: [PATCH 1/3] Hold an exact move direction on PRECISE areas Bot movement presses the nearest of eight key combinations, up to 22.5 degrees off the goal, so a bot looking elsewhere drifts sideways - on a ledge marked NAV_MESH_PRECISE or NAV_MESH_CLIFF, off the edge. On those areas Approach() now hands the player the world direction to the goal. PhysicsSimulate() applies it on every tick until the next bot update, converted against that tick's view, so a bot turning to aim keeps its line. The stock SetButtonScale() does not do this: its 0.01 s timer lapses within a tick and it is relative to the view at update time. Design by sunmachine (review of NeotokyoRebuild/neo#2168). Draft: measured falls down (x0.79, x0.69), but stuck events on these areas up (x1.84, x1.42) as bots on a ledge push into each other. --- .../server/NextBot/Player/NextBotPlayer.h | 38 +++++++++++++++++++ .../Player/NextBotPlayerLocomotion.cpp | 10 +++++ 2 files changed, 48 insertions(+) diff --git a/src/game/server/NextBot/Player/NextBotPlayer.h b/src/game/server/NextBot/Player/NextBotPlayer.h index 092bdb266c..4bff7aba55 100644 --- a/src/game/server/NextBot/Player/NextBotPlayer.h +++ b/src/game/server/NextBot/Player/NextBotPlayer.h @@ -153,6 +153,10 @@ class INextBotPlayerInput #endif // NEO virtual void SetButtonScale( float forward, float right ) = 0; +#ifdef NEO + // Move in this world direction until the next bot update, whichever way the bot turns meanwhile + virtual void SetMoveDirection( const Vector2D &direction ) = 0; +#endif // NEO }; @@ -273,6 +277,9 @@ class NextBotPlayer : public PlayerType, public INextBot, public INextBotPlayerI #endif // NEO virtual void SetButtonScale( float forward, float right ); +#ifdef NEO + virtual void SetMoveDirection( const Vector2D &direction ); +#endif // NEO //------------------------------------------------------------------------ // Event hooks into NextBot system @@ -324,6 +331,10 @@ class NextBotPlayer : public PlayerType, public INextBot, public INextBotPlayerI IntervalTimer m_burningTimer; // how long since we were last burning float m_forwardScale; float m_rightScale; +#ifdef NEO + Vector2D m_moveDirection; + int m_moveDirectionTick; // the bot update that set m_moveDirection +#endif // NEO CHandle< CBaseEntity > m_spawnPointEntity; }; @@ -688,6 +699,15 @@ inline void NextBotPlayer< PlayerType >::SetButtonScale( float forward, float ri m_buttonScaleTimer.Start( 0.01 ); } +#ifdef NEO +template < typename PlayerType > +inline void NextBotPlayer< PlayerType >::SetMoveDirection( const Vector2D &direction ) +{ + m_moveDirection = direction; + m_moveDirectionTick = GetTickLastUpdate(); +} +#endif // NEO + //----------------------------------------------------------------------------------------------------- @@ -698,6 +718,10 @@ inline NextBotPlayer< PlayerType >::NextBotPlayer( void ) m_inputButtons = 0; m_burningTimer.Invalidate(); m_spawnPointEntity = NULL; +#ifdef NEO + m_moveDirection.Init(); + m_moveDirectionTick = -1; +#endif // NEO } @@ -738,6 +762,7 @@ inline void NextBotPlayer< PlayerType >::Spawn( void ) m_thermopticButtonTimer.Invalidate(); m_leanLeftButtonTimer.Invalidate(); m_leanRightButtonTimer.Invalidate(); + m_moveDirectionTick = -1; #endif // NEO // reset first, because Spawn() may access various interfaces @@ -996,6 +1021,19 @@ inline void NextBotPlayer< PlayerType >::PhysicsSimulate( void ) strafeSpeed = mover->GetRunSpeed() * m_rightScale; } +#ifdef NEO + // Held on every tick until the next update, taken against this tick's view so a bot turning to aim keeps its line + if ( m_moveDirectionTick == GetTickLastUpdate() ) + { + const float yaw = DEG2RAD( this->EyeAngles()[ YAW ] ); + const Vector2D forward( cosf( yaw ), sinf( yaw ) ); + const Vector2D right( forward.y, -forward.x ); + + forwardSpeed = mover->GetRunSpeed() * m_moveDirection.Dot( forward ); + strafeSpeed = mover->GetRunSpeed() * m_moveDirection.Dot( right ); + } +#endif // NEO + if ( !NextBotPlayerMove.GetBool() ) { inputButtons &= ~(IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT | IN_JUMP ); diff --git a/src/game/server/NextBot/Player/NextBotPlayerLocomotion.cpp b/src/game/server/NextBot/Player/NextBotPlayerLocomotion.cpp index 012a7afdbc..bd4b8e4a7b 100644 --- a/src/game/server/NextBot/Player/NextBotPlayerLocomotion.cpp +++ b/src/game/server/NextBot/Player/NextBotPlayerLocomotion.cpp @@ -536,6 +536,16 @@ void PlayerLocomotion::Approach( const Vector &pos, float goalWeight ) } } +#ifdef NEO + // The move keys below give the nearest of eight directions, which can drift the bot off the path + // and over a ledge, so on precise and cliff areas move straight at the goal instead. + const CNavArea *pArea = m_player->GetLastKnownArea(); + if ( goalDistance > epsilon && pArea && pArea->HasAttributes( NAV_MESH_PRECISE | NAV_MESH_CLIFF ) ) + { + playerButtons->SetMoveDirection( to ); + } +#endif + if ( ahead > epsilon ) { playerButtons->PressForwardButton(); From fd6c159320f08167ac95efcad1a365e4decbd9ac Mon Sep 17 00:00:00 2001 From: Alan Shen Date: Sun, 4 Oct 2026 21:30:00 -0600 Subject: [PATCH 2/3] Steer airborne velocity on PRECISE areas In the air, move input only adds speed along the move direction (air acceleration is capped per direction), so holding the direction to the goal does not stop a bot's sideways momentum. A bot that climbs or drops onto a narrow marked ledge carries its run-up speed past the edge. While airborne over a PRECISE or CLIFF area, the held move direction is now the difference between the velocity toward the goal and the current velocity, so air control cancels the sideways speed instead of adding to it. On the ground nothing changes. Measured on ntre_ridgeline_ctg's rail hop (a 3 u rail top and a crossbeam with 10 u of footing, both PRECISE), ghost pinned past it, 12 bots per class roster, default damage: bots that went off the hop fell from 24 % to 10 % of traversals (45 of 191 v 17 of 165), and falls from the rail top from 18 to 2. --- .../NextBot/Player/NextBotPlayerLocomotion.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/game/server/NextBot/Player/NextBotPlayerLocomotion.cpp b/src/game/server/NextBot/Player/NextBotPlayerLocomotion.cpp index bd4b8e4a7b..bc947c26fe 100644 --- a/src/game/server/NextBot/Player/NextBotPlayerLocomotion.cpp +++ b/src/game/server/NextBot/Player/NextBotPlayerLocomotion.cpp @@ -542,7 +542,17 @@ void PlayerLocomotion::Approach( const Vector &pos, float goalWeight ) const CNavArea *pArea = m_player->GetLastKnownArea(); if ( goalDistance > epsilon && pArea && pArea->HasAttributes( NAV_MESH_PRECISE | NAV_MESH_CLIFF ) ) { - playerButtons->SetMoveDirection( to ); + Vector2D moveDirection = to; + + // In the air, input only adds speed along the move direction, so a jump or drop onto a narrow ledge + // would carry its sideways speed past the edge: steer the velocity onto the goal line instead + if ( !IsOnGround() ) + { + moveDirection = GetRunSpeed() * to - m_player->GetAbsVelocity().AsVector2D(); + moveDirection.NormalizeInPlace(); + } + + playerButtons->SetMoveDirection( moveDirection ); } #endif From 0837917c9de87372836642df51290fd21b10316e Mon Sep 17 00:00:00 2001 From: Alan Shen Date: Sun, 4 Oct 2026 21:38:32 -0600 Subject: [PATCH 3/3] Split the airborne steering comment at a clause Comment-only: break the two lines at a clause boundary. --- src/game/server/NextBot/Player/NextBotPlayerLocomotion.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/game/server/NextBot/Player/NextBotPlayerLocomotion.cpp b/src/game/server/NextBot/Player/NextBotPlayerLocomotion.cpp index bc947c26fe..ed83abb3af 100644 --- a/src/game/server/NextBot/Player/NextBotPlayerLocomotion.cpp +++ b/src/game/server/NextBot/Player/NextBotPlayerLocomotion.cpp @@ -544,8 +544,8 @@ void PlayerLocomotion::Approach( const Vector &pos, float goalWeight ) { Vector2D moveDirection = to; - // In the air, input only adds speed along the move direction, so a jump or drop onto a narrow ledge - // would carry its sideways speed past the edge: steer the velocity onto the goal line instead + // In the air, input only adds speed along the move direction, + // so steer the velocity onto the goal line before a jump's sideways speed carries the bot past a narrow ledge if ( !IsOnGround() ) { moveDirection = GetRunSpeed() * to - m_player->GetAbsVelocity().AsVector2D();