diff --git a/src/game/server/NextBot/NextBotVisionInterface.cpp b/src/game/server/NextBot/NextBotVisionInterface.cpp index 9523fbdfb..d6ddc0a3b 100644 --- a/src/game/server/NextBot/NextBotVisionInterface.cpp +++ b/src/game/server/NextBot/NextBotVisionInterface.cpp @@ -263,6 +263,33 @@ void IVision::ForgetEntity( CBaseEntity *forgetMe ) } +#ifdef NEO +//------------------------------------------------------------------------------------------ +// Update our knowledge of where an entity is, without asserting that we saw it ourselves. +// Needed because AddKnownEntity does not refresh already known entity position data. +void IVision::UpdateKnownEntityPosition( CBaseEntity *entity ) +{ + if ( !entity ) + { + return; + } + + FOR_EACH_VEC( m_knownEntityVector, it ) + { + if ( m_knownEntityVector[ it ].Is( entity ) ) + { + // Found already known entity + m_knownEntityVector[ it ].UpdatePosition(); + return; + } + } + + // We had no idea this entity existed until now + AddKnownEntity( entity ); +} +#endif // NEO + + //------------------------------------------------------------------------------------------ void IVision::ForgetAllKnownEntities( void ) { diff --git a/src/game/server/NextBot/NextBotVisionInterface.h b/src/game/server/NextBot/NextBotVisionInterface.h index c14d3ea44..172f310b6 100644 --- a/src/game/server/NextBot/NextBotVisionInterface.h +++ b/src/game/server/NextBot/NextBotVisionInterface.h @@ -64,6 +64,10 @@ class IVision : public INextBotComponent virtual void ForgetEntity( CBaseEntity *forgetMe ); // remove the given entity from our awareness (whether we know if it or not) virtual void ForgetAllKnownEntities( void ); +#ifdef NEO + virtual void UpdateKnownEntityPosition( CBaseEntity *entity ); +#endif // NEO + //-- physical vision interface follows ------------------------------------------------------ /** 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 fef39cd0c..97673182d 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 @@ -79,18 +79,21 @@ void CNEOBotGhostEquipmentHandler::Update( CNEOBot *me ) m_enemyUpdateTimer.Start( GetUpdateInterval( me ) ); } + // Check if currently focused enemy has disappeared from beacon range + CBaseEntity *pFocus = m_hCurrentFocusEnemy.Get(); + if ( !IsValidFocusEnemy( me, pFocus ) ) + { + m_hCurrentFocusEnemy = nullptr; + pFocus = nullptr; + } + // Debug: Highlight the location of the enemy a bot ghost carrier is calling out - if ( neo_debug_ghost_carrier.GetBool() ) + if ( neo_debug_ghost_carrier.GetBool() && pFocus && pFocus->IsPlayer() ) { - CBaseEntity *pFocus = m_hCurrentFocusEnemy.Get(); - if ( pFocus && pFocus->IsPlayer() && pFocus->IsAlive() ) - { - NDebugOverlay::Cross3D( pFocus->GetAbsOrigin(), 20.0f, 255, 0, 0, true, 0.1f ); - } + NDebugOverlay::Cross3D( pFocus->GetAbsOrigin(), 20.0f, 255, 0, 0, true, 0.1f ); } - CBaseEntity *pFocus = m_hCurrentFocusEnemy.Get(); - if ( pFocus && pFocus->IsAlive() ) + if ( pFocus ) { if ( bUpdateCallout ) { @@ -125,12 +128,8 @@ void CNEOBotGhostEquipmentHandler::Update( CNEOBot *me ) // NEO Jank: Urge relevant teammate bots look at the enemy pBot->GetBodyInterface()->AimHeadTowards( pFocus, IBody::IMPORTANT, 0.5f, nullptr, "Ghost carrier teammate look override" ); } - else - { - // Force updates to known but not visible entity by forgetting them first - pBot->GetVisionInterface()->ForgetEntity( pFocus ); - } - pBot->GetVisionInterface()->AddKnownEntity( pFocus ); // keep after ForgetEntity + + pBot->GetVisionInterface()->UpdateKnownEntityPosition( pFocus ); } } @@ -177,6 +176,21 @@ void CNEOBotGhostEquipmentHandler::EquipBestWeaponForGhoster( CNEOBot *me ) } } +bool CNEOBotGhostEquipmentHandler::IsValidFocusEnemy( CNEOBot *me, CBaseEntity *pFocus ) const +{ + if ( !pFocus || !pFocus->IsAlive() || pFocus->IsEffectActive( EF_NODRAW ) ) + { + return false; + } + + if ( !me->IsEnemy( pFocus ) ) + { + return false; + } + + return me->GetVisionInterface()->IsAbleToSee( pFocus, IVision::DISREGARD_FOV ); +} + float CNEOBotGhostEquipmentHandler::GetUpdateInterval( CNEOBot *me ) const { switch ( me->GetDifficulty() ) @@ -338,6 +352,11 @@ void CNEOBotGhostEquipmentHandler::UpdateGhostCarrierCallout( CNEOBot *me, const m_enemyLastPos[ idx ] = pBestCallout->GetAbsOrigin(); } } + else + { + // Nobody is in beacon range or in sight + m_hCurrentFocusEnemy = nullptr; + } } @@ -516,8 +535,7 @@ void CNEOBotCtgCarrier::UpdateFollowPath( CNEOBot *me, const CUtlVector( me->Weapon_GetSlot( 0 ) ); - if ( pGhost && pGhost->IsGhost() && pGhost->IsBootupCompleted() ) + if ( me->GetBeaconingGhost() ) { float flDistMeToGoalSq = me->GetAbsOrigin().DistToSqr( vecGoalPos ); @@ -526,7 +544,8 @@ void CNEOBotCtgCarrier::UpdateFollowPath( CNEOBot *me, const CUtlVectormaxClients; i++ ) { CNEO_Player *pPlayer = ToNEOPlayer( UTIL_PlayerByIndex( i ) ); - if ( pPlayer && pPlayer->IsAlive() && pPlayer->GetTeamNumber() != me->GetTeamNumber() ) + if ( pPlayer && pPlayer->IsAlive() && pPlayer->GetTeamNumber() != me->GetTeamNumber() + && me->GetVisionInterface()->IsAbleToSee( pPlayer, IVision::DISREGARD_FOV ) ) { float dSq = pPlayer->GetAbsOrigin().DistToSqr( vecGoalPos ); if ( dSq <= flDistMeToGoalSq ) diff --git a/src/game/server/neo/bot/behavior/neo_bot_ctg_carrier.h b/src/game/server/neo/bot/behavior/neo_bot_ctg_carrier.h index 4b70b47ca..645b56d8a 100644 --- a/src/game/server/neo/bot/behavior/neo_bot_ctg_carrier.h +++ b/src/game/server/neo/bot/behavior/neo_bot_ctg_carrier.h @@ -17,6 +17,7 @@ class CNEOBotGhostEquipmentHandler private: void EquipBestWeaponForGhoster( CNEOBot *me ); float GetUpdateInterval( CNEOBot *me ) const; + bool IsValidFocusEnemy( CNEOBot *me, CBaseEntity *pFocus ) const; void UpdateGhostCarrierCallout( CNEOBot *me, const CUtlVector &enemies ); EHANDLE m_hCurrentFocusEnemy{nullptr}; diff --git a/src/game/server/neo/bot/neo_bot.cpp b/src/game/server/neo/bot/neo_bot.cpp index 175eb07c2..85cf258a6 100644 --- a/src/game/server/neo/bot/neo_bot.cpp +++ b/src/game/server/neo/bot/neo_bot.cpp @@ -22,6 +22,7 @@ #include "neo_weapon_loadout.h" #include "behavior/neo_bot_behavior.h" #include "neo_crosshair.h" +#include "neo/weapons/weapon_ghost.h" ConVar neo_bot_notice_gunfire_range("neo_bot_notice_gunfire_range", "3000", FCVAR_GAMEDLL); ConVar neo_bot_notice_quiet_gunfire_range("neo_bot_notice_quiet_gunfire_range", "500", FCVAR_GAMEDLL); @@ -41,6 +42,7 @@ extern ConVar neo_bot_difficulty; extern ConVar neo_bot_farthest_visible_theater_sample_count; extern ConVar neo_bot_path_lookahead_range; extern ConVar neo_bot_path_around_friendly_cooldown; +extern ConVar sv_neo_ctg_ghost_beacons_when_inactive; @@ -1625,6 +1627,51 @@ void CNEOBot::EquipBestWeaponForThreat(const CKnownEntity* threat, const bool bN } +//----------------------------------------------------------------------------------------------------- +// Return handle to ghost if it is beaconing for the bot player +// Also useful to get true beacon range from weapon implementation +CWeaponGhost *CNEOBot::GetBeaconingGhost( void ) const +{ + if ( !IsCarryingGhost() ) + { + return nullptr; + } + + CBaseCombatWeapon *pCandidate = sv_neo_ctg_ghost_beacons_when_inactive.GetBool() + ? Weapon_GetSlot( 0 ) + : GetActiveWeapon(); + + CNEOBaseCombatWeapon *pNeoWeapon = dynamic_cast( pCandidate ); + if ( !pNeoWeapon || !pNeoWeapon->IsGhost() ) + { + return nullptr; + } + + CWeaponGhost *pGhost = assert_cast( pNeoWeapon ); + return pGhost->IsBootupCompleted() ? pGhost : nullptr; +} + + +//----------------------------------------------------------------------------------------------------- +// Returns whether the conditions are satisfied for the ghost revealing the subject +bool CNEOBot::IsRevealedByMyGhost( CBaseEntity *subject ) const +{ + if ( !subject ) + { + return false; + } + + const CWeaponGhost *pGhost = GetBeaconingGhost(); + if ( !pGhost ) + { + return false; + } + + float flDistIgnored; + return pGhost->BeaconRange( subject, flDistIgnored ); +} + + //----------------------------------------------------------------------------------------------------- bool CNEOBot::DropGhost() { diff --git a/src/game/server/neo/bot/neo_bot.h b/src/game/server/neo/bot/neo_bot.h index a0a21be81..5f388f5d3 100644 --- a/src/game/server/neo/bot/neo_bot.h +++ b/src/game/server/neo/bot/neo_bot.h @@ -22,6 +22,7 @@ class CNEOBotActionPoint; class CNEOBotGenerator; class CNEOBot; +class CWeaponGhost; extern ConVar hl2_normspeed; @@ -160,7 +161,10 @@ class CNEOBot : public NextBotPlayer< CNEO_Player >, public CGameEventListener bool EquipRequiredWeapon(void); // if we're required to equip a specific weapon, do it. void EquipBestWeaponForThreat(const CKnownEntity* threat, const bool bNotPrimary = false); // equip the best weapon we have to attack the given threat void ReloadIfLowClip(bool bForceReload = false); + bool DropGhost(); + CWeaponGhost *GetBeaconingGhost(void) const; + bool IsRevealedByMyGhost(CBaseEntity *subject) const; void DropPrimaryWeapon(void); diff --git a/src/game/server/neo/bot/neo_bot_vision.cpp b/src/game/server/neo/bot/neo_bot_vision.cpp index 2b3dbd2ad..b07bccd7b 100644 --- a/src/game/server/neo/bot/neo_bot_vision.cpp +++ b/src/game/server/neo/bot/neo_bot_vision.cpp @@ -174,16 +174,9 @@ bool CNEOBotVision::IsInFieldOfView( CBaseEntity *subject ) const bool CNEOBotVision::IsAbleToSee(CBaseEntity *subject, FieldOfViewCheckType checkFOV, Vector *visibleSpot) const { CNEOBot *me = (CNEOBot *)GetBot()->GetEntity(); - if (me && me->IsCarryingGhost()) + if (me && me->IsRevealedByMyGhost(subject)) { - auto *pGhost = dynamic_cast(me->GetActiveWeapon()); - if (pGhost && pGhost->IsGhost() && pGhost->IsBootupCompleted()) - { - if (me->GetAbsOrigin().DistToSqr(subject->GetAbsOrigin()) < Square(CWeaponGhost::GetGhostRangeInHammerUnits())) - { - return true; - } - } + return true; } return IVision::IsAbleToSee(subject, checkFOV, visibleSpot);