From 16fe30960f8c52fee1f1f7ee07abf4c6ae174cd2 Mon Sep 17 00:00:00 2001 From: Alan Shen Date: Fri, 9 Oct 2026 19:27:19 -0600 Subject: [PATCH 1/3] Keep death areas out of the stuck area penalty A bot's death added to the same per-area penalty as a bot getting stuck. Cover and retreat searches skip an area whose penalty reaches one stuck, and Attack's cover search ranks candidates by it before their cover, so an area where bots died lost to any area with fewer deaths even when it gave better cover. Keep deaths in their own penalty, read only as the path cost it was before; cover and retreat searches read only where bots got stuck. --- .../neo/bot/behavior/neo_bot_behavior.cpp | 4 +- src/game/server/neo/bot/neo_bot_path_cost.cpp | 1 + .../neo/bot/neo_bot_path_reservation.cpp | 43 +++++++++++++++---- .../server/neo/bot/neo_bot_path_reservation.h | 6 ++- 4 files changed, 42 insertions(+), 12 deletions(-) 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 37969a26a5..857d5fc774 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_behavior.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_behavior.cpp @@ -202,7 +202,7 @@ 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() ); + CNEOBotPathReservations()->IncrementAreaDeathPenalty( navArea->GetID(), neo_bot_path_reservation_killed_penalty.GetFloat() ); } else { @@ -210,7 +210,7 @@ EventDesiredResult CNEOBotMainAction::OnKilled( CNEOBot *me, const CTak CNavArea *nearestArea = TheNavMesh->GetNearestNavArea( me->GetAbsOrigin() ); if ( nearestArea ) { - CNEOBotPathReservations()->IncrementAreaAvoidPenalty( nearestArea->GetID(), neo_bot_path_reservation_killed_penalty.GetFloat() ); + CNEOBotPathReservations()->IncrementAreaDeathPenalty( nearestArea->GetID(), neo_bot_path_reservation_killed_penalty.GetFloat() ); } } 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 daab086208..8986fde3c8 100644 --- a/src/game/server/neo/bot/neo_bot_path_cost.cpp +++ b/src/game/server/neo/bot/neo_bot_path_cost.cpp @@ -155,6 +155,7 @@ float CNEOBotPathCost::operator()(CNavArea* baseArea, CNavArea* fromArea, const if ( !m_bIgnoreReservations && (m_routeType != FASTEST_ROUTE) ) { cost += CNEOBotPathReservations()->GetAreaAvoidPenalty(area->GetID()); + cost += CNEOBotPathReservations()->GetAreaDeathPenalty(area->GetID()); if ( NEORules()->IsTeamplay() ) { 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 e8a4f375b5..ebb5d9e18d 100644 --- a/src/game/server/neo/bot/neo_bot_path_reservation.cpp +++ b/src/game/server/neo/bot/neo_bot_path_reservation.cpp @@ -193,6 +193,7 @@ void CNEOBotPathReservationSystem::Clear() { ClearRound(); m_AreaAvoidPenalties.RemoveAll(); + m_AreaDeathPenalties.RemoveAll(); } //-------------------------------------------------------------------------------------------------------------- @@ -233,38 +234,62 @@ int CNEOBotPathReservationSystem::GetPredictedFriendlyPathCount( int areaID, int } //------------------------------------------------------------------------------------------------- -void CNEOBotPathReservationSystem::IncrementAreaAvoidPenalty(unsigned int navAreaID, float penaltyAmount) +static void IncrementAreaPenalty(CUtlMap &penalties, unsigned int navAreaID, float penaltyAmount) { if ( !neo_bot_path_reservation_avoid_penalty_enable.GetBool() ) { return; } - unsigned short index = m_AreaAvoidPenalties.Find(navAreaID); - if (index == m_AreaAvoidPenalties.InvalidIndex()) + unsigned short index = penalties.Find(navAreaID); + if (index == penalties.InvalidIndex()) { - index = m_AreaAvoidPenalties.Insert(navAreaID, 0.0f); + index = penalties.Insert(navAreaID, 0.0f); } - m_AreaAvoidPenalties[index] += penaltyAmount; + penalties[index] += penaltyAmount; } //------------------------------------------------------------------------------------------------- -float CNEOBotPathReservationSystem::GetAreaAvoidPenalty(unsigned int navAreaID) const +static float GetAreaPenalty(const CUtlMap &penalties, unsigned int navAreaID) { if ( !neo_bot_path_reservation_avoid_penalty_enable.GetBool() ) { return 0.0f; } - unsigned short index = m_AreaAvoidPenalties.Find(navAreaID); - if (index != m_AreaAvoidPenalties.InvalidIndex()) + unsigned short index = penalties.Find(navAreaID); + if (index != penalties.InvalidIndex()) { - return m_AreaAvoidPenalties[index]; + return penalties[index]; } return 0.0f; } +//------------------------------------------------------------------------------------------------- +void CNEOBotPathReservationSystem::IncrementAreaAvoidPenalty(unsigned int navAreaID, float penaltyAmount) +{ + IncrementAreaPenalty(m_AreaAvoidPenalties, navAreaID, penaltyAmount); +} + +//------------------------------------------------------------------------------------------------- +float CNEOBotPathReservationSystem::GetAreaAvoidPenalty(unsigned int navAreaID) const +{ + return GetAreaPenalty(m_AreaAvoidPenalties, navAreaID); +} + +//------------------------------------------------------------------------------------------------- +void CNEOBotPathReservationSystem::IncrementAreaDeathPenalty(unsigned int navAreaID, float penaltyAmount) +{ + IncrementAreaPenalty(m_AreaDeathPenalties, navAreaID, penaltyAmount); +} + +//------------------------------------------------------------------------------------------------- +float CNEOBotPathReservationSystem::GetAreaDeathPenalty(unsigned int navAreaID) const +{ + return GetAreaPenalty(m_AreaDeathPenalties, navAreaID); +} + //------------------------------------------------------------------------------------------------- // Functor to propagate deadly hazard to PVS-adjacent areas struct CNEOFunctorPropagatePVSDeadlyHazard 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 052b63a94d..2bc1ccb90a 100644 --- a/src/game/server/neo/bot/neo_bot_path_reservation.h +++ b/src/game/server/neo/bot/neo_bot_path_reservation.h @@ -55,6 +55,7 @@ class CNEOBotPathReservationSystem CNEOBotPathReservationSystem() : m_BotReservedAreas(DefLessFunc(int)) , m_AreaAvoidPenalties(DefLessFunc(unsigned int)) + , m_AreaDeathPenalties(DefLessFunc(unsigned int)) { for (int i = 0; i < TEAM__TOTAL; ++i) { @@ -72,6 +73,8 @@ class CNEOBotPathReservationSystem void IncrementAreaAvoidPenalty(unsigned int navAreaID, float penaltyAmount); float GetAreaAvoidPenalty(unsigned int navAreaID) const; + void IncrementAreaDeathPenalty(unsigned int navAreaID, float penaltyAmount); + float GetAreaDeathPenalty(unsigned int navAreaID) const; void AddDeadlyHazard(int navAreaID, float expireTime, int teamID, bool propagatePVS = false); void AddFragHazard(int navAreaID, float expireTime, int teamID); @@ -84,7 +87,8 @@ class CNEOBotPathReservationSystem CUtlMap m_Reservations[TEAM__TOTAL]; // keyed by nav area ID CUtlMap m_BotReservedAreas; // keyed by bot entindex - CUtlMap m_AreaAvoidPenalties; + CUtlMap m_AreaAvoidPenalties; // where bots got stuck: cover and retreat searches skip these areas + CUtlMap m_AreaDeathPenalties; // where bots died: only a path cost, an area can still be good cover CUtlMap m_HazardAreas[TEAM__TOTAL]; }; From 56683e2f6684497132107820c4d7a95992787f72 Mon Sep 17 00:00:00 2001 From: Alan Shen Date: Fri, 9 Oct 2026 19:33:28 -0600 Subject: [PATCH 2/3] Forget stuck areas each round, skip only repeats A bot getting stuck once made its area unusable as cover, retreat goal or safe area for every bot until the map changed: 9-21 % of cover and retreat picks on ntre_apparatus_dm skipped such an area. One stuck can be a crowd or a pushed prop. Clear stuck areas with the other round state, and skip an area only after bots got stuck there twice in the round. Below that, the stucks are a cost: Attack already ranks cover by them, RetreatToCover counts the area as exposed to one more threat, grenade cover adds them as path cost does, and a hazard retreat uses such areas only as a fallback. --- .../neo/bot/behavior/neo_bot_attack.cpp | 4 +--- .../behavior/neo_bot_retreat_from_grenade.cpp | 5 ++--- .../neo_bot_retreat_from_hazard_area.cpp | 16 +++++++++------ .../bot/behavior/neo_bot_retreat_to_cover.cpp | 20 +++++++++++++------ src/game/server/neo/bot/neo_bot.cpp | 2 +- .../neo/bot/neo_bot_path_reservation.cpp | 15 +++++++++++++- .../server/neo/bot/neo_bot_path_reservation.h | 3 ++- 7 files changed, 44 insertions(+), 21 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 5dd1979554..1e5f6d345c 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_attack.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_attack.cpp @@ -69,7 +69,6 @@ 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(); } bool IsBetterCandidate( CNavArea *area, float avoidPenalty ) const @@ -133,7 +132,7 @@ class CSearchForAttackCover : public ISearchSurroundingAreasFunctor { return true; } - if (CNEOBotPathReservations()->GetAreaAvoidPenalty(navAreaId) >= m_onStuckPenalty) + if (CNEOBotPathReservations()->IsAreaStuckProne(navAreaId)) { return true; } @@ -232,7 +231,6 @@ 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 }; 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 ab97ce3c73..b94e14da30 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 @@ -165,7 +165,6 @@ class CSearchForCoverFromGrenade : public ISearchSurroundingAreasFunctor { m_me = me; m_grenade = grenade; - m_onStuckPenalty = neo_bot_path_reservation_onstuck_penalty.GetFloat(); m_coverDist = CNEOBotRetreatFromGrenade::GetGrenadeCoverDistance(); m_blastRadius = sv_neo_grenade_blast_radius.GetFloat(); @@ -214,7 +213,7 @@ class CSearchForCoverFromGrenade : public ISearchSurroundingAreasFunctor { return true; } - if (CNEOBotPathReservations()->GetAreaAvoidPenalty(navAreaId) >= m_onStuckPenalty) + if (CNEOBotPathReservations()->IsAreaStuckProne(navAreaId)) { return true; } @@ -236,6 +235,7 @@ class CSearchForCoverFromGrenade : public ISearchSurroundingAreasFunctor CoverCandidate candidate; candidate.area = area; candidate.cost = travelDistanceSoFar + kCoverEnemyExposureCost * CountThreatsExposingArea( m_me, area ); + candidate.cost += CNEOBotPathReservations()->GetAreaAvoidPenalty( area->GetID() ); // where bots got stuck, as in path cost m_coverAreaVector.AddToTail( candidate ); return true; @@ -278,7 +278,6 @@ class CSearchForCoverFromGrenade : public ISearchSurroundingAreasFunctor CNEOBot *m_me; CBaseEntity *m_grenade; - float m_onStuckPenalty; float m_coverDist; float m_blastRadius; Vector m_vecDangerStart; 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 a4dd3d868f..39c69a44ed 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 @@ -12,7 +12,6 @@ class CSearchForSafeArea : public ISearchSurroundingAreasFunctor public: CSearchForSafeArea(CNEOBot *me) : m_me(me) - , m_onStuckPenalty(neo_bot_path_reservation_onstuck_penalty.GetFloat()) { } @@ -27,10 +26,14 @@ class CSearchForSafeArea : public ISearchSurroundingAreasFunctor baseArea->DrawFilled(255, 0, 0, MIN(255, candidateHazardTime), candidateHazardTime); } } - else if (CNEOBotPathReservations()->GetAreaAvoidPenalty(id) < m_onStuckPenalty) + else if (CNEOBotPathReservations()->GetAreaAvoidPenalty(id) <= 0.0f) { m_safeAreas.AddToTail(baseArea); } + else if (!CNEOBotPathReservations()->IsAreaStuckProne(id)) + { + m_stuckOnceAreas.AddToTail(baseArea); // a fallback when no area free of stucks is found + } if (m_safeAreas.Count() >= MAX_NON_HAZARD_AREA_CANDIDATES) { @@ -47,10 +50,10 @@ class CSearchForSafeArea : public ISearchSurroundingAreasFunctor } CUtlVector m_safeAreas; + CUtlVector m_stuckOnceAreas; private: CNEOBot *m_me; - float m_onStuckPenalty; }; CNEOBotRetreatFromHazardArea::CNEOBotRetreatFromHazardArea() @@ -68,14 +71,15 @@ CNavArea *CNEOBotRetreatFromHazardArea::FindSafeArea(CNEOBot *me) CSearchForSafeArea search(me); SearchSurroundingAreas(start, search); - if (search.m_safeAreas.Count() == 0) + const CUtlVector &candidates = search.m_safeAreas.Count() ? search.m_safeAreas : search.m_stuckOnceAreas; + if (candidates.Count() == 0) { return nullptr; } - int last = MIN(MAX_NON_HAZARD_AREA_CANDIDATES, search.m_safeAreas.Count()); + int last = MIN(MAX_NON_HAZARD_AREA_CANDIDATES, candidates.Count()); int which = RandomInt(0, last - 1); - return search.m_safeAreas[which]; + return candidates[which]; } ActionResult CNEOBotRetreatFromHazardArea::OnStart(CNEOBot *me, Action *priorAction) 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 0f4a332a6b..09259222c5 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 @@ -106,6 +106,9 @@ int CountThreatsExposingArea( CNEOBot *me, CNavArea *area ) } +// A cover area where a bot got stuck this round counts as exposed to this many more threats +static constexpr int NEO_BOT_STUCK_AREA_EXPOSURE = 1; + // collect nearby areas that provide cover from our known threats class CSearchForCover : public ISearchSurroundingAreasFunctor { @@ -113,7 +116,6 @@ 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() ) @@ -134,7 +136,7 @@ class CSearchForCover : public ISearchSurroundingAreasFunctor { return true; } - if (CNEOBotPathReservations()->GetAreaAvoidPenalty(navAreaId) >= m_onStuckPenalty) + if (CNEOBotPathReservations()->IsAreaStuckProne(navAreaId)) { return true; } @@ -143,14 +145,21 @@ class CSearchForCover : public ISearchSurroundingAreasFunctor CTestAreaAgainstThreats test( m_me, area ); m_me->GetVisionInterface()->ForEachKnownEntity( test ); - if ( test.m_exposedThreatCount <= m_minExposureCount ) + // an area where a bot got stuck this round counts as exposed to one more threat + int exposure = test.m_exposedThreatCount; + if ( CNEOBotPathReservations()->GetAreaAvoidPenalty( area->GetID() ) > 0.0f ) + { + exposure += NEO_BOT_STUCK_AREA_EXPOSURE; + } + + if ( exposure <= m_minExposureCount ) { // this area is at least as good as already found cover - if ( test.m_exposedThreatCount < m_minExposureCount ) + if ( exposure < m_minExposureCount ) { // this area is better than already found cover - throw out list and start over m_coverAreaVector.RemoveAll(); - m_minExposureCount = test.m_exposedThreatCount; + m_minExposureCount = exposure; } m_coverAreaVector.AddToTail( area ); @@ -181,7 +190,6 @@ class CSearchForCover : public ISearchSurroundingAreasFunctor CNEOBot *m_me; CUtlVector< CNavArea * > m_coverAreaVector; int m_minExposureCount; - float m_onStuckPenalty; }; diff --git a/src/game/server/neo/bot/neo_bot.cpp b/src/game/server/neo/bot/neo_bot.cpp index f98c9c60e1..d1ab9b68ad 100644 --- a/src/game/server/neo/bot/neo_bot.cpp +++ b/src/game/server/neo/bot/neo_bot.cpp @@ -3062,7 +3062,7 @@ CNEOBotIntention::~CNEOBotIntention() static void CNEOBotApplyOnStuckAreaPenalty( CNEOBot *me ) { - // NEO Jank: For the current match, all bots share where they get stuck. + // NEO Jank: For the current round, all bots share where they get stuck. // The reasoning is that bots on either team will get stuck in their respective half of the map // so the overall fairness may balance out for both teams sharing common sticking points. if ( const CNavArea *navArea = me->GetLastKnownArea() ) 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 ebb5d9e18d..fb0cf7c02d 100644 --- a/src/game/server/neo/bot/neo_bot_path_reservation.cpp +++ b/src/game/server/neo/bot/neo_bot_path_reservation.cpp @@ -192,7 +192,6 @@ void CNEOBotPathReservationSystem::ReleaseAllAreas(CNEOBot *bot) void CNEOBotPathReservationSystem::Clear() { ClearRound(); - m_AreaAvoidPenalties.RemoveAll(); m_AreaDeathPenalties.RemoveAll(); } @@ -208,6 +207,9 @@ void CNEOBotPathReservationSystem::ClearRound() m_HazardAreas[team].RemoveAll(); } m_BotReservedAreas.RemoveAll(); + + // a new round respawns everyone, so where bots got stuck starts over + m_AreaAvoidPenalties.RemoveAll(); } //------------------------------------------------------------------------------------------------- @@ -278,6 +280,17 @@ float CNEOBotPathReservationSystem::GetAreaAvoidPenalty(unsigned int navAreaID) return GetAreaPenalty(m_AreaAvoidPenalties, navAreaID); } +// Cover and retreat searches skip an area once bots got stuck in it this many times in the round; +// an area with fewer stucks is only a cost to them, since one stuck can be a crowd or a pushed prop +static constexpr int NEO_BOT_STUCK_PRONE_AREA_COUNT = 2; + +//------------------------------------------------------------------------------------------------- +bool CNEOBotPathReservationSystem::IsAreaStuckProne(unsigned int navAreaID) const +{ + const float flStuckPronePenalty = NEO_BOT_STUCK_PRONE_AREA_COUNT * neo_bot_path_reservation_onstuck_penalty.GetFloat(); + return GetAreaAvoidPenalty(navAreaID) >= flStuckPronePenalty; +} + //------------------------------------------------------------------------------------------------- void CNEOBotPathReservationSystem::IncrementAreaDeathPenalty(unsigned int navAreaID, float penaltyAmount) { 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 2bc1ccb90a..ee2230824c 100644 --- a/src/game/server/neo/bot/neo_bot_path_reservation.h +++ b/src/game/server/neo/bot/neo_bot_path_reservation.h @@ -73,6 +73,7 @@ class CNEOBotPathReservationSystem void IncrementAreaAvoidPenalty(unsigned int navAreaID, float penaltyAmount); float GetAreaAvoidPenalty(unsigned int navAreaID) const; + bool IsAreaStuckProne(unsigned int navAreaID) const; void IncrementAreaDeathPenalty(unsigned int navAreaID, float penaltyAmount); float GetAreaDeathPenalty(unsigned int navAreaID) const; @@ -87,7 +88,7 @@ class CNEOBotPathReservationSystem CUtlMap m_Reservations[TEAM__TOTAL]; // keyed by nav area ID CUtlMap m_BotReservedAreas; // keyed by bot entindex - CUtlMap m_AreaAvoidPenalties; // where bots got stuck: cover and retreat searches skip these areas + CUtlMap m_AreaAvoidPenalties; // where bots got stuck this round: a cost to cover and retreat searches, then a filter CUtlMap m_AreaDeathPenalties; // where bots died: only a path cost, an area can still be good cover CUtlMap m_HazardAreas[TEAM__TOTAL]; }; From be0fbfa3c31dd8eb297c23a9b0a969ea087b7016 Mon Sep 17 00:00:00 2001 From: Alan Shen Date: Fri, 9 Oct 2026 20:59:22 -0600 Subject: [PATCH 3/3] Keep an area's penalties in one struct The stuck and death penalties lived in two maps, so path cost and the cover and retreat searches looked an area up once per penalty type, two or three times per area. Keep both in one map of per-area structs: each reader looks the area up once and reads the sum or the type it needs, and a new penalty type is one more field. A new round clears the stuck field in place and map load clears the map, as before. The hazard marks stay apart: they are per team and expire by time, not by round. --- .../neo/bot/behavior/neo_bot_attack.cpp | 5 +- .../behavior/neo_bot_retreat_from_grenade.cpp | 5 +- .../neo_bot_retreat_from_hazard_area.cpp | 5 +- .../bot/behavior/neo_bot_retreat_to_cover.cpp | 5 +- src/game/server/neo/bot/neo_bot_path_cost.cpp | 3 +- .../neo/bot/neo_bot_path_reservation.cpp | 70 +++++++++---------- .../server/neo/bot/neo_bot_path_reservation.h | 24 +++++-- 7 files changed, 63 insertions(+), 54 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 1e5f6d345c..41b24701ed 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_attack.cpp +++ b/src/game/server/neo/bot/behavior/neo_bot_attack.cpp @@ -125,6 +125,7 @@ class CSearchForAttackCover : public ISearchSurroundingAreasFunctor } // Skip areas that are hazardous or where bots get stuck + const AreaPenalty_t penalty = CNEOBotPathReservations()->GetAreaPenalty( area->GetID() ); if ( neo_bot_path_reservation_enable.GetBool() ) { int navAreaId = area->GetID(); @@ -132,13 +133,13 @@ class CSearchForAttackCover : public ISearchSurroundingAreasFunctor { return true; } - if (CNEOBotPathReservations()->IsAreaStuckProne(navAreaId)) + if (penalty.IsStuckProne()) { return true; } } - float avoidPenalty = CNEOBotPathReservations()->GetAreaAvoidPenalty( area->GetID() ); + float avoidPenalty = penalty.flStuck; if ( !IsBetterCandidate( area, avoidPenalty ) ) { return true; // the cover candidate we already have is at least as good 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 b94e14da30..89e843dd8f 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 @@ -206,6 +206,7 @@ class CSearchForCoverFromGrenade : public ISearchSurroundingAreasFunctor } // Skip areas that are hazardous or where bots get stuck + const AreaPenalty_t penalty = CNEOBotPathReservations()->GetAreaPenalty( area->GetID() ); if ( neo_bot_path_reservation_enable.GetBool() ) { int navAreaId = area->GetID(); @@ -213,7 +214,7 @@ class CSearchForCoverFromGrenade : public ISearchSurroundingAreasFunctor { return true; } - if (CNEOBotPathReservations()->IsAreaStuckProne(navAreaId)) + if (penalty.IsStuckProne()) { return true; } @@ -235,7 +236,7 @@ class CSearchForCoverFromGrenade : public ISearchSurroundingAreasFunctor CoverCandidate candidate; candidate.area = area; candidate.cost = travelDistanceSoFar + kCoverEnemyExposureCost * CountThreatsExposingArea( m_me, area ); - candidate.cost += CNEOBotPathReservations()->GetAreaAvoidPenalty( area->GetID() ); // where bots got stuck, as in path cost + candidate.cost += penalty.flStuck; // where bots got stuck, as in path cost m_coverAreaVector.AddToTail( candidate ); return true; 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 39c69a44ed..99057cbbfa 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 @@ -18,6 +18,7 @@ class CSearchForSafeArea : public ISearchSurroundingAreasFunctor virtual bool operator()(CNavArea *baseArea, CNavArea *priorArea, float travelDistanceSoFar) { int id = baseArea->GetID(); + const AreaPenalty_t penalty = CNEOBotPathReservations()->GetAreaPenalty(id); float candidateHazardTime = CNEOBotPathReservations()->GetAreaHazardousTime(id, m_me); if (candidateHazardTime > 0) { @@ -26,11 +27,11 @@ class CSearchForSafeArea : public ISearchSurroundingAreasFunctor baseArea->DrawFilled(255, 0, 0, MIN(255, candidateHazardTime), candidateHazardTime); } } - else if (CNEOBotPathReservations()->GetAreaAvoidPenalty(id) <= 0.0f) + else if (penalty.flStuck <= 0.0f) { m_safeAreas.AddToTail(baseArea); } - else if (!CNEOBotPathReservations()->IsAreaStuckProne(id)) + else if (!penalty.IsStuckProne()) { m_stuckOnceAreas.AddToTail(baseArea); // a fallback when no area free of stucks is found } 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 09259222c5..218b61624f 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 @@ -129,6 +129,7 @@ class CSearchForCover : public ISearchSurroundingAreasFunctor CNavArea *area = (CNavArea *)baseArea; // Skip areas that are hazardous or where bots get stuck + const AreaPenalty_t penalty = CNEOBotPathReservations()->GetAreaPenalty( area->GetID() ); if ( neo_bot_path_reservation_enable.GetBool() ) { int navAreaId = area->GetID(); @@ -136,7 +137,7 @@ class CSearchForCover : public ISearchSurroundingAreasFunctor { return true; } - if (CNEOBotPathReservations()->IsAreaStuckProne(navAreaId)) + if (penalty.IsStuckProne()) { return true; } @@ -147,7 +148,7 @@ class CSearchForCover : public ISearchSurroundingAreasFunctor // an area where a bot got stuck this round counts as exposed to one more threat int exposure = test.m_exposedThreatCount; - if ( CNEOBotPathReservations()->GetAreaAvoidPenalty( area->GetID() ) > 0.0f ) + if ( penalty.flStuck > 0.0f ) { exposure += NEO_BOT_STUCK_AREA_EXPOSURE; } 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 8986fde3c8..9fc6b8a738 100644 --- a/src/game/server/neo/bot/neo_bot_path_cost.cpp +++ b/src/game/server/neo/bot/neo_bot_path_cost.cpp @@ -154,8 +154,7 @@ float CNEOBotPathCost::operator()(CNavArea* baseArea, CNavArea* fromArea, const // New path reservation related cost adjustments if ( !m_bIgnoreReservations && (m_routeType != FASTEST_ROUTE) ) { - cost += CNEOBotPathReservations()->GetAreaAvoidPenalty(area->GetID()); - cost += CNEOBotPathReservations()->GetAreaDeathPenalty(area->GetID()); + cost += CNEOBotPathReservations()->GetAreaPenalty(area->GetID()).GetPathCost(); if ( NEORules()->IsTeamplay() ) { 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 fb0cf7c02d..9d3fce432a 100644 --- a/src/game/server/neo/bot/neo_bot_path_reservation.cpp +++ b/src/game/server/neo/bot/neo_bot_path_reservation.cpp @@ -192,7 +192,7 @@ void CNEOBotPathReservationSystem::ReleaseAllAreas(CNEOBot *bot) void CNEOBotPathReservationSystem::Clear() { ClearRound(); - m_AreaDeathPenalties.RemoveAll(); + m_AreaPenalties.RemoveAll(); } //-------------------------------------------------------------------------------------------------------------- @@ -209,7 +209,10 @@ void CNEOBotPathReservationSystem::ClearRound() m_BotReservedAreas.RemoveAll(); // a new round respawns everyone, so where bots got stuck starts over - m_AreaAvoidPenalties.RemoveAll(); + FOR_EACH_MAP_FAST( m_AreaPenalties, i ) + { + m_AreaPenalties[i].flStuck = 0.0f; + } } //------------------------------------------------------------------------------------------------- @@ -236,48 +239,53 @@ int CNEOBotPathReservationSystem::GetPredictedFriendlyPathCount( int areaID, int } //------------------------------------------------------------------------------------------------- -static void IncrementAreaPenalty(CUtlMap &penalties, unsigned int navAreaID, float penaltyAmount) +AreaPenalty_t *CNEOBotPathReservationSystem::FindOrAddAreaPenalty(unsigned int navAreaID) { if ( !neo_bot_path_reservation_avoid_penalty_enable.GetBool() ) { - return; + return nullptr; } - unsigned short index = penalties.Find(navAreaID); - if (index == penalties.InvalidIndex()) + unsigned short index = m_AreaPenalties.Find(navAreaID); + if (index == m_AreaPenalties.InvalidIndex()) { - index = penalties.Insert(navAreaID, 0.0f); + index = m_AreaPenalties.Insert(navAreaID, AreaPenalty_t()); } - - penalties[index] += penaltyAmount; + return &m_AreaPenalties[index]; } //------------------------------------------------------------------------------------------------- -static float GetAreaPenalty(const CUtlMap &penalties, unsigned int navAreaID) +void CNEOBotPathReservationSystem::IncrementAreaAvoidPenalty(unsigned int navAreaID, float penaltyAmount) { - if ( !neo_bot_path_reservation_avoid_penalty_enable.GetBool() ) + if ( AreaPenalty_t *pPenalty = FindOrAddAreaPenalty(navAreaID) ) { - return 0.0f; + pPenalty->flStuck += penaltyAmount; } - - unsigned short index = penalties.Find(navAreaID); - if (index != penalties.InvalidIndex()) - { - return penalties[index]; - } - return 0.0f; } //------------------------------------------------------------------------------------------------- -void CNEOBotPathReservationSystem::IncrementAreaAvoidPenalty(unsigned int navAreaID, float penaltyAmount) +void CNEOBotPathReservationSystem::IncrementAreaDeathPenalty(unsigned int navAreaID, float penaltyAmount) { - IncrementAreaPenalty(m_AreaAvoidPenalties, navAreaID, penaltyAmount); + if ( AreaPenalty_t *pPenalty = FindOrAddAreaPenalty(navAreaID) ) + { + pPenalty->flDeath += penaltyAmount; + } } //------------------------------------------------------------------------------------------------- -float CNEOBotPathReservationSystem::GetAreaAvoidPenalty(unsigned int navAreaID) const +AreaPenalty_t CNEOBotPathReservationSystem::GetAreaPenalty(unsigned int navAreaID) const { - return GetAreaPenalty(m_AreaAvoidPenalties, navAreaID); + if ( !neo_bot_path_reservation_avoid_penalty_enable.GetBool() ) + { + return AreaPenalty_t(); + } + + unsigned short index = m_AreaPenalties.Find(navAreaID); + if (index != m_AreaPenalties.InvalidIndex()) + { + return m_AreaPenalties[index]; + } + return AreaPenalty_t(); } // Cover and retreat searches skip an area once bots got stuck in it this many times in the round; @@ -285,22 +293,10 @@ float CNEOBotPathReservationSystem::GetAreaAvoidPenalty(unsigned int navAreaID) static constexpr int NEO_BOT_STUCK_PRONE_AREA_COUNT = 2; //------------------------------------------------------------------------------------------------- -bool CNEOBotPathReservationSystem::IsAreaStuckProne(unsigned int navAreaID) const +bool AreaPenalty_t::IsStuckProne() const { const float flStuckPronePenalty = NEO_BOT_STUCK_PRONE_AREA_COUNT * neo_bot_path_reservation_onstuck_penalty.GetFloat(); - return GetAreaAvoidPenalty(navAreaID) >= flStuckPronePenalty; -} - -//------------------------------------------------------------------------------------------------- -void CNEOBotPathReservationSystem::IncrementAreaDeathPenalty(unsigned int navAreaID, float penaltyAmount) -{ - IncrementAreaPenalty(m_AreaDeathPenalties, navAreaID, penaltyAmount); -} - -//------------------------------------------------------------------------------------------------- -float CNEOBotPathReservationSystem::GetAreaDeathPenalty(unsigned int navAreaID) const -{ - return GetAreaPenalty(m_AreaDeathPenalties, navAreaID); + return flStuck >= flStuckPronePenalty; } //------------------------------------------------------------------------------------------------- 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 ee2230824c..0354981331 100644 --- a/src/game/server/neo/bot/neo_bot_path_reservation.h +++ b/src/game/server/neo/bot/neo_bot_path_reservation.h @@ -23,6 +23,18 @@ struct AreaReservation_t AreaReservation_t& operator=(const AreaReservation_t& src) { claims = src.claims; return *this; } }; +// What bots learned about a nav area this match, kept as path cost penalties +struct AreaPenalty_t +{ + // where bots got stuck this round: a path cost, and to cover and retreat searches a cost, then a filter + float flStuck = 0.0f; + // where bots died: only a path cost, since an area where bots died can still be good cover + float flDeath = 0.0f; + + float GetPathCost() const { return flStuck + flDeath; } + bool IsStuckProne() const; +}; + struct HazardInfo { float smokeExpireTime; // when the smoke hazard risk expires @@ -54,8 +66,7 @@ class CNEOBotPathReservationSystem CNEOBotPathReservationSystem() : m_BotReservedAreas(DefLessFunc(int)) - , m_AreaAvoidPenalties(DefLessFunc(unsigned int)) - , m_AreaDeathPenalties(DefLessFunc(unsigned int)) + , m_AreaPenalties(DefLessFunc(unsigned int)) { for (int i = 0; i < TEAM__TOTAL; ++i) { @@ -72,10 +83,8 @@ class CNEOBotPathReservationSystem int GetPredictedFriendlyPathCount( int areaID, int teamID, const CNEOBot *excluding = NULL ) const; void IncrementAreaAvoidPenalty(unsigned int navAreaID, float penaltyAmount); - float GetAreaAvoidPenalty(unsigned int navAreaID) const; - bool IsAreaStuckProne(unsigned int navAreaID) const; void IncrementAreaDeathPenalty(unsigned int navAreaID, float penaltyAmount); - float GetAreaDeathPenalty(unsigned int navAreaID) const; + AreaPenalty_t GetAreaPenalty(unsigned int navAreaID) const; void AddDeadlyHazard(int navAreaID, float expireTime, int teamID, bool propagatePVS = false); void AddFragHazard(int navAreaID, float expireTime, int teamID); @@ -88,8 +97,9 @@ class CNEOBotPathReservationSystem CUtlMap m_Reservations[TEAM__TOTAL]; // keyed by nav area ID CUtlMap m_BotReservedAreas; // keyed by bot entindex - CUtlMap m_AreaAvoidPenalties; // where bots got stuck this round: a cost to cover and retreat searches, then a filter - CUtlMap m_AreaDeathPenalties; // where bots died: only a path cost, an area can still be good cover + AreaPenalty_t *FindOrAddAreaPenalty(unsigned int navAreaID); + + CUtlMap m_AreaPenalties; // keyed by nav area ID CUtlMap m_HazardAreas[TEAM__TOTAL]; };