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..41b24701ed 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 @@ -126,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(); @@ -133,13 +133,13 @@ class CSearchForAttackCover : public ISearchSurroundingAreasFunctor { return true; } - if (CNEOBotPathReservations()->GetAreaAvoidPenalty(navAreaId) >= m_onStuckPenalty) + 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 @@ -232,7 +232,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_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/behavior/neo_bot_retreat_from_grenade.cpp b/src/game/server/neo/bot/behavior/neo_bot_retreat_from_grenade.cpp index ab97ce3c73..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 @@ -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(); @@ -207,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(); @@ -214,7 +214,7 @@ class CSearchForCoverFromGrenade : public ISearchSurroundingAreasFunctor { return true; } - if (CNEOBotPathReservations()->GetAreaAvoidPenalty(navAreaId) >= m_onStuckPenalty) + if (penalty.IsStuckProne()) { return true; } @@ -236,6 +236,7 @@ class CSearchForCoverFromGrenade : public ISearchSurroundingAreasFunctor CoverCandidate candidate; candidate.area = area; candidate.cost = travelDistanceSoFar + kCoverEnemyExposureCost * CountThreatsExposingArea( m_me, area ); + candidate.cost += penalty.flStuck; // where bots got stuck, as in path cost m_coverAreaVector.AddToTail( candidate ); return true; @@ -278,7 +279,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..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 @@ -12,13 +12,13 @@ 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) { int id = baseArea->GetID(); + const AreaPenalty_t penalty = CNEOBotPathReservations()->GetAreaPenalty(id); float candidateHazardTime = CNEOBotPathReservations()->GetAreaHazardousTime(id, m_me); if (candidateHazardTime > 0) { @@ -27,10 +27,14 @@ class CSearchForSafeArea : public ISearchSurroundingAreasFunctor baseArea->DrawFilled(255, 0, 0, MIN(255, candidateHazardTime), candidateHazardTime); } } - else if (CNEOBotPathReservations()->GetAreaAvoidPenalty(id) < m_onStuckPenalty) + else if (penalty.flStuck <= 0.0f) { m_safeAreas.AddToTail(baseArea); } + else if (!penalty.IsStuckProne()) + { + 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 +51,10 @@ class CSearchForSafeArea : public ISearchSurroundingAreasFunctor } CUtlVector m_safeAreas; + CUtlVector m_stuckOnceAreas; private: CNEOBot *m_me; - float m_onStuckPenalty; }; CNEOBotRetreatFromHazardArea::CNEOBotRetreatFromHazardArea() @@ -68,14 +72,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..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 @@ -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() ) @@ -127,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(); @@ -134,7 +137,7 @@ class CSearchForCover : public ISearchSurroundingAreasFunctor { return true; } - if (CNEOBotPathReservations()->GetAreaAvoidPenalty(navAreaId) >= m_onStuckPenalty) + if (penalty.IsStuckProne()) { return true; } @@ -143,14 +146,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 ( penalty.flStuck > 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 +191,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_cost.cpp b/src/game/server/neo/bot/neo_bot_path_cost.cpp index daab086208..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,7 +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()->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 e8a4f375b5..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_AreaAvoidPenalties.RemoveAll(); + m_AreaPenalties.RemoveAll(); } //-------------------------------------------------------------------------------------------------------------- @@ -207,6 +207,12 @@ void CNEOBotPathReservationSystem::ClearRound() m_HazardAreas[team].RemoveAll(); } m_BotReservedAreas.RemoveAll(); + + // a new round respawns everyone, so where bots got stuck starts over + FOR_EACH_MAP_FAST( m_AreaPenalties, i ) + { + m_AreaPenalties[i].flStuck = 0.0f; + } } //------------------------------------------------------------------------------------------------- @@ -233,36 +239,64 @@ int CNEOBotPathReservationSystem::GetPredictedFriendlyPathCount( int areaID, int } //------------------------------------------------------------------------------------------------- -void CNEOBotPathReservationSystem::IncrementAreaAvoidPenalty(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 = m_AreaAvoidPenalties.Find(navAreaID); - if (index == m_AreaAvoidPenalties.InvalidIndex()) + unsigned short index = m_AreaPenalties.Find(navAreaID); + if (index == m_AreaPenalties.InvalidIndex()) { - index = m_AreaAvoidPenalties.Insert(navAreaID, 0.0f); + index = m_AreaPenalties.Insert(navAreaID, AreaPenalty_t()); } + return &m_AreaPenalties[index]; +} - m_AreaAvoidPenalties[index] += penaltyAmount; +//------------------------------------------------------------------------------------------------- +void CNEOBotPathReservationSystem::IncrementAreaAvoidPenalty(unsigned int navAreaID, float penaltyAmount) +{ + if ( AreaPenalty_t *pPenalty = FindOrAddAreaPenalty(navAreaID) ) + { + pPenalty->flStuck += penaltyAmount; + } } //------------------------------------------------------------------------------------------------- -float CNEOBotPathReservationSystem::GetAreaAvoidPenalty(unsigned int navAreaID) const +void CNEOBotPathReservationSystem::IncrementAreaDeathPenalty(unsigned int navAreaID, float penaltyAmount) +{ + if ( AreaPenalty_t *pPenalty = FindOrAddAreaPenalty(navAreaID) ) + { + pPenalty->flDeath += penaltyAmount; + } +} + +//------------------------------------------------------------------------------------------------- +AreaPenalty_t CNEOBotPathReservationSystem::GetAreaPenalty(unsigned int navAreaID) const { if ( !neo_bot_path_reservation_avoid_penalty_enable.GetBool() ) { - return 0.0f; + return AreaPenalty_t(); } - unsigned short index = m_AreaAvoidPenalties.Find(navAreaID); - if (index != m_AreaAvoidPenalties.InvalidIndex()) + unsigned short index = m_AreaPenalties.Find(navAreaID); + if (index != m_AreaPenalties.InvalidIndex()) { - return m_AreaAvoidPenalties[index]; + return m_AreaPenalties[index]; } - return 0.0f; + return AreaPenalty_t(); +} + +// 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 AreaPenalty_t::IsStuckProne() const +{ + const float flStuckPronePenalty = NEO_BOT_STUCK_PRONE_AREA_COUNT * neo_bot_path_reservation_onstuck_penalty.GetFloat(); + 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 052b63a94d..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,7 +66,7 @@ class CNEOBotPathReservationSystem CNEOBotPathReservationSystem() : m_BotReservedAreas(DefLessFunc(int)) - , m_AreaAvoidPenalties(DefLessFunc(unsigned int)) + , m_AreaPenalties(DefLessFunc(unsigned int)) { for (int i = 0; i < TEAM__TOTAL; ++i) { @@ -71,7 +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; + void IncrementAreaDeathPenalty(unsigned int navAreaID, float penaltyAmount); + 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); @@ -84,7 +97,9 @@ class CNEOBotPathReservationSystem CUtlMap m_Reservations[TEAM__TOTAL]; // keyed by nav area ID CUtlMap m_BotReservedAreas; // keyed by bot entindex - CUtlMap m_AreaAvoidPenalties; + AreaPenalty_t *FindOrAddAreaPenalty(unsigned int navAreaID); + + CUtlMap m_AreaPenalties; // keyed by nav area ID CUtlMap m_HazardAreas[TEAM__TOTAL]; };