From fe67cd1d09147eec92b9903cdad7bedfababeb73 Mon Sep 17 00:00:00 2001 From: Alan Shen Date: Sat, 11 Jul 2026 02:25:26 -0600 Subject: [PATCH 1/2] Bots avoid hazard areas Bots avoid hazard areas: - areas that are in line of sight of a smoke cloud - areas that are in line of sight of a grenade trajectory - areas where a teammate recently died --- src/game/server/CMakeLists.txt | 1 + .../server/NextBot/NextBotVisionInterface.cpp | 17 ++ .../neo/bot/behavior/neo_bot_attack.cpp | 14 +- .../neo/bot/behavior/neo_bot_behavior.cpp | 20 +- .../neo/bot/behavior/neo_bot_ctg_carrier.cpp | 5 + .../neo/bot/behavior/neo_bot_ctg_enemy.cpp | 2 + .../neo/bot/behavior/neo_bot_ctg_escort.cpp | 2 + .../bot/behavior/neo_bot_ctg_lone_wolf.cpp | 8 + .../neo/bot/behavior/neo_bot_ctg_lone_wolf.h | 1 + .../neo/bot/behavior/neo_bot_get_ammo.cpp | 2 + .../neo/bot/behavior/neo_bot_get_health.cpp | 2 + .../behavior/neo_bot_grenade_throw_frag.cpp | 11 + .../neo/bot/behavior/neo_bot_jgr_enemy.cpp | 2 + .../neo/bot/behavior/neo_bot_jgr_escort.cpp | 2 + .../bot/behavior/neo_bot_jgr_juggernaut.cpp | 1 + .../neo_bot_move_to_vantage_point.cpp | 1 + .../behavior/neo_bot_retreat_from_grenade.cpp | 33 +++ .../neo_bot_retreat_from_hazard_area.cpp | 131 ++++++++++ .../neo_bot_retreat_from_hazard_area.h | 27 ++ .../bot/behavior/neo_bot_retreat_to_cover.cpp | 19 ++ .../bot/behavior/neo_bot_seek_and_destroy.cpp | 2 + .../neo/bot/behavior/neo_bot_seek_weapon.cpp | 2 + .../bot/behavior/neo_bot_tactical_monitor.cpp | 16 ++ .../bot/behavior/neo_bot_tactical_monitor.h | 1 + src/game/server/neo/bot/neo_bot_path_cost.cpp | 246 +++++++++--------- .../neo/bot/neo_bot_path_reservation.cpp | 178 +++++++++++++ .../server/neo/bot/neo_bot_path_reservation.h | 13 + 27 files changed, 626 insertions(+), 133 deletions(-) create mode 100644 src/game/server/neo/bot/behavior/neo_bot_retreat_from_hazard_area.cpp create mode 100644 src/game/server/neo/bot/behavior/neo_bot_retreat_from_hazard_area.h diff --git a/src/game/server/CMakeLists.txt b/src/game/server/CMakeLists.txt index 5f9434ff9a..e37fec41dc 100644 --- a/src/game/server/CMakeLists.txt +++ b/src/game/server/CMakeLists.txt @@ -1541,6 +1541,7 @@ set(UNITY_SOURCE_NEO_BOT neo/bot/behavior/neo_bot_pause.cpp neo/bot/behavior/neo_bot_retreat_to_cover.cpp neo/bot/behavior/neo_bot_retreat_from_grenade.cpp + neo/bot/behavior/neo_bot_retreat_from_hazard_area.cpp neo/bot/behavior/neo_bot_scenario_monitor.cpp neo/bot/behavior/neo_bot_seek_and_destroy.cpp neo/bot/behavior/neo_bot_seek_weapon.cpp diff --git a/src/game/server/NextBot/NextBotVisionInterface.cpp b/src/game/server/NextBot/NextBotVisionInterface.cpp index 9523fbdfbe..e98972d28c 100644 --- a/src/game/server/NextBot/NextBotVisionInterface.cpp +++ b/src/game/server/NextBot/NextBotVisionInterface.cpp @@ -21,6 +21,8 @@ #include "neo_player.h" #include "neo_smokelineofsightblocker.h" #include "bot/neo_bot.h" +#include "bot/neo_bot_path_reservation.h" +#include "nav_mesh.h" #endif #include "tier0/vprof.h" @@ -794,6 +796,21 @@ bool IVision::IsLineOfSightClearToEntity( const CBaseEntity *subject, Vector *vi UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), firstPosition, MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result ); if ( result.DidHit() ) { + // Check if first center traceline hit any blocking smoke + if (neo_bot_path_reservation_enable.GetBool() + && result.m_pEnt + && FStrEq(result.m_pEnt->GetClassname(), SMOKELINEOFSIGHTBLOCKER_ENTITYNAME)) + { + auto pSmoke = static_cast(result.m_pEnt); + CNavArea *area = TheNavMesh->GetNearestNavArea(pSmoke->GetAbsOrigin()); + if (area && neoBot) + { + // Register smoke sightlines for team to avoid as hazardous + int team = neoBot->GetTeamNumber(); + CNEOBotPathReservations()->AddSmokeHazard(area->GetID(), pSmoke->GetNextThink(), team); + } + } + UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), secondPosition, MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result ); if ( result.DidHit() ) 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 cc13574b3e..e8580da508 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_attack.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_attack.cpp @@ -79,10 +79,18 @@ class CSearchForAttackCover : public ISearchSurroundingAreasFunctor return true; // skip our starting area } - if ( neo_bot_path_reservation_enable.GetBool() && - ( CNEOBotPathReservations()->GetAreaAvoidPenalty(area->GetID()) > 0 ) ) + // Skip areas that are hazardous or where bots get stuck + if ( neo_bot_path_reservation_enable.GetBool() ) { - return true; // skip areas that have had navigation hiccups + int navAreaId = area->GetID(); + if (CNEOBotPathReservations()->IsAreaHazardous(navAreaId, m_me)) + { + return true; + } + if (CNEOBotPathReservations()->GetAreaAvoidPenalty(navAreaId) != 0.0f) + { + return true; + } } if ( m_attackCoverArea ) 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 543bb4aa88..0c040a36ed 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_behavior.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_behavior.cpp @@ -17,6 +17,8 @@ #include "bot/behavior/neo_bot_tactical_monitor.h" #include "weapons/weapon_balc.h" +extern ConVar sv_neo_grenade_fuse_timer; + ConVar neo_bot_path_lookahead_range( "neo_bot_path_lookahead_range", "300" ); ConVar neo_bot_sniper_aim_error( "neo_bot_sniper_aim_error", "0.01", FCVAR_CHEAT ); ConVar neo_bot_sniper_aim_steady_rate( "neo_bot_sniper_aim_steady_rate", "10", FCVAR_CHEAT ); @@ -195,16 +197,14 @@ EventDesiredResult CNEOBotMainAction::OnKilled( CNEOBot *me, const CTak // Intended to add some variance to pathing for similar starting scenarios if ( const CNavArea *navArea = me->GetLastKnownArea() ) { - CNEOBotPathReservations()->IncrementAreaAvoidPenalty( navArea->GetID(), neo_bot_path_reservation_killed_penalty.GetFloat() ); - } - else - { - // Fallback if GetLastKnownArea is null, try finding nearest nav area - CNavArea *nearestArea = TheNavMesh->GetNearestNavArea( me->GetAbsOrigin() ); - if ( nearestArea ) - { - CNEOBotPathReservations()->IncrementAreaAvoidPenalty( nearestArea->GetID(), neo_bot_path_reservation_killed_penalty.GetFloat() ); - } + int areaId = navArea->GetID(); + int teamId = me->GetTeamNumber(); + CNEOBotPathReservations()->IncrementAreaAvoidPenalty( areaId, neo_bot_path_reservation_killed_penalty.GetFloat() ); + + // Discourage friendlies from stepping out into recent danger zone + // Use grenade fuse timer so that we don't need to compare expiration times with previous entries + // where the frag grenade timer is the usual time input into AddFragHazard + CNEOBotPathReservations()->AddDeadlyHazard(areaId, gpGlobals->curtime + sv_neo_grenade_fuse_timer.GetFloat(), teamId); } return TryChangeTo( new CNEOBotDead, RESULT_CRITICAL, "I died!" ); diff --git a/src/game/server/neo/bot/behavior/neo_bot_ctg_carrier.cpp b/src/game/server/neo/bot/behavior/neo_bot_ctg_carrier.cpp index 7a8f79a149..3fe936f347 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_ctg_carrier.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_ctg_carrier.cpp @@ -667,6 +667,7 @@ EventDesiredResult< CNEOBot > CNEOBotCtgCarrier::OnStuck( CNEOBot *me ) m_teammates.RemoveAll(); CollectPlayers( me, &m_teammates ); UpdateFollowPath( me, m_teammates ); + CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryContinue(); } @@ -679,5 +680,9 @@ EventDesiredResult< CNEOBot > CNEOBotCtgCarrier::OnMoveToSuccess( CNEOBot *me, c //--------------------------------------------------------------------------------------------- EventDesiredResult< CNEOBot > CNEOBotCtgCarrier::OnMoveToFailure( CNEOBot *me, const Path *path, MoveToFailureType reason ) { + m_teammates.RemoveAll(); + CollectPlayers( me, &m_teammates ); + UpdateFollowPath( me, m_teammates ); + CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryContinue(); } diff --git a/src/game/server/neo/bot/behavior/neo_bot_ctg_enemy.cpp b/src/game/server/neo/bot/behavior/neo_bot_ctg_enemy.cpp index 9ad40bf0a8..703d1b42e9 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_ctg_enemy.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_ctg_enemy.cpp @@ -62,6 +62,7 @@ ActionResult< CNEOBot > CNEOBotCtgEnemy::OnResume( CNEOBot *me, Action< CNEOBot //--------------------------------------------------------------------------------------------- EventDesiredResult< CNEOBot > CNEOBotCtgEnemy::OnStuck( CNEOBot *me ) { + CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryContinue(); } @@ -74,5 +75,6 @@ EventDesiredResult< CNEOBot > CNEOBotCtgEnemy::OnMoveToSuccess( CNEOBot *me, con //--------------------------------------------------------------------------------------------- EventDesiredResult< CNEOBot > CNEOBotCtgEnemy::OnMoveToFailure( CNEOBot *me, const Path *path, MoveToFailureType reason ) { + CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryContinue(); } diff --git a/src/game/server/neo/bot/behavior/neo_bot_ctg_escort.cpp b/src/game/server/neo/bot/behavior/neo_bot_ctg_escort.cpp index 077b9143e3..c27c2a9160 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_ctg_escort.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_ctg_escort.cpp @@ -219,6 +219,7 @@ EventDesiredResult< CNEOBot > CNEOBotCtgEscort::OnStuck( CNEOBot *me ) m_chasePath.Invalidate(); m_path.Invalidate(); m_repathTimer.Invalidate(); + CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryContinue(); } @@ -232,6 +233,7 @@ EventDesiredResult< CNEOBot > CNEOBotCtgEscort::OnMoveToSuccess( CNEOBot *me, co EventDesiredResult< CNEOBot > CNEOBotCtgEscort::OnMoveToFailure( CNEOBot *me, const Path *path, MoveToFailureType reason ) { m_repathTimer.Invalidate(); + CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryContinue(); } diff --git a/src/game/server/neo/bot/behavior/neo_bot_ctg_lone_wolf.cpp b/src/game/server/neo/bot/behavior/neo_bot_ctg_lone_wolf.cpp index 060824007a..6a1828b489 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_ctg_lone_wolf.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_ctg_lone_wolf.cpp @@ -160,9 +160,17 @@ EventDesiredResult< CNEOBot > CNEOBotCtgLoneWolf::OnStuck( CNEOBot *me ) { m_path.Invalidate(); me->GetLocomotionInterface()->Jump(); + CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryContinue(); } +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CNEOBot > CNEOBotCtgLoneWolf::OnMoveToFailure( CNEOBot *me, const Path *path, MoveToFailureType reason ) +{ + m_path.Invalidate(); + CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); + return TryContinue(); +} //--------------------------------------------------------------------------------------------- Vector CNEOBotCtgLoneWolf::GetNearestEnemyCapPoint( CNEOBot *me ) const diff --git a/src/game/server/neo/bot/behavior/neo_bot_ctg_lone_wolf.h b/src/game/server/neo/bot/behavior/neo_bot_ctg_lone_wolf.h index a38f6e3371..c611b60505 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_ctg_lone_wolf.h +++ b/src/game/server/neo/bot/behavior/neo_bot_ctg_lone_wolf.h @@ -14,6 +14,7 @@ class CNEOBotCtgLoneWolf : public Action< CNEOBot > virtual ActionResult< CNEOBot > OnResume( CNEOBot *me, Action< CNEOBot > *interruptingAction ) override; virtual EventDesiredResult< CNEOBot > OnStuck( CNEOBot *me ) override; + virtual EventDesiredResult< CNEOBot > OnMoveToFailure( CNEOBot *me, const Path *path, MoveToFailureType reason ) override; virtual const char *GetName( void ) const override { return "ctgLoneWolf"; } diff --git a/src/game/server/neo/bot/behavior/neo_bot_get_ammo.cpp b/src/game/server/neo/bot/behavior/neo_bot_get_ammo.cpp index 2a07096112..7f40df4f4e 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_get_ammo.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_get_ammo.cpp @@ -200,6 +200,7 @@ EventDesiredResult< CNEOBot > CNEOBotGetAmmo::OnContact( CNEOBot *me, CBaseEntit //--------------------------------------------------------------------------------------------- EventDesiredResult< CNEOBot > CNEOBotGetAmmo::OnStuck( CNEOBot *me ) { + CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryDone( RESULT_CRITICAL, "Stuck trying to reach ammo" ); } @@ -214,6 +215,7 @@ EventDesiredResult< CNEOBot > CNEOBotGetAmmo::OnMoveToSuccess( CNEOBot *me, cons //--------------------------------------------------------------------------------------------- EventDesiredResult< CNEOBot > CNEOBotGetAmmo::OnMoveToFailure( CNEOBot *me, const Path *path, MoveToFailureType reason ) { + CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryDone( RESULT_CRITICAL, "Failed to reach ammo" ); } diff --git a/src/game/server/neo/bot/behavior/neo_bot_get_health.cpp b/src/game/server/neo/bot/behavior/neo_bot_get_health.cpp index 2f1d9eb8da..be90f2b419 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_get_health.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_get_health.cpp @@ -230,6 +230,7 @@ ActionResult< CNEOBot > CNEOBotGetHealth::Update( CNEOBot *me, float interval ) //--------------------------------------------------------------------------------------------- EventDesiredResult< CNEOBot > CNEOBotGetHealth::OnStuck( CNEOBot *me ) { + CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryDone( RESULT_CRITICAL, "Stuck trying to reach health kit" ); } @@ -244,6 +245,7 @@ EventDesiredResult< CNEOBot > CNEOBotGetHealth::OnMoveToSuccess( CNEOBot *me, co //--------------------------------------------------------------------------------------------- EventDesiredResult< CNEOBot > CNEOBotGetHealth::OnMoveToFailure( CNEOBot *me, const Path *path, MoveToFailureType reason ) { + CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryDone( RESULT_CRITICAL, "Failed to reach health kit" ); } diff --git a/src/game/server/neo/bot/behavior/neo_bot_grenade_throw_frag.cpp b/src/game/server/neo/bot/behavior/neo_bot_grenade_throw_frag.cpp index f9b215c1a6..893d5b7e03 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_grenade_throw_frag.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_grenade_throw_frag.cpp @@ -1,11 +1,13 @@ #include "cbase.h" #include "bot/neo_bot.h" #include "bot/neo_bot_path_compute.h" +#include "bot/neo_bot_path_reservation.h" #include "bot/behavior/neo_bot_grenade_throw_frag.h" #include "neo_gamerules.h" #include "neo_player.h" #include "weapon_neobasecombatweapon.h" +#include "nav_mesh.h" #include "nav_pathfind.h" extern ConVar sv_neo_grenade_blast_radius; @@ -106,6 +108,15 @@ CNEOBotGrenadeThrow::ThrowTargetResult CNEOBotGrenadeThrowFrag::UpdateGrenadeTar return THROW_TARGET_CANCEL; // risk of friendly fire } + // Register explosive hazard at the target position + // so the throwing bot's team can avoid the trajectory and landing areas + CNavArea *area = TheNavMesh->GetNearestNavArea(m_vecTarget); + if (area) + { + int team = me->GetTeamNumber(); + CNEOBotPathReservations()->AddFragHazard(area->GetID(), gpGlobals->curtime + sv_neo_grenade_fuse_timer.GetFloat(), team); + } + return THROW_TARGET_READY; } diff --git a/src/game/server/neo/bot/behavior/neo_bot_jgr_enemy.cpp b/src/game/server/neo/bot/behavior/neo_bot_jgr_enemy.cpp index 5e1c18867f..6d947b5ebd 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_jgr_enemy.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_jgr_enemy.cpp @@ -63,12 +63,14 @@ ActionResult< CNEOBot > CNEOBotJgrEnemy::OnResume( CNEOBot *me, Action< CNEOBot //--------------------------------------------------------------------------------------------- EventDesiredResult< CNEOBot > CNEOBotJgrEnemy::OnStuck( CNEOBot *me ) { + CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryContinue(); } //--------------------------------------------------------------------------------------------- EventDesiredResult< CNEOBot > CNEOBotJgrEnemy::OnMoveToSuccess( CNEOBot *me, const Path *path ) { + CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryContinue(); } diff --git a/src/game/server/neo/bot/behavior/neo_bot_jgr_escort.cpp b/src/game/server/neo/bot/behavior/neo_bot_jgr_escort.cpp index bfdd18919a..489849d770 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_jgr_escort.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_jgr_escort.cpp @@ -84,6 +84,7 @@ ActionResult< CNEOBot > CNEOBotJgrEscort::OnResume( CNEOBot *me, Action< CNEOBot //--------------------------------------------------------------------------------------------- EventDesiredResult< CNEOBot > CNEOBotJgrEscort::OnStuck( CNEOBot *me ) { + CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryContinue(); } @@ -96,5 +97,6 @@ EventDesiredResult< CNEOBot > CNEOBotJgrEscort::OnMoveToSuccess( CNEOBot *me, co //--------------------------------------------------------------------------------------------- EventDesiredResult< CNEOBot > CNEOBotJgrEscort::OnMoveToFailure( CNEOBot *me, const Path *path, MoveToFailureType reason ) { + CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryContinue(); } diff --git a/src/game/server/neo/bot/behavior/neo_bot_jgr_juggernaut.cpp b/src/game/server/neo/bot/behavior/neo_bot_jgr_juggernaut.cpp index 4117bc974e..a8c288978a 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_jgr_juggernaut.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_jgr_juggernaut.cpp @@ -144,6 +144,7 @@ EventDesiredResult< CNEOBot > CNEOBotJgrJuggernaut::OnStuck( CNEOBot *me ) me->PressRightButton(); } + CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryContinue(); } diff --git a/src/game/server/neo/bot/behavior/neo_bot_move_to_vantage_point.cpp b/src/game/server/neo/bot/behavior/neo_bot_move_to_vantage_point.cpp index 800dc7931a..abdea6b876 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_move_to_vantage_point.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_move_to_vantage_point.cpp @@ -79,6 +79,7 @@ EventDesiredResult< CNEOBot > CNEOBotMoveToVantagePoint::OnMoveToSuccess( CNEOBo EventDesiredResult< CNEOBot > CNEOBotMoveToVantagePoint::OnMoveToFailure( CNEOBot *me, const Path *path, MoveToFailureType reason ) { m_path.Invalidate(); + CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryContinue(); } diff --git a/src/game/server/neo/bot/behavior/neo_bot_retreat_from_grenade.cpp b/src/game/server/neo/bot/behavior/neo_bot_retreat_from_grenade.cpp index e98035eaf6..a3b72a11a2 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_retreat_from_grenade.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_retreat_from_grenade.cpp @@ -5,6 +5,8 @@ #include "bot/behavior/neo_bot_retreat_from_grenade.h" #include "bot/behavior/neo_bot_retreat_to_cover.h" #include "bot/neo_bot_path_compute.h" +#include "bot/neo_bot_path_reservation.h" +#include "nav_mesh.h" #include "sdk/sdk_basegrenade_projectile.h" // memdbgon must be the last include file in a .cpp file!!! @@ -91,6 +93,20 @@ class CSearchForCoverFromGrenade : public ISearchSurroundingAreasFunctor return false; // can't search if there's no threat source } + // Skip areas that are hazardous or where bots get stuck + if ( neo_bot_path_reservation_enable.GetBool() ) + { + int navAreaId = area->GetID(); + if (CNEOBotPathReservations()->IsAreaHazardous(navAreaId, m_me)) + { + return true; + } + if (CNEOBotPathReservations()->GetAreaAvoidPenalty(navAreaId) != 0.0f) + { + return true; + } + } + if ( m_pGrenadeStats ) { if ( ( area->GetCenter() - m_pGrenadeStats->GetAbsOrigin() ).LengthSqr() < m_safeRadiusSqr * 2 ) @@ -181,6 +197,19 @@ ActionResult< CNEOBot > CNEOBotRetreatFromGrenade::OnStart( CNEOBot *me, Action< m_grenade = FindDangerousGrenade( me ); } + if ( !m_grenade ) // not a duplicate, check FindDangerousGrenade result + { + return Done("No grenade found"); + } + + // Register explosive hazard for the bot's team at the grenade's initial position + CNavArea *grenadeArea = TheNavMesh->GetNearestNavArea( m_grenade->GetAbsOrigin() ); + if (grenadeArea) + { + int team = me->GetTeamNumber(); + CNEOBotPathReservations()->AddFragHazard(grenadeArea->GetID(), gpGlobals->curtime + sv_neo_grenade_fuse_timer.GetFloat(), team); + } + // Sometimes grenades can be in a bad limbo state, so force exit eventually m_expiryTimer.Start( sv_neo_grenade_fuse_timer.GetFloat() ); @@ -247,6 +276,8 @@ ActionResult< CNEOBot > CNEOBotRetreatFromGrenade::Update( CNEOBot *me, float in //--------------------------------------------------------------------------------------------- EventDesiredResult< CNEOBot > CNEOBotRetreatFromGrenade::OnStuck( CNEOBot *me ) { + CNEOBotPathCompute(me, m_path, m_coverArea->GetCenter(), FASTEST_ROUTE); + CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryContinue(); } @@ -261,6 +292,8 @@ EventDesiredResult< CNEOBot > CNEOBotRetreatFromGrenade::OnMoveToSuccess( CNEOBo //--------------------------------------------------------------------------------------------- EventDesiredResult< CNEOBot > CNEOBotRetreatFromGrenade::OnMoveToFailure( CNEOBot *me, const Path *path, MoveToFailureType reason ) { + CNEOBotPathCompute(me, m_path, m_coverArea->GetCenter(), FASTEST_ROUTE); + CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryContinue(); } diff --git a/src/game/server/neo/bot/behavior/neo_bot_retreat_from_hazard_area.cpp b/src/game/server/neo/bot/behavior/neo_bot_retreat_from_hazard_area.cpp new file mode 100644 index 0000000000..dce1b1715a --- /dev/null +++ b/src/game/server/neo/bot/behavior/neo_bot_retreat_from_hazard_area.cpp @@ -0,0 +1,131 @@ +#include "cbase.h" +#include "neo_bot_retreat_from_hazard_area.h" +#include "neo_bot_retreat_from_grenade.h" +#include "neo_bot_retreat_to_cover.h" +#include "neo/bot/neo_bot_path_compute.h" +#include "neo/bot/neo_bot_path_reservation.h" + +extern ConVar neo_bot_retreat_to_cover_range; + +const int MAX_NON_HAZARD_AREA_CANDIDATES = 10; + +class CSearchForSafeArea : public ISearchSurroundingAreasFunctor +{ +public: + CSearchForSafeArea(CNEOBot *me) + : m_me(me) + { + } + + virtual bool operator()(CNavArea *baseArea, CNavArea *priorArea, float travelDistanceSoFar) + { + if (travelDistanceSoFar > neo_bot_retreat_to_cover_range.GetFloat()) + { + return true; + } + + int id = baseArea->GetID(); + if (!CNEOBotPathReservations()->IsAreaHazardous(id, m_me) && + CNEOBotPathReservations()->GetAreaAvoidPenalty(id) == 0.0f) + { + m_safeAreas.AddToTail(baseArea); + } + + if (m_safeAreas.Count() >= MAX_NON_HAZARD_AREA_CANDIDATES) + { + return false; // found enough candidates + } + + return true; + } + + virtual bool ShouldSearch(CNavArea *adjArea, CNavArea *currentArea, float travelDistanceSoFar) + { + return (currentArea->ComputeAdjacentConnectionHeightChange(adjArea) < + m_me->GetLocomotionInterface()->GetStepHeight()); + } + + CUtlVector m_safeAreas; + +private: + CNEOBot *m_me; +}; + +CNEOBotRetreatFromHazardArea::CNEOBotRetreatFromHazardArea() +{ +} + +CNavArea *CNEOBotRetreatFromHazardArea::FindSafeArea(CNEOBot *me) +{ + CNavArea *start = me->GetLastKnownArea(); + if (!start) + { + return nullptr; + } + + CSearchForSafeArea search(me); + SearchSurroundingAreas(start, search); + + if (search.m_safeAreas.Count() == 0) + { + return nullptr; + } + + int last = MIN(MAX_NON_HAZARD_AREA_CANDIDATES, search.m_safeAreas.Count()); + int which = RandomInt(0, last - 1); + return search.m_safeAreas[which]; +} + +ActionResult CNEOBotRetreatFromHazardArea::OnStart(CNEOBot *me, Action *priorAction) +{ + m_safeArea = FindSafeArea(me); + + if (!m_safeArea) + { + return Done("No safe area available!"); + } + + m_path.SetMinLookAheadDistance(me->GetDesiredPathLookAheadRange()); + m_repathTimer.Start(0.1f); + + return Continue(); +} + +ActionResult CNEOBotRetreatFromHazardArea::Update(CNEOBot *me, float interval) +{ + if (m_repathTimer.IsElapsed()) + { + m_repathTimer.Start(RandomFloat(0.3f, 0.5f)); + // RETREAT_ROUTE: Allow pathing through hazardous areas in CNEOBotPathCost + CNEOBotPathCompute(me, m_path, m_safeArea->GetCenter(), RETREAT_ROUTE); + } + + CBaseEntity *dangerousGrenade = CNEOBotRetreatFromGrenade::FindDangerousGrenade(me); + if (dangerousGrenade) + { + return ChangeTo(new CNEOBotRetreatFromGrenade(dangerousGrenade), "Encountered grenade while avoiding hazard!"); + } + + CNavArea *myArea = me->GetLastKnownArea(); + if (!myArea || !CNEOBotPathReservations()->IsAreaHazardous(myArea->GetID(), me)) + { + return Done("Left hazardous area"); + } + + m_path.Update(me); + return Continue(); +} + +EventDesiredResult< CNEOBot > CNEOBotRetreatFromHazardArea::OnStuck( CNEOBot *me ) +{ + CNEOBotPathCompute(me, m_path, m_safeArea->GetCenter(), RETREAT_ROUTE); + CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); + return TryContinue(); +} + +EventDesiredResult< CNEOBot > CNEOBotRetreatFromHazardArea::OnMoveToFailure( CNEOBot *me, const Path *path, MoveToFailureType reason ) +{ + CNEOBotPathCompute(me, m_path, m_safeArea->GetCenter(), RETREAT_ROUTE); + CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); + return TryContinue(); +} \ No newline at end of file diff --git a/src/game/server/neo/bot/behavior/neo_bot_retreat_from_hazard_area.h b/src/game/server/neo/bot/behavior/neo_bot_retreat_from_hazard_area.h new file mode 100644 index 0000000000..e6ba7c444c --- /dev/null +++ b/src/game/server/neo/bot/behavior/neo_bot_retreat_from_hazard_area.h @@ -0,0 +1,27 @@ +#pragma once + +#include "NextBotBehavior.h" +#include "nav_mesh.h" +#include "neo/bot/neo_bot.h" +#include "neo/bot/neo_bot_path_compute.h" + +class CNEOBotRetreatFromHazardArea : public Action +{ +public: + CNEOBotRetreatFromHazardArea(); + + virtual ActionResult OnStart(CNEOBot *me, Action *priorAction) OVERRIDE; + virtual ActionResult Update(CNEOBot *me, float interval) OVERRIDE; + + virtual EventDesiredResult OnStuck(CNEOBot *me) OVERRIDE; + virtual EventDesiredResult OnMoveToFailure(CNEOBot *me, const Path *path, MoveToFailureType reason) OVERRIDE; + + virtual const char *GetName() const OVERRIDE { return "RetreatFromHazardArea"; } + +private: + CNavArea *m_safeArea; + CountdownTimer m_repathTimer; + PathFollower m_path; + + CNavArea *FindSafeArea(CNEOBot *me); +}; diff --git a/src/game/server/neo/bot/behavior/neo_bot_retreat_to_cover.cpp b/src/game/server/neo/bot/behavior/neo_bot_retreat_to_cover.cpp index b31b8a5ae4..9a444bd61a 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_retreat_to_cover.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_retreat_to_cover.cpp @@ -8,6 +8,7 @@ #include "bot/behavior/neo_bot_retreat_from_grenade.h" #include "bot/behavior/neo_bot_retreat_to_cover.h" #include "bot/neo_bot_path_compute.h" +#include "bot/neo_bot_path_reservation.h" extern ConVar neo_bot_path_lookahead_range; ConVar neo_bot_retreat_to_cover_range( "neo_bot_retreat_to_cover_range", "1000", FCVAR_CHEAT ); @@ -115,6 +116,20 @@ class CSearchForCover : public ISearchSurroundingAreasFunctor CNavArea *area = (CNavArea *)baseArea; + // Skip areas that are hazardous or where bots get stuck + if ( neo_bot_path_reservation_enable.GetBool() ) + { + int navAreaId = area->GetID(); + if (CNEOBotPathReservations()->IsAreaHazardous(navAreaId, m_me)) + { + return true; + } + if (CNEOBotPathReservations()->GetAreaAvoidPenalty(navAreaId) > 0.0f) + { + return true; + } + } + CTestAreaAgainstThreats test( m_me, area ); m_me->GetVisionInterface()->ForEachKnownEntity( test ); @@ -326,6 +341,8 @@ ActionResult< CNEOBot > CNEOBotRetreatToCover::Update( CNEOBot *me, float interv //--------------------------------------------------------------------------------------------- EventDesiredResult< CNEOBot > CNEOBotRetreatToCover::OnStuck( CNEOBot *me ) { + CNEOBotPathCompute( me, m_path, m_coverArea->GetCenter(), RETREAT_ROUTE ); + CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryContinue(); } @@ -340,6 +357,8 @@ EventDesiredResult< CNEOBot > CNEOBotRetreatToCover::OnMoveToSuccess( CNEOBot *m //--------------------------------------------------------------------------------------------- EventDesiredResult< CNEOBot > CNEOBotRetreatToCover::OnMoveToFailure( CNEOBot *me, const Path *path, MoveToFailureType reason ) { + CNEOBotPathCompute( me, m_path, m_coverArea->GetCenter(), RETREAT_ROUTE ); + CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryContinue(); } diff --git a/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp b/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp index 00419171c3..1219b0c256 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_seek_and_destroy.cpp @@ -344,6 +344,7 @@ ActionResult< CNEOBot > CNEOBotSeekAndDestroy::OnResume( CNEOBot *me, Action< CN EventDesiredResult< CNEOBot > CNEOBotSeekAndDestroy::OnStuck( CNEOBot *me ) { RecomputeSeekPath( me ); + CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryContinue(); } @@ -362,6 +363,7 @@ EventDesiredResult< CNEOBot > CNEOBotSeekAndDestroy::OnMoveToSuccess( CNEOBot *m EventDesiredResult< CNEOBot > CNEOBotSeekAndDestroy::OnMoveToFailure( CNEOBot *me, const Path *path, MoveToFailureType reason ) { RecomputeSeekPath( me ); + CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryContinue(); } diff --git a/src/game/server/neo/bot/behavior/neo_bot_seek_weapon.cpp b/src/game/server/neo/bot/behavior/neo_bot_seek_weapon.cpp index 268f2ba1ac..6fc7bd6caf 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_seek_weapon.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_seek_weapon.cpp @@ -357,6 +357,7 @@ EventDesiredResult< CNEOBot > CNEOBotSeekWeapon::OnStuck( CNEOBot *me ) m_hTargetWeapon = nullptr; m_repathTimer.Invalidate(); FindAndPathToWeapon(me); + CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryContinue(); } @@ -372,5 +373,6 @@ EventDesiredResult< CNEOBot > CNEOBotSeekWeapon::OnMoveToFailure( CNEOBot *me, c m_hTargetWeapon = nullptr; m_repathTimer.Invalidate(); FindAndPathToWeapon(me); + CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryContinue(); } 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 78e8be610a..7ebce3eb6b 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 @@ -7,6 +7,7 @@ #include "bot/neo_bot.h" #include "bot/neo_bot_manager.h" +#include "bot/neo_bot_path_reservation.h" #include "bot/behavior/neo_bot_tactical_monitor.h" #include "bot/behavior/neo_bot_scenario_monitor.h" @@ -15,6 +16,7 @@ #include "bot/behavior/neo_bot_seek_weapon.h" #include "bot/behavior/neo_bot_retreat_to_cover.h" #include "bot/behavior/neo_bot_retreat_from_grenade.h" +#include "bot/behavior/neo_bot_retreat_from_hazard_area.h" #include "bot/behavior/neo_bot_ladder_approach.h" #include "bot/behavior/neo_bot_ladder_climb.h" #include "bot/behavior/neo_bot_path_clear_breakable.h" @@ -81,6 +83,7 @@ Action< CNEOBot > *CNEOBotTacticalMonitor::InitialContainedAction( CNEOBot *me ) ActionResult< CNEOBot > CNEOBotTacticalMonitor::OnStart( CNEOBot *me, Action< CNEOBot > *priorAction ) { m_pIgnoredWeapons->Reset(); + m_hazardCheckTimer.Start( 0.5f ); return Continue(); } @@ -287,6 +290,19 @@ ActionResult< CNEOBot > CNEOBotTacticalMonitor::Update( CNEOBot *me, float inter return SuspendFor( new CNEOBotPathClearBreakable( breakable ), "Clearing breakable in path" ); } + // Don't want to interfere with human squad leader's control just to ignore hazards + // Might be annoying if bots ignored following or waypoints (e.g. smoke sightlines avoidance) + if ( !me->m_hCommandingPlayer.Get() && m_hazardCheckTimer.IsElapsed() ) + { + m_hazardCheckTimer.Start( 0.5f ); + + CNavArea *myArea = me->GetLastKnownArea(); + if ( myArea && CNEOBotPathReservations()->IsAreaHazardous( myArea->GetID(), me ) ) + { + return SuspendFor( new CNEOBotRetreatFromHazardArea( ), "Avoiding hazard area" ); + } + } + ActionResult< CNEOBot > scavengeResult = ScavengeForPrimaryWeapon( me ); if ( scavengeResult.IsRequestingChange() ) { diff --git a/src/game/server/neo/bot/behavior/neo_bot_tactical_monitor.h b/src/game/server/neo/bot/behavior/neo_bot_tactical_monitor.h index 83634acfcf..352dc5caed 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_tactical_monitor.h +++ b/src/game/server/neo/bot/behavior/neo_bot_tactical_monitor.h @@ -26,6 +26,7 @@ class CNEOBotTacticalMonitor : public Action< CNEOBot > virtual const char* GetName(void) const { return "TacticalMonitor"; } private: + CountdownTimer m_hazardCheckTimer; CountdownTimer m_maintainTimer; CountdownTimer m_acknowledgeAttentionTimer; diff --git a/src/game/server/neo/bot/neo_bot_path_cost.cpp b/src/game/server/neo/bot/neo_bot_path_cost.cpp index 99b6db67a2..e921696981 100644 --- a/src/game/server/neo/bot/neo_bot_path_cost.cpp +++ b/src/game/server/neo/bot/neo_bot_path_cost.cpp @@ -55,158 +55,164 @@ float CNEOBotPathCost::operator()(CNavArea* baseArea, CNavArea* fromArea, const // first area in path, no cost return 0.0f; } - else + + if ( CNEOBotPathReservations()->IsAreaHazardous(area->GetID(), m_me) ) { - if (!m_me->GetLocomotionInterface()->IsAreaTraversable(area)) + if ( (m_routeType != RETREAT_ROUTE) || (m_routeType != FASTEST_ROUTE) ) { - return -1.0f; + return -1.0f; // attempt to route around hazards } + } - // compute distance traveled along path so far - float dist; + if (!m_me->GetLocomotionInterface()->IsAreaTraversable(area)) + { + return -1.0f; + } - if (ladder) - { - dist = ladder->m_length; + // compute distance traveled along path so far + float dist; - // ladders leave bots exposed, but can be a shortcut - const float ladderPenalty = neo_bot_path_penalty_ladder_multiplier.GetFloat(); - dist *= ladderPenalty; - } - else if (length > 0.0) - { - dist = length; - } - else - { - dist = (area->GetCenter() - fromArea->GetCenter()).Length(); - } + if (ladder) + { + dist = ladder->m_length; - // Only apply height restrictions for non-ladder jump paths - if (!ladder) - { - // check height change - float deltaZ = fromArea->ComputeAdjacentConnectionHeightChange(area); + // ladders leave bots exposed, but can be a shortcut + const float ladderPenalty = neo_bot_path_penalty_ladder_multiplier.GetFloat(); + dist *= ladderPenalty; + } + else if (length > 0.0) + { + dist = length; + } + else + { + dist = (area->GetCenter() - fromArea->GetCenter()).Length(); + } - if (deltaZ >= m_stepHeight) - { - if (deltaZ >= m_maxJumpHeight) - { - // too high to reach - return -1.0f; - } + // Only apply height restrictions for non-ladder jump paths + if (!ladder) + { + // check height change + float deltaZ = fromArea->ComputeAdjacentConnectionHeightChange(area); - // jumping is slower than flat ground - const float jumpPenalty = neo_bot_path_penalty_jump_multiplier.GetFloat() * Square( deltaZ / m_maxJumpHeight ); - dist *= jumpPenalty; - } - else if (deltaZ < -m_maxDropHeight) + if (deltaZ >= m_stepHeight) + { + if (deltaZ >= m_maxJumpHeight) { - // too far to drop + // too high to reach return -1.0f; } - } - - // add a random penalty unique to this character so they choose different routes to the same place - float preference = 1.0f; - if (m_routeType == DEFAULT_ROUTE) + // jumping is slower than flat ground + const float jumpPenalty = neo_bot_path_penalty_jump_multiplier.GetFloat() * Square( deltaZ / m_maxJumpHeight ); + dist *= jumpPenalty; + } + else if (deltaZ < -m_maxDropHeight) { - // this term causes the same bot to choose different routes over time, - // but keep the same route for a period in case of repaths - int timeMod = (int)(gpGlobals->curtime / 10.0f) + 1; - preference = 1.0f + 50.0f * (1.0f + FastCos((float)(m_me->GetEntity()->entindex() * area->GetID() * timeMod))); + // too far to drop + return -1.0f; } + } - if (m_routeType == SAFEST_ROUTE) - { - // misyl: combat areas. + // add a random penalty unique to this character so they choose different routes to the same place + float preference = 1.0f; + + if (m_routeType == DEFAULT_ROUTE) + { + // this term causes the same bot to choose different routes over time, + // but keep the same route for a period in case of repaths + int timeMod = (int)(gpGlobals->curtime / 10.0f) + 1; + preference = 1.0f + 50.0f * (1.0f + FastCos((float)(m_me->GetEntity()->entindex() * area->GetID() * timeMod))); + } + + if (m_routeType == SAFEST_ROUTE) + { + // misyl: combat areas. #if 0 - // avoid combat areas - if (area->IsInCombat()) - { - const float combatDangerCost = 4.0f; - dist *= combatDangerCost * area->GetCombatIntensity(); - } -#endif + // avoid combat areas + if (area->IsInCombat()) + { + const float combatDangerCost = 4.0f; + dist *= combatDangerCost * area->GetCombatIntensity(); } +#endif + } - float cost = (dist * preference); + float cost = (dist * preference); - // ------------------------------------------------------------------------------------------------ - // New path reservation related cost adjustments - if ( !m_bIgnoreReservations && (m_routeType != FASTEST_ROUTE) ) - { - cost += CNEOBotPathReservations()->GetPredictedFriendlyPathCount(area->GetID(), m_me->GetTeamNumber()) * neo_bot_path_reservation_penalty.GetFloat(); - cost += CNEOBotPathReservations()->GetAreaAvoidPenalty(area->GetID()); + // ------------------------------------------------------------------------------------------------ + // New path reservation related cost adjustments + if ( !m_bIgnoreReservations && (m_routeType != FASTEST_ROUTE) ) + { + cost += CNEOBotPathReservations()->GetPredictedFriendlyPathCount(area->GetID(), m_me->GetTeamNumber()) * neo_bot_path_reservation_penalty.GetFloat(); + cost += CNEOBotPathReservations()->GetAreaAvoidPenalty(area->GetID()); - // Weapon range penalties - auto* myWeapon = assert_cast(m_me->GetActiveWeapon()); - if (myWeapon) + // Weapon range penalties + auto* myWeapon = assert_cast(m_me->GetActiveWeapon()); + if (myWeapon) + { + const int nWeaponBits = myWeapon->GetNeoWepBits(); + if (nWeaponBits & NEO_WEP_FIREARM) { - const int nWeaponBits = myWeapon->GetNeoWepBits(); - if (nWeaponBits & NEO_WEP_FIREARM) + const int visibleAreaCount = area->GetPotentiallyVisibleAreaCount(); + if (visibleAreaCount > 0) { - const int visibleAreaCount = area->GetPotentiallyVisibleAreaCount(); - if (visibleAreaCount > 0) + constexpr int nShotgunBits = NEO_WEP_AA13 | NEO_WEP_SUPA7; + constexpr int nBattleRifleBits = NEO_WEP_M41 | NEO_WEP_M41_S; + constexpr int nPistolCaliberBits = NEO_WEP_MILSO | NEO_WEP_TACHI | NEO_WEP_KYLA + | NEO_WEP_MPN | NEO_WEP_MPN_S | NEO_WEP_JITTE | NEO_WEP_JITTE_S | NEO_WEP_SRM | NEO_WEP_SRM_S; + + if (nWeaponBits & nPistolCaliberBits) { - constexpr int nShotgunBits = NEO_WEP_AA13 | NEO_WEP_SUPA7; - constexpr int nBattleRifleBits = NEO_WEP_M41 | NEO_WEP_M41_S; - constexpr int nPistolCaliberBits = NEO_WEP_MILSO | NEO_WEP_TACHI | NEO_WEP_KYLA - | NEO_WEP_MPN | NEO_WEP_MPN_S | NEO_WEP_JITTE | NEO_WEP_JITTE_S | NEO_WEP_SRM | NEO_WEP_SRM_S; - - if (nWeaponBits & nPistolCaliberBits) - { - // Weapons that don't have max first shot accuracy - const float exposurePenalty = neo_bot_path_penalty_exposure_pistol.GetFloat(); - cost += visibleAreaCount * exposurePenalty; - } - else if (nWeaponBits & nShotgunBits) - { - // Weapons that have spread that can't hit long range targets - const float exposurePenalty = neo_bot_path_penalty_exposure_shotgun.GetFloat(); - cost += visibleAreaCount * exposurePenalty; - } - else if (nWeaponBits & nBattleRifleBits) - { - // Weapons that benefit from medium sightlines that can see many NavAreas - const float baseline_penalty = neo_bot_path_penalty_exposure_inverse_base_battle_rifle.GetFloat(); - cost += baseline_penalty / visibleAreaCount; - } - else if (nWeaponBits & NEO_WEP_SCOPEDWEAPON) - { - // Weapons that benefit from long sightlines that can see many NavAreas - const float baseline_penalty = neo_bot_path_penalty_exposure_inverse_base_scoped.GetFloat(); - cost += baseline_penalty / visibleAreaCount; - } - else - { - // Generally avoiding exposed areas when traversing a wide open area - const float exposurePenalty = neo_bot_path_penalty_exposure_base.GetFloat(); - cost += visibleAreaCount * exposurePenalty; - } + // Weapons that don't have max first shot accuracy + const float exposurePenalty = neo_bot_path_penalty_exposure_pistol.GetFloat(); + cost += visibleAreaCount * exposurePenalty; + } + else if (nWeaponBits & nShotgunBits) + { + // Weapons that have spread that can't hit long range targets + const float exposurePenalty = neo_bot_path_penalty_exposure_shotgun.GetFloat(); + cost += visibleAreaCount * exposurePenalty; + } + else if (nWeaponBits & nBattleRifleBits) + { + // Weapons that benefit from medium sightlines that can see many NavAreas + const float baseline_penalty = neo_bot_path_penalty_exposure_inverse_base_battle_rifle.GetFloat(); + cost += baseline_penalty / visibleAreaCount; + } + else if (nWeaponBits & NEO_WEP_SCOPEDWEAPON) + { + // Weapons that benefit from long sightlines that can see many NavAreas + const float baseline_penalty = neo_bot_path_penalty_exposure_inverse_base_scoped.GetFloat(); + cost += baseline_penalty / visibleAreaCount; + } + else + { + // Generally avoiding exposed areas when traversing a wide open area + const float exposurePenalty = neo_bot_path_penalty_exposure_base.GetFloat(); + cost += visibleAreaCount * exposurePenalty; } } } - - if (m_routeType == SAFEST_ROUTE) - { - // NEO Jank Cheat: Incorporate enemy bot paths so that we don't run directly into their line of fire - // Intended for use by ghost carrier team, to emulate a team that knows where enemies are likely to ambush - // Compensates for bots' lack of meta knowledge by making them prefer routes not reserved by enemies - // Adheres to cheat against bots but not against humans philosophy by not considering human players' positions - cost += CNEOBotPathReservations()->GetPredictedFriendlyPathCount(area->GetID(), GetEnemyTeam(m_me->GetTeamNumber())) * neo_bot_path_reservation_penalty.GetFloat() * 2; - } } - // ------------------------------------------------------------------------------------------------ - if (area->HasAttributes(NAV_MESH_FUNC_COST)) + if (m_routeType == SAFEST_ROUTE) { - cost *= area->ComputeFuncNavCost(m_me); - DebuggerBreakOnNaN_StagingOnly(cost); + // NEO Jank Cheat: Incorporate enemy bot paths so that we don't run directly into their line of fire + // Intended for use by ghost carrier team, to emulate a team that knows where enemies are likely to ambush + // Compensates for bots' lack of meta knowledge by making them prefer routes not reserved by enemies + // Adheres to cheat against bots but not against humans philosophy by not considering human players' positions + cost += CNEOBotPathReservations()->GetPredictedFriendlyPathCount(area->GetID(), GetEnemyTeam(m_me->GetTeamNumber())) * neo_bot_path_reservation_penalty.GetFloat() * 2; } + } + // ------------------------------------------------------------------------------------------------ - return cost + fromArea->GetCostSoFar(); + if (area->HasAttributes(NAV_MESH_FUNC_COST)) + { + cost *= area->ComputeFuncNavCost(m_me); + DebuggerBreakOnNaN_StagingOnly(cost); } + + return cost + fromArea->GetCostSoFar(); } diff --git a/src/game/server/neo/bot/neo_bot_path_reservation.cpp b/src/game/server/neo/bot/neo_bot_path_reservation.cpp index 89f0244d5b..15a77c81eb 100644 --- a/src/game/server/neo/bot/neo_bot_path_reservation.cpp +++ b/src/game/server/neo/bot/neo_bot_path_reservation.cpp @@ -249,6 +249,7 @@ void CNEOBotPathReservationSystem::Clear() { m_Reservations[team].RemoveAll(); m_AreaPathCounts[team].RemoveAll(); + m_HazardAreas[team].RemoveAll(); } m_BotReservedAreas.RemoveAll(); m_AreaAvoidPenalties.RemoveAll(); @@ -342,3 +343,180 @@ float CNEOBotPathReservationSystem::GetAreaAvoidPenalty(unsigned int navAreaID) } return 0.0f; } + +//------------------------------------------------------------------------------------------------- +// Functor for ForAllPotentiallyVisibleAreas to propagate deadly hazard to PVS-adjacent areas +struct CNEOFunctorPropagatePVSDeadlyHazard +{ + CNEOFunctorPropagatePVSDeadlyHazard(float expireTime, int teamID) + : m_expireTime(expireTime), m_teamID(teamID) + { + } + + bool operator()(CNavArea *area) + { + CNEOBotPathReservations()->AddDeadlyHazard(area->GetID(), m_expireTime, m_teamID); + return true; + } + + float m_expireTime; + int m_teamID; +}; + +//------------------------------------------------------------------------------------------------- +// Marks an area temporarily for bots to avoid or escape from +void CNEOBotPathReservationSystem::AddDeadlyHazard(int navAreaID, float expireTime, int teamID, bool propagatePVS) +{ + if ( !neo_bot_path_reservation_avoid_penalty_enable.GetBool() ) + { + return; + } + + if ( (teamID < 0) || (teamID >= TEAM__TOTAL) ) + { + return; + } + + int index = m_HazardAreas[teamID].Find(navAreaID); + if ( !m_HazardAreas[teamID].IsValidIndex(index) ) + { + // Initialize blank slate lookup entry + HazardInfo blank; + blank.hazardExpireTime = expireTime; + blank.smokeExpireTime = 0.0f; + index = m_HazardAreas[teamID].Insert(navAreaID, blank); + } + else + { + HazardInfo &existing = m_HazardAreas[teamID][index]; + // Optimizing simplification: assume new time is later to skip comparisons + // May also work for resetting an area to be non-hazardous early + existing.hazardExpireTime = expireTime; + } + + if ( propagatePVS ) + { + CNavArea *area = TheNavMesh->GetNavAreaByID(navAreaID); + if (area) + { + CNEOFunctorPropagatePVSDeadlyHazard propagate(expireTime, teamID); + area->ForAllPotentiallyVisibleAreas(propagate); + } + } +} + +//------------------------------------------------------------------------------------------------- +void CNEOBotPathReservationSystem::AddFragHazard(int navAreaID, float expireTime, int teamID) +{ + AddDeadlyHazard(navAreaID, expireTime, teamID, true); + // true (propagatePVS) - propagate hazard to all adjacent PVS areas + // shorthand for labeling entire trajectory and potential stray angles as hazardous + // also intended for grenade thrower to duck behind cover from perspective of target +} + +//------------------------------------------------------------------------------------------------- +// Functor for ForAllPotentiallyVisibleAreas to propagate a smoke hazard to PVS-adjacent areas +struct CNEOFunctorPropagatePVSSmokeHazard +{ + CNEOFunctorPropagatePVSSmokeHazard(float expireTime, int teamID) + : m_expireTime(expireTime), m_teamID(teamID) + { + } + + bool operator()(CNavArea *area) + { + CNEOBotPathReservations()->AddSmokeHazard(area->GetID(), m_expireTime, m_teamID, false); + return true; + } + + float m_expireTime; + int m_teamID; +}; + +//------------------------------------------------------------------------------------------------- +// Marks an area temporarily for bots to avoid or escape from +// Support bots ignore this smoke hazard +void CNEOBotPathReservationSystem::AddSmokeHazard(int navAreaID, float expireTime, int teamID, bool propagatePVS) +{ + if ( !neo_bot_path_reservation_avoid_penalty_enable.GetBool() ) + { + return; + } + + if (teamID < 0 || teamID >= TEAM__TOTAL) + { + return; + } + + int index = m_HazardAreas[teamID].Find(navAreaID); + if (!m_HazardAreas[teamID].IsValidIndex(index)) + { + // Initialize blank slate lookup entry + HazardInfo blank; + blank.hazardExpireTime = 0.0f; + blank.smokeExpireTime = expireTime; + index = m_HazardAreas[teamID].Insert(navAreaID, blank); + } + else + { + HazardInfo &existing = m_HazardAreas[teamID][index]; + // Optimizing simplification: assume new time is later to skip comparisons + // May also work for resetting an area to be non-hazardous early + existing.smokeExpireTime = expireTime; + + } + + if (propagatePVS) + { + CNavArea *area = TheNavMesh->GetNavAreaByID(navAreaID); + if (area) + { + CNEOFunctorPropagatePVSSmokeHazard propagate(expireTime, teamID); + area->ForAllPotentiallyVisibleAreas(propagate); + } + } +} + +//------------------------------------------------------------------------------------------------- +bool CNEOBotPathReservationSystem::IsAreaHazardous(int navAreaID, const CNEOBot *me) const +{ + if (!neo_bot_path_reservation_avoid_penalty_enable.GetBool()) + { + return false; + } + + if (!me) + { + return false; + } + + int teamID = me->GetTeamNumber(); + + if (teamID < 0 || teamID >= TEAM__TOTAL) + { + return false; + } + + int index = m_HazardAreas[teamID].Find(navAreaID); + if (m_HazardAreas[teamID].IsValidIndex(index)) + { + const HazardInfo &h = m_HazardAreas[teamID][index]; + + if (gpGlobals->curtime < h.hazardExpireTime) + { + // All bots avoid explosive hazards + return true; + } + + // Support class can see through smoke + if (me->GetClass() != NEO_CLASS_SUPPORT) + { + if (gpGlobals->curtime < h.smokeExpireTime) + { + return true; + } + } + } + + return false; +} diff --git a/src/game/server/neo/bot/neo_bot_path_reservation.h b/src/game/server/neo/bot/neo_bot_path_reservation.h index e41401e8b3..3d5cbad817 100644 --- a/src/game/server/neo/bot/neo_bot_path_reservation.h +++ b/src/game/server/neo/bot/neo_bot_path_reservation.h @@ -13,6 +13,12 @@ struct ReservationInfo float flExpirationTime; // When the reservation expires (gpGlobals->curtime) }; +struct HazardInfo +{ + float smokeExpireTime; // when the smoke hazard risk expires + float hazardExpireTime; // when a general deadly hazard risk expires +}; + struct BotReservedAreas_t { CUtlVector areas; @@ -48,6 +54,7 @@ class CNEOBotPathReservationSystem { m_Reservations[i].SetLessFunc(ReservationLessFunc); m_AreaPathCounts[i].SetLessFunc(ReservationLessFunc); + m_HazardAreas[i].SetLessFunc(ReservationLessFunc); } } @@ -64,6 +71,11 @@ class CNEOBotPathReservationSystem void IncrementAreaAvoidPenalty(unsigned int navAreaID, float penaltyAmount); float GetAreaAvoidPenalty(unsigned int navAreaID) const; + void AddDeadlyHazard(int navAreaID, float expireTime, int teamID, bool propagatePVS = false); + void AddFragHazard(int navAreaID, float expireTime, int teamID); + void AddSmokeHazard(int navAreaID, float expireTime, int teamID, bool propagatePVS = true); + bool IsAreaHazardous(int navAreaID, const CNEOBot *me) const; + // Allow the global accessor to access private members if needed, though constructor handles init now. friend CNEOBotPathReservationSystem* CNEOBotPathReservations(); @@ -72,6 +84,7 @@ class CNEOBotPathReservationSystem CUtlMap m_BotReservedAreas; CUtlMap m_AreaPathCounts[TEAM__TOTAL]; CUtlMap m_AreaAvoidPenalties; + CUtlMap m_HazardAreas[TEAM__TOTAL]; }; From d12881af430aab8581a7f8b726bca6976eb5204a Mon Sep 17 00:00:00 2001 From: Alan Shen Date: Tue, 14 Jul 2026 19:13:48 -0600 Subject: [PATCH 2/2] Allow choosing escape destinations that have death area penalties --- .../server/neo/bot/behavior/neo_bot_attack.cpp | 9 +++------ .../bot/behavior/neo_bot_retreat_from_grenade.cpp | 6 +++++- .../behavior/neo_bot_retreat_from_hazard_area.cpp | 15 ++++++--------- .../neo/bot/behavior/neo_bot_retreat_to_cover.cpp | 6 +++++- 4 files changed, 19 insertions(+), 17 deletions(-) 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 e8580da508..98ce6931f5 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_attack.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_attack.cpp @@ -62,6 +62,7 @@ class CSearchForAttackCover : public ISearchSurroundingAreasFunctor m_threatArea = threat->GetLastKnownArea(); m_goalArea = goalArea ? goalArea : m_threatArea; // prioritize movement towards input goal area or threat m_myDistToGoalSq = m_goalArea ? ( m_goalArea->GetCenter() - m_me->GetAbsOrigin() ).LengthSqr() : 0; + m_onStuckPenalty = neo_bot_path_reservation_onstuck_penalty.GetFloat(); } virtual bool operator() ( CNavArea *baseArea, CNavArea *priorArea, float travelDistanceSoFar ) @@ -87,7 +88,7 @@ class CSearchForAttackCover : public ISearchSurroundingAreasFunctor { return true; } - if (CNEOBotPathReservations()->GetAreaAvoidPenalty(navAreaId) != 0.0f) + if (CNEOBotPathReservations()->GetAreaAvoidPenalty(navAreaId) >= m_onStuckPenalty) { return true; } @@ -207,6 +208,7 @@ class CSearchForAttackCover : public ISearchSurroundingAreasFunctor const CNavArea *m_myArea; // reference point of myself const CNavArea *m_threatArea; // reference point of the threat float m_myDistToGoalSq; // the bot's current distance to the threat + float m_onStuckPenalty; // cache onstuck penalty }; @@ -460,11 +462,6 @@ QueryResultType CNEOBotAttack::ShouldRetreat( const INextBot *me ) const return ANSWER_UNDEFINED; } - if ( m_goalArea && m_attackCoverArea ) - { - return ANSWER_NO; - } - return ANSWER_UNDEFINED; } diff --git a/src/game/server/neo/bot/behavior/neo_bot_retreat_from_grenade.cpp b/src/game/server/neo/bot/behavior/neo_bot_retreat_from_grenade.cpp index a3b72a11a2..b998c8d387 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_retreat_from_grenade.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_retreat_from_grenade.cpp @@ -74,6 +74,7 @@ class CSearchForCoverFromGrenade : public ISearchSurroundingAreasFunctor { m_me = me; m_grenade = grenade; + m_onStuckPenalty = neo_bot_path_reservation_onstuck_penalty.GetFloat(); m_pGrenadeStats = dynamic_cast( grenade ); m_safeRadiusSqr = m_pGrenadeStats ? Square(m_pGrenadeStats->m_DmgRadius * sv_neo_bot_grenade_frag_safety_range_multiplier.GetFloat()) : 0.0f; @@ -101,7 +102,7 @@ class CSearchForCoverFromGrenade : public ISearchSurroundingAreasFunctor { return true; } - if (CNEOBotPathReservations()->GetAreaAvoidPenalty(navAreaId) != 0.0f) + if (CNEOBotPathReservations()->GetAreaAvoidPenalty(navAreaId) >= m_onStuckPenalty) { return true; } @@ -154,6 +155,7 @@ class CSearchForCoverFromGrenade : public ISearchSurroundingAreasFunctor CNEOBot *m_me; CBaseEntity *m_grenade; CBaseGrenadeProjectile *m_pGrenadeStats; + float m_onStuckPenalty; float m_safeRadiusSqr; CUtlVector< CNavArea * > m_coverAreaVector; }; @@ -276,6 +278,7 @@ ActionResult< CNEOBot > CNEOBotRetreatFromGrenade::Update( CNEOBot *me, float in //--------------------------------------------------------------------------------------------- EventDesiredResult< CNEOBot > CNEOBotRetreatFromGrenade::OnStuck( CNEOBot *me ) { + m_coverArea = FindCoverArea( me ); CNEOBotPathCompute(me, m_path, m_coverArea->GetCenter(), FASTEST_ROUTE); CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryContinue(); @@ -292,6 +295,7 @@ EventDesiredResult< CNEOBot > CNEOBotRetreatFromGrenade::OnMoveToSuccess( CNEOBo //--------------------------------------------------------------------------------------------- EventDesiredResult< CNEOBot > CNEOBotRetreatFromGrenade::OnMoveToFailure( CNEOBot *me, const Path *path, MoveToFailureType reason ) { + m_coverArea = FindCoverArea( me ); CNEOBotPathCompute(me, m_path, m_coverArea->GetCenter(), FASTEST_ROUTE); CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryContinue(); diff --git a/src/game/server/neo/bot/behavior/neo_bot_retreat_from_hazard_area.cpp b/src/game/server/neo/bot/behavior/neo_bot_retreat_from_hazard_area.cpp index dce1b1715a..8359569eb9 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_retreat_from_hazard_area.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_retreat_from_hazard_area.cpp @@ -5,28 +5,22 @@ #include "neo/bot/neo_bot_path_compute.h" #include "neo/bot/neo_bot_path_reservation.h" -extern ConVar neo_bot_retreat_to_cover_range; - -const int MAX_NON_HAZARD_AREA_CANDIDATES = 10; +const int MAX_NON_HAZARD_AREA_CANDIDATES = 5; class CSearchForSafeArea : public ISearchSurroundingAreasFunctor { public: CSearchForSafeArea(CNEOBot *me) : m_me(me) + , m_onStuckPenalty(neo_bot_path_reservation_onstuck_penalty.GetFloat()) { } virtual bool operator()(CNavArea *baseArea, CNavArea *priorArea, float travelDistanceSoFar) { - if (travelDistanceSoFar > neo_bot_retreat_to_cover_range.GetFloat()) - { - return true; - } - int id = baseArea->GetID(); if (!CNEOBotPathReservations()->IsAreaHazardous(id, m_me) && - CNEOBotPathReservations()->GetAreaAvoidPenalty(id) == 0.0f) + CNEOBotPathReservations()->GetAreaAvoidPenalty(id) < m_onStuckPenalty) { m_safeAreas.AddToTail(baseArea); } @@ -49,6 +43,7 @@ class CSearchForSafeArea : public ISearchSurroundingAreasFunctor private: CNEOBot *m_me; + float m_onStuckPenalty; }; CNEOBotRetreatFromHazardArea::CNEOBotRetreatFromHazardArea() @@ -118,6 +113,7 @@ ActionResult CNEOBotRetreatFromHazardArea::Update(CNEOBot *me, float in EventDesiredResult< CNEOBot > CNEOBotRetreatFromHazardArea::OnStuck( CNEOBot *me ) { + m_safeArea = FindSafeArea(me); CNEOBotPathCompute(me, m_path, m_safeArea->GetCenter(), RETREAT_ROUTE); CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryContinue(); @@ -125,6 +121,7 @@ EventDesiredResult< CNEOBot > CNEOBotRetreatFromHazardArea::OnStuck( CNEOBot *me EventDesiredResult< CNEOBot > CNEOBotRetreatFromHazardArea::OnMoveToFailure( CNEOBot *me, const Path *path, MoveToFailureType reason ) { + m_safeArea = FindSafeArea(me); CNEOBotPathCompute(me, m_path, m_safeArea->GetCenter(), RETREAT_ROUTE); CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryContinue(); diff --git a/src/game/server/neo/bot/behavior/neo_bot_retreat_to_cover.cpp b/src/game/server/neo/bot/behavior/neo_bot_retreat_to_cover.cpp index 9a444bd61a..66abc788eb 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_retreat_to_cover.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_retreat_to_cover.cpp @@ -104,6 +104,7 @@ class CSearchForCover : public ISearchSurroundingAreasFunctor CSearchForCover( CNEOBot *me ) { m_me = me; + m_onStuckPenalty = neo_bot_path_reservation_onstuck_penalty.GetFloat(); m_minExposureCount = 9999; if ( neo_bot_debug_retreat_to_cover.GetBool() ) @@ -124,7 +125,7 @@ class CSearchForCover : public ISearchSurroundingAreasFunctor { return true; } - if (CNEOBotPathReservations()->GetAreaAvoidPenalty(navAreaId) > 0.0f) + if (CNEOBotPathReservations()->GetAreaAvoidPenalty(navAreaId) >= m_onStuckPenalty) { return true; } @@ -171,6 +172,7 @@ class CSearchForCover : public ISearchSurroundingAreasFunctor CNEOBot *m_me; CUtlVector< CNavArea * > m_coverAreaVector; int m_minExposureCount; + float m_onStuckPenalty; }; @@ -341,6 +343,7 @@ ActionResult< CNEOBot > CNEOBotRetreatToCover::Update( CNEOBot *me, float interv //--------------------------------------------------------------------------------------------- EventDesiredResult< CNEOBot > CNEOBotRetreatToCover::OnStuck( CNEOBot *me ) { + m_coverArea = FindCoverArea( me ); CNEOBotPathCompute( me, m_path, m_coverArea->GetCenter(), RETREAT_ROUTE ); CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryContinue(); @@ -357,6 +360,7 @@ EventDesiredResult< CNEOBot > CNEOBotRetreatToCover::OnMoveToSuccess( CNEOBot *m //--------------------------------------------------------------------------------------------- EventDesiredResult< CNEOBot > CNEOBotRetreatToCover::OnMoveToFailure( CNEOBot *me, const Path *path, MoveToFailureType reason ) { + m_coverArea = FindCoverArea( me ); CNEOBotPathCompute( me, m_path, m_coverArea->GetCenter(), RETREAT_ROUTE ); CNEOBotPathReservations()->IncrementAreaAvoidPenalty(me->GetLastKnownArea()->GetID(), neo_bot_path_reservation_onstuck_penalty.GetFloat()); return TryContinue();