From 086ffc13baabd5e19d1be9bfbb05656e7585415c Mon Sep 17 00:00:00 2001 From: Alan Shen Date: Sat, 26 Sep 2026 19:14:00 -0600 Subject: [PATCH 1/2] Bots walk NAV_MESH_PRECISE areas exactly On an area marked NAV_MESH_PRECISE or NAV_MESH_CLIFF: - the path follower does not skip past a point that enters a PRECISE area, as the path builder already does not - move input follows the exact direction to the goal instead of the nearest of eight key combinations - no combat dodge, no close-range circle-strafe and no stepping away from teammates, any of which can carry a bot off a ledge No mesh on master marks PRECISE or CLIFF today. --- .../server/NextBot/Path/NextBotPathFollow.cpp | 7 +++++++ .../NextBot/Player/NextBotPlayerLocomotion.cpp | 9 ++++++++- .../server/neo/bot/behavior/neo_bot_attack.cpp | 17 ++++++++++------- .../neo/bot/behavior/neo_bot_behavior.cpp | 4 ++++ .../bot/behavior/neo_bot_tactical_monitor.cpp | 6 ++++++ src/game/server/neo/bot/neo_bot.cpp | 8 ++++++++ src/game/server/neo/bot/neo_bot.h | 1 + 7 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/game/server/NextBot/Path/NextBotPathFollow.cpp b/src/game/server/NextBot/Path/NextBotPathFollow.cpp index eff4ba5e33..7f65ca0078 100644 --- a/src/game/server/NextBot/Path/NextBotPathFollow.cpp +++ b/src/game/server/NextBot/Path/NextBotPathFollow.cpp @@ -474,6 +474,13 @@ bool PathFollower::CheckProgress( INextBot *bot ) { if ( ( pSkipToGoal->pos - myFeet ).IsLengthLessThan( m_minLookAheadRange ) ) { +#ifdef NEO + if ( pSkipToGoal->area && pSkipToGoal->area->HasAttributes( NAV_MESH_PRECISE ) ) + { + // don't skip segments or take shortcuts in a PRECISE area + break; + } +#endif // goal is too close - step to next segment const Path::Segment *nextSegment = NextSegment( pSkipToGoal ); diff --git a/src/game/server/NextBot/Player/NextBotPlayerLocomotion.cpp b/src/game/server/NextBot/Player/NextBotPlayerLocomotion.cpp index 012a7afdbc..109aa219f1 100644 --- a/src/game/server/NextBot/Player/NextBotPlayerLocomotion.cpp +++ b/src/game/server/NextBot/Player/NextBotPlayerLocomotion.cpp @@ -528,7 +528,14 @@ void PlayerLocomotion::Approach( const Vector &pos, float goalWeight ) else { const float epsilon = 0.25f; - if ( NextBotPlayerMoveDirect.GetBool() ) + bool bMoveDirect = NextBotPlayerMoveDirect.GetBool(); +#ifdef NEO + // The move keys below often lead to the bot drifting off the path a few degrees off the goal + // which can lead to fall deaths, so use direct movemenet in precise and cliff areas. + const CNavArea *pArea = m_player->GetLastKnownArea(); + bMoveDirect |= pArea && pArea->HasAttributes( NAV_MESH_PRECISE | NAV_MESH_CLIFF ); +#endif + if ( bMoveDirect ) { if ( goalDistance > epsilon ) { diff --git a/src/game/server/neo/bot/behavior/neo_bot_attack.cpp b/src/game/server/neo/bot/behavior/neo_bot_attack.cpp index fa46e0c05b..f663476874 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_attack.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_attack.cpp @@ -291,14 +291,17 @@ ActionResult< CNEOBot > CNEOBotAttack::Update( CNEOBot *me, float interval ) if (!m_attackCoverArea // don't slow movement to cover with strafing && isUsingCloseRangeWeapon && threat->IsVisibleRecently() && me->IsRangeLessThan( threatLastKnownPos, 1.1f * me->GetDesiredAttackRange() ) ) { - // circle around our victim - if ( me->TransientlyConsistentRandomValue( 3.0f ) < 0.5f ) + // circle around our victim, unless a sidestep could take us off a ledge + if ( !me->IsOnPreciseArea() ) { - me->PressLeftButton(); - } - else - { - me->PressRightButton(); + if ( me->TransientlyConsistentRandomValue( 3.0f ) < 0.5f ) + { + me->PressLeftButton(); + } + else + { + me->PressRightButton(); + } } } diff --git a/src/game/server/neo/bot/behavior/neo_bot_behavior.cpp b/src/game/server/neo/bot/behavior/neo_bot_behavior.cpp index 4612059324..37969a26a5 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_behavior.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_behavior.cpp @@ -1236,6 +1236,10 @@ void CNEOBotMainAction::Dodge( CNEOBot *me ) if ( me->HasAttribute( CNEOBot::DISABLE_DODGE ) ) return; + // a sidestep on a ledge or beside a drop can walk the bot off it + if ( me->IsOnPreciseArea() ) + return; + // don't waste time doding if we're in a hurry if ( me->GetIntentionInterface()->ShouldHurry( me ) == ANSWER_YES ) return; diff --git a/src/game/server/neo/bot/behavior/neo_bot_tactical_monitor.cpp b/src/game/server/neo/bot/behavior/neo_bot_tactical_monitor.cpp index e0f1cfffca..bcc327e9a4 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_tactical_monitor.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_tactical_monitor.cpp @@ -220,6 +220,12 @@ void CNEOBotTacticalMonitor::AvoidBumpingFriends( CNEOBot *me ) return; } + // stepping straight away from a teammate can step off a ledge + if ( me->IsOnPreciseArea() ) + { + return; + } + CUtlVector< CNEO_Player * > friendVector; CollectPlayers( &friendVector, me->GetTeamNumber(), COLLECT_ONLY_LIVING_PLAYERS ); diff --git a/src/game/server/neo/bot/neo_bot.cpp b/src/game/server/neo/bot/neo_bot.cpp index 7d0e4d4ea5..0f53be5d8c 100644 --- a/src/game/server/neo/bot/neo_bot.cpp +++ b/src/game/server/neo/bot/neo_bot.cpp @@ -2876,6 +2876,14 @@ bool CNEOBot::IsEnemy(const CBaseEntity* them) const } +// For disabling erratic movements like strafing where one wrong step could move a bot off a cliff +// Also useful for navigating areas that require precision to navigate like obstructed pathways +bool CNEOBot::IsOnPreciseArea() const +{ + const CNavArea *area = GetLastKnownArea(); + return area && area->HasAttributes( NAV_MESH_PRECISE | NAV_MESH_CLIFF ); +} + bool CNEOBot::IsBotOnLadder() const { ILocomotion* mover = GetLocomotionInterface(); diff --git a/src/game/server/neo/bot/neo_bot.h b/src/game/server/neo/bot/neo_bot.h index 4f9716b9d4..2124346bcd 100644 --- a/src/game/server/neo/bot/neo_bot.h +++ b/src/game/server/neo/bot/neo_bot.h @@ -187,6 +187,7 @@ class CNEOBot : public NextBotPlayer< CNEO_Player >, public CGameEventListener bool IsEnemy(const CBaseEntity* them) const OVERRIDE; bool IsBotOnLadder( ) const; + bool IsOnPreciseArea() const; // on a NAV_MESH_PRECISE or NAV_MESH_CLIFF area, where the path must be walked exactly CNEOBaseCombatWeapon* GetBludgeonWeapon(void); From 12e3b9cba442f60117006b4221d1a52e41a326b6 Mon Sep 17 00:00:00 2001 From: Alan Shen Date: Sun, 27 Sep 2026 02:40:00 -0600 Subject: [PATCH 2/2] Drop the exact move direction on PRECISE areas Review (sunmachine) showed the button scale it relied on lapses within a tick, and bot updates run every 6-13 ticks, so on most ticks the bot still moved on the eight-direction keys. Their design - hold the world direction until the next bot update and convert it against the view every tick - was measured against this branch without any move-input change: fall deaths x0.79 and x0.69 in two batches, but stuck events on PRECISE / CLIFF areas x1.84 and x1.42, with bots on a ledge now pushing into each other instead of sliding past. That trade needs more work, so it moves to its own draft PR and this one keeps the waypoint and the no-sidestep rules. --- .../server/NextBot/Player/NextBotPlayerLocomotion.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/game/server/NextBot/Player/NextBotPlayerLocomotion.cpp b/src/game/server/NextBot/Player/NextBotPlayerLocomotion.cpp index 109aa219f1..012a7afdbc 100644 --- a/src/game/server/NextBot/Player/NextBotPlayerLocomotion.cpp +++ b/src/game/server/NextBot/Player/NextBotPlayerLocomotion.cpp @@ -528,14 +528,7 @@ void PlayerLocomotion::Approach( const Vector &pos, float goalWeight ) else { const float epsilon = 0.25f; - bool bMoveDirect = NextBotPlayerMoveDirect.GetBool(); -#ifdef NEO - // The move keys below often lead to the bot drifting off the path a few degrees off the goal - // which can lead to fall deaths, so use direct movemenet in precise and cliff areas. - const CNavArea *pArea = m_player->GetLastKnownArea(); - bMoveDirect |= pArea && pArea->HasAttributes( NAV_MESH_PRECISE | NAV_MESH_CLIFF ); -#endif - if ( bMoveDirect ) + if ( NextBotPlayerMoveDirect.GetBool() ) { if ( goalDistance > epsilon ) {