//========= Copyright © 1996-2008, Valve Corporation, All rights reserved. ============// // // Purpose: System to generate events as specified entities become visible to players. // This contains some specific early-outs and culling code, so it's not // exactly general purpose yet. (sjb) // // $NoKeywords: $ //=====================================================================================// #include "cbase.h" #include "cvisibilitymonitor.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" ConVar debug_visibility_monitor("debug_visibility_monitor", "0", FCVAR_CHEAT ); ConVar vismon_poll_frequency("vismon_poll_frequency", ".5", FCVAR_CHEAT ); ConVar vismon_trace_limit("vismon_trace_limit", "12", FCVAR_CHEAT ); //============================================================================= // This structure packages up an entity for the visibility monitor. //============================================================================= #define NO_VISIBILITY_MEMORY 0 struct visibility_target_t { EHANDLE entity; float minDistSqr; int memory;// A bit vector that remembers which clients have seen this thing already. bool bNotVisibleThroughGlass; VisibilityMonitorCallback pfnCallback; VisibilityMonitorEvaluator pfnEvaluator; }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- class CVisibilityMonitor : public CAutoGameSystemPerFrame { public: CVisibilityMonitor( char const *name ) : CAutoGameSystemPerFrame( name ) { m_flPollFrequency = 1.0f; m_flTimeNextPoll = 0.0f; } // Methods of IGameSystem virtual char const *Name() { return "VisibilityMonitor"; } virtual bool Init(); virtual void FrameUpdatePostEntityThink(); virtual bool EntityIsVisibleToPlayer( const visibility_target_t &target, CBasePlayer *pPlayer, int *numTraces ); virtual void Shutdown(); virtual void LevelInitPreEntity(); virtual void LevelInitPostEntity(); virtual void LevelShutdownPreEntity(); // Visibility Monitor Methods void AddEntity( CBaseEntity *pEntity, float flMinDist, VisibilityMonitorCallback pfnCallback, VisibilityMonitorEvaluator pfnEvaluator, bool bNotVisibleThroughGlass = false ); void RemoveEntity( CBaseEntity *pEntity ); bool IsTrackingEntity( CBaseEntity *pEntity ); private: CUtlVector< visibility_target_t > m_Entities; float m_flPollFrequency; float m_flTimeNextPoll; int m_iMaxTracesPerThink; int m_iMaxEntitiesPerThink; int m_iStartElement; }; //========================================================= // Auto game system instantiation //========================================================= CVisibilityMonitor VisibilityMonitor( "CVisibilityMonitor" ); //--------------------------------------------------------- //--------------------------------------------------------- bool CVisibilityMonitor::Init() { return true; } //--------------------------------------------------------- // Purpose: See if it is time to poll and do so. // // SOON: We need to load-balance this. //--------------------------------------------------------- void CVisibilityMonitor::FrameUpdatePostEntityThink() { if( gpGlobals->curtime < m_flTimeNextPoll ) return; m_flTimeNextPoll = gpGlobals->curtime + vismon_poll_frequency.GetFloat(); int iDebugging = debug_visibility_monitor.GetInt(); if( m_Entities.Count() > m_iMaxEntitiesPerThink ) m_iMaxEntitiesPerThink = m_Entities.Count(); if( iDebugging > 1 ) { Msg("\nVisMon: Polling now. (Frequency: %f)\n", m_flPollFrequency ); Msg("VisMon: Time: %f - Tracking %d Entities. (Max:%d)\n", gpGlobals->curtime, m_Entities.Count(), m_iMaxEntitiesPerThink ); } // Cleanup, dump entities that have been removed since we last polled. for ( int i = 0 ; i < m_Entities.Count() ; i++ ) { if ( m_Entities[i].entity == NULL ) { m_Entities.FastRemove(i); if ( i >= m_Entities.Count() ) { break; } } } int numTraces = 0; bool bHitTraceLimit = false; if( m_iStartElement >= m_Entities.Count() ) { if( iDebugging > 1 ) { Msg("VisMon: RESET\n"); } m_iStartElement = 0; } if( iDebugging > 1 ) { Msg("VisMon: Starting at element: %d\n", m_iStartElement ); } for( int i = m_iStartElement ; i < m_Entities.Count() ; i++ ) { for( int j = 1 ; j <= gpGlobals->maxClients ; j++ ) { CBasePlayer *pPlayer =UTIL_PlayerByIndex( j ); if( pPlayer != NULL && pPlayer->IsAlive() && !pPlayer->IsBot() ) { int memoryBit = 1 << j; // The bit that is used to remember whether a given entity has been seen by a given player. CBaseEntity *pSeeEntity = m_Entities[ i ].entity.Get(); if( pSeeEntity == NULL ) { continue; } if( !(m_Entities[i].memory & memoryBit) ) { // If this player hasn't seen this entity yet, check it. if( EntityIsVisibleToPlayer( m_Entities[i], pPlayer, &numTraces ) ) { bool bIgnore = false; if( m_Entities[i].pfnEvaluator != NULL && !m_Entities[i].pfnEvaluator( m_Entities[i].entity, pPlayer ) ) { bIgnore = true; } // See it! Generate our event. if( iDebugging > 0 ) { if( bIgnore ) { Msg("VisMon: Player %s IGNORING VISIBILE Entity: %s\n", pPlayer->GetDebugName(), pSeeEntity->GetDebugName() ); NDebugOverlay::Cross3D( pSeeEntity->WorldSpaceCenter(), 16, 255, 0, 0, false, 10.0f ); } else { Msg("VisMon: Player %s sees Entity: %s\n", pPlayer->GetDebugName(), pSeeEntity->GetDebugName() ); NDebugOverlay::Cross3D( pSeeEntity->WorldSpaceCenter(), 16, 0, 255, 0, false, 10.0f ); } } if( !bIgnore ) { bool bGenerateEvent = true; if( m_Entities[i].pfnCallback != NULL ) { // Make the callback, and let it determine whether to generate the simple event. bGenerateEvent = m_Entities[i].pfnCallback( m_Entities[i].entity, pPlayer ); } if( bGenerateEvent ) { // No callback, generate the generic game event. IGameEvent * event = gameeventmanager->CreateEvent( "entity_visible" ); if ( event ) { event->SetInt( "userid", pPlayer->GetUserID() ); event->SetInt( "subject", pSeeEntity->entindex() ); event->SetString( "classname", pSeeEntity->GetClassname() ); event->SetString( "entityname", STRING( pSeeEntity->GetEntityName() ) ); gameeventmanager->FireEvent( event ); } } // Remember that this entity was visible to the player m_Entities[i].memory |= memoryBit; } } } } } if( numTraces >= vismon_trace_limit.GetInt() ) { if( iDebugging > 1 ) { Msg("VisMon: MAX Traces. Stopping after element %d\n", i ); } m_iStartElement = i + 1; // Pick up here next think. bHitTraceLimit = true; break; } } if( !bHitTraceLimit ) { m_iStartElement = 0; } if( numTraces > m_iMaxTracesPerThink ) m_iMaxTracesPerThink = numTraces; if( iDebugging > 1 ) { Msg("VisMon: %d traces performed during this polling cycle (Max: %d)\n\n", numTraces, m_iMaxTracesPerThink ); } } //--------------------------------------------------------- //--------------------------------------------------------- bool CVisibilityMonitor::EntityIsVisibleToPlayer( const visibility_target_t &target, CBasePlayer *pPlayer, int *numTraces ) { // if it's both invisible and not solid or interactive, we don't see it if ( target.entity->m_nRenderMode == kRenderNone && !target.entity->IsSolidFlagSet( FSOLID_TRIGGER ) ) { // It's invisible return false; } CBaseCombatCharacter *pEyeEntity = pPlayer->ActivePlayerCombatCharacter(); Vector vecTargetOrigin = target.entity->WorldSpaceCenter(); Vector vecPlayerOrigin = pEyeEntity->EyePosition(); float flDistSqr = vecPlayerOrigin.DistToSqr( vecTargetOrigin ); if( flDistSqr <= target.minDistSqr ) { // Increment the counter of traces done during this polling cycle *numTraces += 1; int mask = MASK_BLOCKLOS_AND_NPCS & ~CONTENTS_BLOCKLOS; if ( target.bNotVisibleThroughGlass ) { mask |= CONTENTS_WINDOW; } trace_t tr; UTIL_TraceLine( vecPlayerOrigin, vecTargetOrigin, mask, pEyeEntity, COLLISION_GROUP_NONE, &tr ); if( tr.fraction == 1.0f || tr.m_pEnt == target.entity ) return true; if( debug_visibility_monitor.GetInt() > 1 ) { NDebugOverlay::Line( vecPlayerOrigin, vecTargetOrigin, 255, 0, 0, false, vismon_poll_frequency.GetFloat() ); } } return false; } //--------------------------------------------------------- //--------------------------------------------------------- void CVisibilityMonitor::Shutdown() { } //--------------------------------------------------------- //--------------------------------------------------------- void CVisibilityMonitor::LevelInitPreEntity() { } //--------------------------------------------------------- //--------------------------------------------------------- void CVisibilityMonitor::LevelInitPostEntity() { m_iMaxTracesPerThink = 0; m_iMaxEntitiesPerThink = 0; m_iStartElement = 0; m_flTimeNextPoll = gpGlobals->curtime + vismon_poll_frequency.GetFloat(); } //--------------------------------------------------------- //--------------------------------------------------------- void CVisibilityMonitor::LevelShutdownPreEntity() { m_Entities.RemoveAll(); } //--------------------------------------------------------- //--------------------------------------------------------- void CVisibilityMonitor::AddEntity( CBaseEntity *pEntity, float flMinDist, VisibilityMonitorCallback pfnCallback, VisibilityMonitorEvaluator pfnEvaluator, bool bNotVisibleThroughGlass ) { Assert( pEntity != NULL ); if( !IsTrackingEntity( pEntity ) ) { visibility_target_t newTarget; newTarget.entity = pEntity; newTarget.minDistSqr = Square(flMinDist); newTarget.memory = NO_VISIBILITY_MEMORY; newTarget.pfnCallback = pfnCallback; newTarget.pfnEvaluator = pfnEvaluator; newTarget.bNotVisibleThroughGlass = bNotVisibleThroughGlass; m_Entities.AddToTail( newTarget ); if( debug_visibility_monitor.GetBool() ) { Msg("VisMon: Added Entity: %s (%s)\n", pEntity->GetClassname(), pEntity->GetDebugName() ); } } } //--------------------------------------------------------- //--------------------------------------------------------- void CVisibilityMonitor::RemoveEntity( CBaseEntity *pEntity ) { Assert( pEntity != NULL ); for( int i = 0 ; i < m_Entities.Count() ; i++ ) { if( m_Entities[i].entity == pEntity ) { m_Entities.Remove( i ); if( debug_visibility_monitor.GetBool() ) { Msg("VisMon: Removed Entity: %s (%s)\n", pEntity->GetClassname(), pEntity->GetDebugName() ); } return; } } } //--------------------------------------------------------- //--------------------------------------------------------- bool CVisibilityMonitor::IsTrackingEntity( CBaseEntity *pEntity ) { Assert( pEntity != NULL ); for( int i = 0 ; i < m_Entities.Count() ; i++ ) { if( m_Entities[i].entity == pEntity ) { return true; } } return false; } //--------------------------------------------------------- // Purpose: Adds an entity to the list of entities that // the visibility monitor is tracking. //--------------------------------------------------------- void VisibilityMonitor_AddEntity( CBaseEntity *pEntity, float flMinDist, VisibilityMonitorCallback pfnCallback, VisibilityMonitorEvaluator pfnEvaluator ) { VisibilityMonitor.AddEntity( pEntity, flMinDist, pfnCallback, pfnEvaluator ); } void VisibilityMonitor_AddEntity_NotVisibleThroughGlass( CBaseEntity *pEntity, float flMinDist, VisibilityMonitorCallback pfnCallback, VisibilityMonitorEvaluator pfnEvaluator ) { VisibilityMonitor.AddEntity( pEntity, flMinDist, pfnCallback, pfnEvaluator, true ); } //--------------------------------------------------------- // Purpose: Remove (stop looking for) this entity //--------------------------------------------------------- void VisibilityMonitor_RemoveEntity( CBaseEntity *pEntity ) { VisibilityMonitor.RemoveEntity( pEntity ); }