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 99057cbbfa..9764b46865 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 @@ -135,6 +135,20 @@ ActionResult CNEOBotRetreatFromHazardArea::Update(CNEOBot *me, float in CNEOBotPathCompute(me, m_path, m_safeArea->GetCenter(), RETREAT_ROUTE); } + // Possibly don't want to cloak with grenade/smoke hazards yet + // because in such situations either the threat noticed you already + // or because they didn't notice you yet, you shouldn't announce your position + if (CNEOBotPathReservations()->IsAreaNpcTurretHazard(myArea->GetID(), me)) + { + me->EnableCloak(3.0f); + if (me->IsCloakEnabled()) + { + // The tank's motion vision only ignores cloaked players under 180 u/s + me->ReleaseRunButton(); + me->PressSneakButton(0.3f); + } + } + m_path.Update(me); return Continue(); } 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 a75c482872..dd3bd90559 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 @@ -316,6 +316,33 @@ ActionResult< CNEOBot > CNEOBotTacticalMonitor::WatchForLadders( CNEOBot *me ) } +//----------------------------------------------------------------------------------------- +// A bot in a hurry does not retreat from the tank, so it cloaks in the tank's line of fire and keeps going: +// the tank's motion vision ignores cloaked players under 180 u/s +void CNEOBotTacticalMonitor::CloakPastNpcTurret( CNEOBot *me ) +{ + if ( me->m_hCommandingPlayer.Get() ) + { + return; + } + + CNavArea *myArea = me->GetLastKnownArea(); + if ( !myArea || !CNEOBotPathReservations()->IsAreaNpcTurretHazard( myArea->GetID(), me ) ) + { + return; + } + + me->EnableCloak( 3.0f ); + + // An Assault stays under that speed unless it sprints, while a Recon is over it anyway and keeps its pace + if ( me->IsCloakEnabled() && ( me->GetClass() == NEO_CLASS_ASSAULT ) ) + { + // the child behaviors moved first this update, so this cancels their run press + me->ReleaseRunButton(); + } +} + + //----------------------------------------------------------------------------------------- ActionResult< CNEOBot > CNEOBotTacticalMonitor::Update( CNEOBot *me, float interval ) { @@ -394,6 +421,10 @@ ActionResult< CNEOBot > CNEOBotTacticalMonitor::Update( CNEOBot *me, float inter return scavengeResult; } } + else + { + CloakPastNpcTurret( me ); + } #if 0 // NEO TODO (Adam) search for dropped weapons to resupply ammunition bool isAvailable = ( me->GetIntentionInterface()->ShouldHurry( me ) != ANSWER_YES ); 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 017c6a15c3..7a00cad38d 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 @@ -41,4 +41,5 @@ class CNEOBotTacticalMonitor : public Action< CNEOBot > void AvoidBumpingFriends(CNEOBot* me); ActionResult< CNEOBot > WatchForLadders(CNEOBot* me); + void CloakPastNpcTurret(CNEOBot* me); }; 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 77b9173413..b68b1bed47 100644 --- a/src/game/server/neo/bot/neo_bot_path_reservation.cpp +++ b/src/game/server/neo/bot/neo_bot_path_reservation.cpp @@ -340,6 +340,7 @@ void CNEOBotPathReservationSystem::AddDeadlyHazard(int navAreaID, float expireTi blank.hazardExpireTime = expireTime; blank.smokeExpireTime = 0.0f; blank.smokePropagateTime = 0.0f; + blank.npcTurretExpireTime = 0.0f; index = m_HazardAreas[teamID].Insert(navAreaID, blank); } else @@ -414,6 +415,7 @@ void CNEOBotPathReservationSystem::AddSmokeHazard(int navAreaID, float expireTim blank.hazardExpireTime = 0.0f; blank.smokeExpireTime = expireTime; blank.smokePropagateTime = 0.0f; + blank.npcTurretExpireTime = 0.0f; index = m_HazardAreas[teamID].Insert(navAreaID, blank); } else @@ -444,6 +446,90 @@ void CNEOBotPathReservationSystem::AddSmokeHazard(int navAreaID, float expireTim } } +//------------------------------------------------------------------------------------------------- +// Functor to propagate a neo_npc_targetsystem turret hazard to PVS-adjacent areas +struct CNEOFunctorPropagatePVSNpcTurretHazard +{ + CNEOFunctorPropagatePVSNpcTurretHazard(float expireTime, int teamID) + : m_expireTime(expireTime), m_teamID(teamID) + { + } + + bool operator()(CNavArea *area) + { + CNEOBotPathReservations()->AddNpcTurretHazard(area->GetID(), m_expireTime, m_teamID, false); + return true; + } + + float m_expireTime; + int m_teamID; +}; + +//------------------------------------------------------------------------------------------------- +// Area tagged by neo_npc_targetsystem turret, whose motion vision +// cannot see slow cloaked players, so bots escaping it should cloak +void CNEOBotPathReservationSystem::AddNpcTurretHazard(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; + } + + // Kept apart from deadly hazards, so a bot in a hurry can accept the turret's risk + int index = m_HazardAreas[teamID].Find(navAreaID); + if ( !m_HazardAreas[teamID].IsValidIndex(index) ) + { + HazardInfo blank; + blank.hazardExpireTime = 0.0f; + blank.smokeExpireTime = 0.0f; + blank.smokePropagateTime = 0.0f; + blank.npcTurretExpireTime = 0.0f; + index = m_HazardAreas[teamID].Insert(navAreaID, blank); + } + + // Inform the bots in every area the turret can see to turn on cloak + m_HazardAreas[teamID][index].npcTurretExpireTime = expireTime; + + if ( propagatePVS ) + { + CNavArea *area = TheNavMesh->GetNavAreaByID(navAreaID); + if (area) + { + // Same visible set as a propagated deadly hazard + CNEOFunctorPropagatePVSNpcTurretHazard propagate(expireTime, teamID); + area->ForAllCompletelyVisibleAreas(propagate); + } + } +} + +//------------------------------------------------------------------------------------------------- +bool CNEOBotPathReservationSystem::IsAreaNpcTurretHazard(int navAreaID, const CNEOBot *me) const +{ + if ( !neo_bot_path_reservation_avoid_penalty_enable.GetBool() || !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) ) + { + return false; + } + + return m_HazardAreas[teamID][index].npcTurretExpireTime > gpGlobals->curtime; +} + //------------------------------------------------------------------------------------------------- float CNEOBotPathReservationSystem::GetAreaHazardousTime(int navAreaID, const CNEOBot *me) const { @@ -476,6 +562,12 @@ float CNEOBotPathReservationSystem::GetAreaHazardousTime(int navAreaID, const CN return hazardTimeLeft; } + auto npcTurretTimeLeft = h.npcTurretExpireTime - gpGlobals->curtime; + if (npcTurretTimeLeft > 0) + { + return npcTurretTimeLeft; + } + // Support class can see through smoke if (me->GetClass() != NEO_CLASS_SUPPORT) { @@ -497,7 +589,7 @@ bool CNEOBotPathReservationSystem::IsAreaHazardous(int navAreaID, const CNEOBot } //------------------------------------------------------------------------------------------------- -// Unlike IsAreaHazardous, smoke does not count: only marks that can kill, such as a frag grenade's blast area +// Unlike IsAreaHazardous, smoke and the turret's sightline do not count: only marks that can kill, such as a frag grenade's blast area bool CNEOBotPathReservationSystem::IsAreaDeadlyHazard(int navAreaID, const CNEOBot *me) const { if (!neo_bot_path_reservation_avoid_penalty_enable.GetBool()) 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 b6a411e606..d0ae6539fe 100644 --- a/src/game/server/neo/bot/neo_bot_path_reservation.h +++ b/src/game/server/neo/bot/neo_bot_path_reservation.h @@ -40,6 +40,7 @@ struct HazardInfo float smokeExpireTime; // when the smoke hazard risk expires float hazardExpireTime; // when a general deadly hazard risk expires float smokePropagateTime; // expiry this area has already propagated to its visible set + float npcTurretExpireTime; // when a neo_npc_targetsystem hazard expires }; struct BotReservedAreas_t @@ -89,6 +90,8 @@ class CNEOBotPathReservationSystem 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); + void AddNpcTurretHazard(int navAreaID, float expireTime, int teamID, bool propagatePVS = true); + bool IsAreaNpcTurretHazard(int navAreaID, const CNEOBot *me) const; float GetAreaHazardousTime(int navAreaID, const CNEOBot *me) const; bool IsAreaHazardous(int navAreaID, const CNEOBot *me) const; bool IsAreaDeadlyHazard(int navAreaID, const CNEOBot *me) const; diff --git a/src/game/server/neo/neo_npc_targetsystem.cpp b/src/game/server/neo/neo_npc_targetsystem.cpp index 0923545928..c49480651d 100644 --- a/src/game/server/neo/neo_npc_targetsystem.cpp +++ b/src/game/server/neo/neo_npc_targetsystem.cpp @@ -1,11 +1,19 @@ #include "neo_npc_targetsystem.h" #include "neo_player.h" #include "ammodef.h" +#include "nav_mesh.h" +#include "NextBotManager.h" +#include "bot/neo_bot_path_reservation.h" #include "tier0/memdbgon.h" #define CLOAKED_VELOCITY_THRESHOLD 32400 // 180 horizontal velocity. Slightly under sprint/wigglerun speed +// Bot hazard timing +static constexpr float BOT_HAZARD_REACTION_TIME = 0.25f; +static constexpr float BOT_HAZARD_INTERVAL = BOT_HAZARD_REACTION_TIME; +static constexpr float BOT_HAZARD_DURATION = 2.0f; + LINK_ENTITY_TO_CLASS(neo_npc_targetsystem, CNEO_NPCTargetSystem); BEGIN_DATADESC(CNEO_NPCTargetSystem) @@ -185,6 +193,7 @@ void CNEO_NPCTargetSystem::Think() { m_OnSpotted.FireOutput(pBestTarget, this); m_bTargetAcquired = true; + m_flNextHazardTime = gpGlobals->curtime + BOT_HAZARD_REACTION_TIME; } if (bMiddleIgnore && !m_bMiddleIgnoreActive) @@ -234,6 +243,8 @@ void CNEO_NPCTargetSystem::Think() m_bMiddleIgnoreActive = false; } + PublishBotHazards(pBestTarget); + // Optional - damage the target if (m_hDamageSource && pBestTarget && (iNewZone == ZONE_MIDDLE)) { @@ -260,6 +271,83 @@ void CNEO_NPCTargetSystem::Think() SetNextThink(gpGlobals->curtime + 0.05f); } +//----------------------------------------------------------------------------- +// Bot hazard publishing - Bots cannot fight this entity. +// Hazards: its own area for every team (run-over), the areas it can see +// for targeted teams, and its targeted player's area once acquired. +//----------------------------------------------------------------------------- + +void CNEO_NPCTargetSystem::PublishBotHazards(CBasePlayer *pTarget) +{ + if (!TheNavMesh->IsLoaded() || TheNextBots().GetNextBotCount() == 0) + { + return; + } + + if (gpGlobals->curtime < m_flNextHazardTime) + { + return; + } + + m_flNextHazardTime = gpGlobals->curtime + BOT_HAZARD_INTERVAL; + const float flExpireTime = gpGlobals->curtime + BOT_HAZARD_DURATION; + + // A team is targeted if a living member passes the filter; on ntre_rogue_ctg + // the filter swaps on ghost pickup so the carrier's team is left out + bool bTeamTargeted[TEAM__TOTAL] = {}; + for (int i = 1; i <= gpGlobals->maxClients; i++) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); + if (!pPlayer || !pPlayer->IsAlive() || pPlayer->GetTeamNumber() < FIRST_GAME_TEAM) + { + continue; + } + + if (!m_pFilter || m_pFilter->PassesFilter(this, pPlayer)) + { + bTeamTargeted[pPlayer->GetTeamNumber()] = true; + } + } + + // Mark areas PVS to the tank as do not hang out in my view + CNavArea *pOwnArea = TheNavMesh->GetNearestNavArea(GetAbsOrigin()); + if (pOwnArea) + { + for (int iTeam = FIRST_GAME_TEAM; iTeam < TEAM__TOTAL; ++iTeam) + { + // Being run over is deadly even for a bot in a hurry, unlike the turret's sightline + CNEOBotPathReservations()->AddDeadlyHazard(pOwnArea->GetID(), flExpireTime, iTeam); + + if (bTeamTargeted[iTeam]) + { + AddVisibleHazard(pOwnArea, iTeam, flExpireTime); + } + } + } + + if (pTarget) + { + CNavArea *pTargetArea = TheNavMesh->GetNearestNavArea(pTarget->GetAbsOrigin()); + if (pTargetArea) + { + // Don't propagate the PVS for the targeted bot location to avoid marking the areas around the corner + AddVisibleHazard(pTargetArea, pTarget->GetTeamNumber(), flExpireTime, false); + } + } +} + +void CNEO_NPCTargetSystem::AddVisibleHazard(CNavArea *pArea, int iTeam, float flExpireTime, bool bPropagatePVS) +{ + if (m_bMotionVision) + { + CNEOBotPathReservations()->AddNpcTurretHazard(pArea->GetID(), flExpireTime, iTeam, bPropagatePVS); + } + else + { + CNEOBotPathReservations()->AddDeadlyHazard(pArea->GetID(), flExpireTime, iTeam, bPropagatePVS); + } +} + bool CNEO_NPCTargetSystem::CanSee(CBaseEntity *pEntity) { if (m_pFilter && m_pFilter->PassesFilter(this, pEntity)) diff --git a/src/game/server/neo/neo_npc_targetsystem.h b/src/game/server/neo/neo_npc_targetsystem.h index 620f6ad597..9b3085c65c 100644 --- a/src/game/server/neo/neo_npc_targetsystem.h +++ b/src/game/server/neo/neo_npc_targetsystem.h @@ -2,6 +2,8 @@ #include "cbase.h" #include "filters.h" +class CNavArea; + class CNEO_NPCTargetSystem : public CBaseEntity { public: @@ -45,6 +47,9 @@ class CNEO_NPCTargetSystem : public CBaseEntity bool CanSee(CBaseEntity *pEntity); private: + void PublishBotHazards(CBasePlayer *pTarget); + void AddVisibleHazard(CNavArea *pArea, int iTeam, float flExpireTime, bool bPropagatePVS = true); + enum TargetZone_e { ZONE_NONE = 0, @@ -58,4 +63,5 @@ class CNEO_NPCTargetSystem : public CBaseEntity bool m_bMiddleIgnoreActive = false; float m_flNextFireTime = 0; CBasePlayer *m_pLastBestTarget = nullptr; + float m_flNextHazardTime = 0; }; \ No newline at end of file