You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1636 lines
43 KiB
1636 lines
43 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#include "entitylist.h" |
|
#include "utlvector.h" |
|
#include "igamesystem.h" |
|
#include "collisionutils.h" |
|
#include "UtlSortVector.h" |
|
#include "tier0/vprof.h" |
|
#include "mapentities.h" |
|
#include "client.h" |
|
#include "ai_initutils.h" |
|
#include "globalstate.h" |
|
#include "datacache/imdlcache.h" |
|
|
|
#ifdef HL2_DLL |
|
#include "npc_playercompanion.h" |
|
#endif // HL2_DLL |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
CBaseEntity *FindPickerEntity( CBasePlayer *pPlayer ); |
|
void SceneManager_ClientActive( CBasePlayer *player ); |
|
|
|
static CUtlVector<IServerNetworkable*> g_DeleteList; |
|
|
|
CGlobalEntityList gEntList; |
|
CBaseEntityList *g_pEntityList = &gEntList; |
|
|
|
class CAimTargetManager : public IEntityListener |
|
{ |
|
public: |
|
// Called by CEntityListSystem |
|
void LevelInitPreEntity() |
|
{ |
|
gEntList.AddListenerEntity( this ); |
|
Clear(); |
|
} |
|
void LevelShutdownPostEntity() |
|
{ |
|
gEntList.RemoveListenerEntity( this ); |
|
Clear(); |
|
} |
|
|
|
void Clear() |
|
{ |
|
m_targetList.Purge(); |
|
} |
|
|
|
void ForceRepopulateList() |
|
{ |
|
Clear(); |
|
|
|
CBaseEntity *pEnt = gEntList.FirstEnt(); |
|
|
|
while( pEnt ) |
|
{ |
|
if( ShouldAddEntity(pEnt) ) |
|
AddEntity(pEnt); |
|
|
|
pEnt = gEntList.NextEnt( pEnt ); |
|
} |
|
} |
|
|
|
bool ShouldAddEntity( CBaseEntity *pEntity ) |
|
{ |
|
return ((pEntity->GetFlags() & FL_AIMTARGET) != 0); |
|
} |
|
|
|
// IEntityListener |
|
virtual void OnEntityCreated( CBaseEntity *pEntity ) {} |
|
virtual void OnEntityDeleted( CBaseEntity *pEntity ) |
|
{ |
|
if ( !(pEntity->GetFlags() & FL_AIMTARGET) ) |
|
return; |
|
RemoveEntity(pEntity); |
|
} |
|
void AddEntity( CBaseEntity *pEntity ) |
|
{ |
|
if ( pEntity->IsMarkedForDeletion() ) |
|
return; |
|
m_targetList.AddToTail( pEntity ); |
|
} |
|
void RemoveEntity( CBaseEntity *pEntity ) |
|
{ |
|
int index = m_targetList.Find( pEntity ); |
|
if ( m_targetList.IsValidIndex(index) ) |
|
{ |
|
m_targetList.FastRemove( index ); |
|
} |
|
} |
|
int ListCount() { return m_targetList.Count(); } |
|
int ListCopy( CBaseEntity *pList[], int listMax ) |
|
{ |
|
int count = MIN(listMax, ListCount() ); |
|
memcpy( pList, m_targetList.Base(), sizeof(CBaseEntity *) * count ); |
|
return count; |
|
} |
|
|
|
private: |
|
CUtlVector<CBaseEntity *> m_targetList; |
|
}; |
|
|
|
static CAimTargetManager g_AimManager; |
|
|
|
int AimTarget_ListCount() |
|
{ |
|
return g_AimManager.ListCount(); |
|
} |
|
int AimTarget_ListCopy( CBaseEntity *pList[], int listMax ) |
|
{ |
|
return g_AimManager.ListCopy( pList, listMax ); |
|
} |
|
void AimTarget_ForceRepopulateList() |
|
{ |
|
g_AimManager.ForceRepopulateList(); |
|
} |
|
|
|
|
|
// Manages a list of all entities currently doing game simulation or thinking |
|
// NOTE: This is usually a small subset of the global entity list, so it's |
|
// an optimization to maintain this list incrementally rather than polling each |
|
// frame. |
|
struct simthinkentry_t |
|
{ |
|
unsigned short entEntry; |
|
unsigned short unused0; |
|
int nextThinkTick; |
|
}; |
|
class CSimThinkManager : public IEntityListener |
|
{ |
|
public: |
|
CSimThinkManager() |
|
{ |
|
Clear(); |
|
} |
|
void Clear() |
|
{ |
|
m_simThinkList.Purge(); |
|
for ( int i = 0; i < ARRAYSIZE(m_entinfoIndex); i++ ) |
|
{ |
|
m_entinfoIndex[i] = 0xFFFF; |
|
} |
|
} |
|
void LevelInitPreEntity() |
|
{ |
|
gEntList.AddListenerEntity( this ); |
|
} |
|
|
|
void LevelShutdownPostEntity() |
|
{ |
|
gEntList.RemoveListenerEntity( this ); |
|
Clear(); |
|
} |
|
|
|
void OnEntityCreated( CBaseEntity *pEntity ) |
|
{ |
|
Assert( m_entinfoIndex[pEntity->GetRefEHandle().GetEntryIndex()] == 0xFFFF ); |
|
} |
|
void OnEntityDeleted( CBaseEntity *pEntity ) |
|
{ |
|
RemoveEntinfoIndex( pEntity->GetRefEHandle().GetEntryIndex() ); |
|
} |
|
|
|
void RemoveEntinfoIndex( int index ) |
|
{ |
|
int listHandle = m_entinfoIndex[index]; |
|
// If this guy is in the active list, remove him |
|
if ( listHandle != 0xFFFF ) |
|
{ |
|
Assert(m_simThinkList[listHandle].entEntry == index); |
|
m_simThinkList.FastRemove( listHandle ); |
|
m_entinfoIndex[index] = 0xFFFF; |
|
|
|
// fast remove shifted someone, update that someone |
|
if ( listHandle < m_simThinkList.Count() ) |
|
{ |
|
m_entinfoIndex[m_simThinkList[listHandle].entEntry] = listHandle; |
|
} |
|
} |
|
} |
|
int ListCount() |
|
{ |
|
return m_simThinkList.Count(); |
|
} |
|
|
|
int ListCopy( CBaseEntity *pList[], int listMax ) |
|
{ |
|
int count = MIN(listMax, ListCount()); |
|
int out = 0; |
|
for ( int i = 0; i < count; i++ ) |
|
{ |
|
// only copy out entities that will simulate or think this frame |
|
if ( m_simThinkList[i].nextThinkTick <= gpGlobals->tickcount ) |
|
{ |
|
Assert(m_simThinkList[i].nextThinkTick>=0); |
|
int entinfoIndex = m_simThinkList[i].entEntry; |
|
const CEntInfo *pInfo = gEntList.GetEntInfoPtrByIndex( entinfoIndex ); |
|
pList[out] = (CBaseEntity *)pInfo->m_pEntity; |
|
Assert(m_simThinkList[i].nextThinkTick==0 || pList[out]->GetFirstThinkTick()==m_simThinkList[i].nextThinkTick); |
|
Assert( gEntList.IsEntityPtr( pList[out] ) ); |
|
out++; |
|
} |
|
} |
|
|
|
return out; |
|
} |
|
|
|
void EntityChanged( CBaseEntity *pEntity ) |
|
{ |
|
// might change after deletion, don't put back into the list |
|
if ( pEntity->IsMarkedForDeletion() ) |
|
return; |
|
|
|
const CBaseHandle &eh = pEntity->GetRefEHandle(); |
|
if ( !eh.IsValid() ) |
|
return; |
|
|
|
int index = eh.GetEntryIndex(); |
|
if ( pEntity->IsEFlagSet( EFL_NO_THINK_FUNCTION ) && pEntity->IsEFlagSet( EFL_NO_GAME_PHYSICS_SIMULATION ) ) |
|
{ |
|
RemoveEntinfoIndex( index ); |
|
} |
|
else |
|
{ |
|
// already in the list? (had think or sim last time, now has both - or had both last time, now just one) |
|
if ( m_entinfoIndex[index] == 0xFFFF ) |
|
{ |
|
MEM_ALLOC_CREDIT(); |
|
m_entinfoIndex[index] = m_simThinkList.AddToTail(); |
|
m_simThinkList[m_entinfoIndex[index]].entEntry = (unsigned short)index; |
|
m_simThinkList[m_entinfoIndex[index]].nextThinkTick = 0; |
|
if ( pEntity->IsEFlagSet(EFL_NO_GAME_PHYSICS_SIMULATION) ) |
|
{ |
|
m_simThinkList[m_entinfoIndex[index]].nextThinkTick = pEntity->GetFirstThinkTick(); |
|
Assert(m_simThinkList[m_entinfoIndex[index]].nextThinkTick>=0); |
|
} |
|
} |
|
else |
|
{ |
|
// updating existing entry - if no sim, reset think time |
|
if ( pEntity->IsEFlagSet(EFL_NO_GAME_PHYSICS_SIMULATION) ) |
|
{ |
|
m_simThinkList[m_entinfoIndex[index]].nextThinkTick = pEntity->GetFirstThinkTick(); |
|
Assert(m_simThinkList[m_entinfoIndex[index]].nextThinkTick>=0); |
|
} |
|
else |
|
{ |
|
m_simThinkList[m_entinfoIndex[index]].nextThinkTick = 0; |
|
} |
|
} |
|
} |
|
} |
|
|
|
private: |
|
unsigned short m_entinfoIndex[NUM_ENT_ENTRIES]; |
|
CUtlVector<simthinkentry_t> m_simThinkList; |
|
}; |
|
|
|
CSimThinkManager g_SimThinkManager; |
|
|
|
int SimThink_ListCount() |
|
{ |
|
return g_SimThinkManager.ListCount(); |
|
} |
|
|
|
int SimThink_ListCopy( CBaseEntity *pList[], int listMax ) |
|
{ |
|
return g_SimThinkManager.ListCopy( pList, listMax ); |
|
} |
|
|
|
void SimThink_EntityChanged( CBaseEntity *pEntity ) |
|
{ |
|
g_SimThinkManager.EntityChanged( pEntity ); |
|
} |
|
|
|
static CBaseEntityClassList *s_pClassLists = NULL; |
|
CBaseEntityClassList::CBaseEntityClassList() |
|
{ |
|
m_pNextClassList = s_pClassLists; |
|
s_pClassLists = this; |
|
} |
|
CBaseEntityClassList::~CBaseEntityClassList() |
|
{ |
|
} |
|
|
|
CGlobalEntityList::CGlobalEntityList() |
|
{ |
|
m_iHighestEnt = m_iNumEnts = m_iNumEdicts = 0; |
|
m_bClearingEntities = false; |
|
} |
|
|
|
|
|
// removes the entity from the global list |
|
// only called from with the CBaseEntity destructor |
|
static bool g_fInCleanupDelete; |
|
|
|
|
|
// mark an entity as deleted |
|
void CGlobalEntityList::AddToDeleteList( IServerNetworkable *ent ) |
|
{ |
|
if ( ent && ent->GetEntityHandle()->GetRefEHandle() != INVALID_EHANDLE_INDEX ) |
|
{ |
|
g_DeleteList.AddToTail( ent ); |
|
} |
|
} |
|
|
|
extern bool g_bDisableEhandleAccess; |
|
// call this before and after each frame to delete all of the marked entities. |
|
void CGlobalEntityList::CleanupDeleteList( void ) |
|
{ |
|
VPROF( "CGlobalEntityList::CleanupDeleteList" ); |
|
g_fInCleanupDelete = true; |
|
// clean up the vphysics delete list as well |
|
PhysOnCleanupDeleteList(); |
|
|
|
g_bDisableEhandleAccess = true; |
|
for ( int i = 0; i < g_DeleteList.Count(); i++ ) |
|
{ |
|
g_DeleteList[i]->Release(); |
|
} |
|
g_bDisableEhandleAccess = false; |
|
g_DeleteList.RemoveAll(); |
|
|
|
g_fInCleanupDelete = false; |
|
} |
|
|
|
int CGlobalEntityList::ResetDeleteList( void ) |
|
{ |
|
int result = g_DeleteList.Count(); |
|
g_DeleteList.RemoveAll(); |
|
return result; |
|
} |
|
|
|
|
|
// add a class that gets notified of entity events |
|
void CGlobalEntityList::AddListenerEntity( IEntityListener *pListener ) |
|
{ |
|
if ( m_entityListeners.Find( pListener ) >= 0 ) |
|
{ |
|
AssertMsg( 0, "Can't add listeners multiple times\n" ); |
|
return; |
|
} |
|
m_entityListeners.AddToTail( pListener ); |
|
} |
|
|
|
void CGlobalEntityList::RemoveListenerEntity( IEntityListener *pListener ) |
|
{ |
|
m_entityListeners.FindAndRemove( pListener ); |
|
} |
|
|
|
void CGlobalEntityList::Clear( void ) |
|
{ |
|
m_bClearingEntities = true; |
|
|
|
// Add all remaining entities in the game to the delete list and call appropriate UpdateOnRemove |
|
CBaseHandle hCur = FirstHandle(); |
|
while ( hCur != InvalidHandle() ) |
|
{ |
|
IServerNetworkable *ent = GetServerNetworkable( hCur ); |
|
if ( ent ) |
|
{ |
|
MDLCACHE_CRITICAL_SECTION(); |
|
// Force UpdateOnRemove to be called |
|
UTIL_Remove( ent ); |
|
} |
|
hCur = NextHandle( hCur ); |
|
} |
|
|
|
CleanupDeleteList(); |
|
// free the memory |
|
g_DeleteList.Purge(); |
|
|
|
CBaseEntity::m_nDebugPlayer = -1; |
|
CBaseEntity::m_bInDebugSelect = false; |
|
m_iHighestEnt = 0; |
|
m_iNumEnts = 0; |
|
|
|
m_bClearingEntities = false; |
|
} |
|
|
|
|
|
int CGlobalEntityList::NumberOfEntities( void ) |
|
{ |
|
return m_iNumEnts; |
|
} |
|
|
|
int CGlobalEntityList::NumberOfEdicts( void ) |
|
{ |
|
return m_iNumEdicts; |
|
} |
|
|
|
CBaseEntity *CGlobalEntityList::NextEnt( CBaseEntity *pCurrentEnt ) |
|
{ |
|
if ( !pCurrentEnt ) |
|
{ |
|
const CEntInfo *pInfo = FirstEntInfo(); |
|
if ( !pInfo ) |
|
return NULL; |
|
|
|
return (CBaseEntity *)pInfo->m_pEntity; |
|
} |
|
|
|
// Run through the list until we get a CBaseEntity. |
|
const CEntInfo *pList = GetEntInfoPtr( pCurrentEnt->GetRefEHandle() ); |
|
if ( pList ) |
|
pList = NextEntInfo(pList); |
|
|
|
while ( pList ) |
|
{ |
|
#if 0 |
|
if ( pList->m_pEntity ) |
|
{ |
|
IServerUnknown *pUnk = static_cast<IServerUnknown*>(const_cast<IHandleEntity*>(pList->m_pEntity)); |
|
CBaseEntity *pRet = pUnk->GetBaseEntity(); |
|
if ( pRet ) |
|
return pRet; |
|
} |
|
#else |
|
return (CBaseEntity *)pList->m_pEntity; |
|
#endif |
|
pList = pList->m_pNext; |
|
} |
|
|
|
return NULL; |
|
|
|
} |
|
|
|
|
|
void CGlobalEntityList::ReportEntityFlagsChanged( CBaseEntity *pEntity, unsigned int flagsOld, unsigned int flagsNow ) |
|
{ |
|
if ( pEntity->IsMarkedForDeletion() ) |
|
return; |
|
// UNDONE: Move this into IEntityListener instead? |
|
unsigned int flagsChanged = flagsOld ^ flagsNow; |
|
if ( flagsChanged & FL_AIMTARGET ) |
|
{ |
|
unsigned int flagsAdded = flagsNow & flagsChanged; |
|
unsigned int flagsRemoved = flagsOld & flagsChanged; |
|
|
|
if ( flagsAdded & FL_AIMTARGET ) |
|
{ |
|
g_AimManager.AddEntity( pEntity ); |
|
} |
|
if ( flagsRemoved & FL_AIMTARGET ) |
|
{ |
|
g_AimManager.RemoveEntity( pEntity ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Used to confirm a pointer is a pointer to an entity, useful for |
|
// asserts. |
|
//----------------------------------------------------------------------------- |
|
bool CGlobalEntityList::IsEntityPtr( void *pTest ) |
|
{ |
|
if ( pTest ) |
|
{ |
|
const CEntInfo *pInfo = FirstEntInfo(); |
|
for ( ;pInfo; pInfo = pInfo->m_pNext ) |
|
{ |
|
if ( pTest == (void *)pInfo->m_pEntity ) |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Iterates the entities with a given classname. |
|
// Input : pStartEntity - Last entity found, NULL to start a new iteration. |
|
// szName - Classname to search for. |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CGlobalEntityList::FindEntityByClassname( CBaseEntity *pStartEntity, const char *szName ) |
|
{ |
|
const CEntInfo *pInfo = pStartEntity ? GetEntInfoPtr( pStartEntity->GetRefEHandle() )->m_pNext : FirstEntInfo(); |
|
|
|
for ( ;pInfo; pInfo = pInfo->m_pNext ) |
|
{ |
|
CBaseEntity *pEntity = (CBaseEntity *)pInfo->m_pEntity; |
|
if ( !pEntity ) |
|
{ |
|
DevWarning( "NULL entity in global entity list!\n" ); |
|
continue; |
|
} |
|
|
|
if ( pEntity->ClassMatches(szName) ) |
|
return pEntity; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Finds an entity given a procedural name. |
|
// Input : szName - The procedural name to search for, should start with '!'. |
|
// pSearchingEntity - |
|
// pActivator - The activator entity if this was called from an input |
|
// or Use handler. |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CGlobalEntityList::FindEntityProcedural( const char *szName, CBaseEntity *pSearchingEntity, CBaseEntity *pActivator, CBaseEntity *pCaller ) |
|
{ |
|
// |
|
// Check for the name escape character. |
|
// |
|
if ( szName[0] == '!' ) |
|
{ |
|
const char *pName = szName + 1; |
|
|
|
// |
|
// It is a procedural name, look for the ones we understand. |
|
// |
|
if ( FStrEq( pName, "player" ) ) |
|
{ |
|
return (CBaseEntity *)UTIL_PlayerByIndex( 1 ); |
|
} |
|
else if ( FStrEq( pName, "pvsplayer" ) ) |
|
{ |
|
if ( pSearchingEntity ) |
|
{ |
|
return CBaseEntity::Instance( UTIL_FindClientInPVS( pSearchingEntity->edict() ) ); |
|
} |
|
else if ( pActivator ) |
|
{ |
|
// FIXME: error condition? |
|
return CBaseEntity::Instance( UTIL_FindClientInPVS( pActivator->edict() ) ); |
|
} |
|
else |
|
{ |
|
// FIXME: error condition? |
|
return (CBaseEntity *)UTIL_PlayerByIndex( 1 ); |
|
} |
|
|
|
} |
|
else if ( FStrEq( pName, "activator" ) ) |
|
{ |
|
return pActivator; |
|
} |
|
else if ( FStrEq( pName, "caller" ) ) |
|
{ |
|
return pCaller; |
|
} |
|
else if ( FStrEq( pName, "picker" ) ) |
|
{ |
|
return FindPickerEntity( UTIL_PlayerByIndex(1) ); |
|
} |
|
else if ( FStrEq( pName, "self" ) ) |
|
{ |
|
return pSearchingEntity; |
|
} |
|
else |
|
{ |
|
Warning( "Invalid entity search name %s\n", szName ); |
|
Assert(0); |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Iterates the entities with a given name. |
|
// Input : pStartEntity - Last entity found, NULL to start a new iteration. |
|
// szName - Name to search for. |
|
// pActivator - Activator entity if this was called from an input |
|
// handler or Use handler. |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CGlobalEntityList::FindEntityByName( CBaseEntity *pStartEntity, const char *szName, CBaseEntity *pSearchingEntity, CBaseEntity *pActivator, CBaseEntity *pCaller, IEntityFindFilter *pFilter ) |
|
{ |
|
if ( !szName || szName[0] == 0 ) |
|
return NULL; |
|
|
|
if ( szName[0] == '!' ) |
|
{ |
|
// |
|
// Avoid an infinite loop, only find one match per procedural search! |
|
// |
|
if (pStartEntity == NULL) |
|
return FindEntityProcedural( szName, pSearchingEntity, pActivator, pCaller ); |
|
|
|
return NULL; |
|
} |
|
|
|
const CEntInfo *pInfo = pStartEntity ? GetEntInfoPtr( pStartEntity->GetRefEHandle() )->m_pNext : FirstEntInfo(); |
|
|
|
for ( ;pInfo; pInfo = pInfo->m_pNext ) |
|
{ |
|
CBaseEntity *ent = (CBaseEntity *)pInfo->m_pEntity; |
|
if ( !ent ) |
|
{ |
|
DevWarning( "NULL entity in global entity list!\n" ); |
|
continue; |
|
} |
|
|
|
if ( !ent->m_iName ) |
|
continue; |
|
|
|
if ( ent->NameMatches( szName ) ) |
|
{ |
|
if ( pFilter && !pFilter->ShouldFindEntity(ent) ) |
|
continue; |
|
|
|
return ent; |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : pStartEntity - |
|
// szModelName - |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CGlobalEntityList::FindEntityByModel( CBaseEntity *pStartEntity, const char *szModelName ) |
|
{ |
|
const CEntInfo *pInfo = pStartEntity ? GetEntInfoPtr( pStartEntity->GetRefEHandle() )->m_pNext : FirstEntInfo(); |
|
|
|
for ( ;pInfo; pInfo = pInfo->m_pNext ) |
|
{ |
|
CBaseEntity *ent = (CBaseEntity *)pInfo->m_pEntity; |
|
if ( !ent ) |
|
{ |
|
DevWarning( "NULL entity in global entity list!\n" ); |
|
continue; |
|
} |
|
|
|
if ( !ent->edict() || !ent->GetModelName() ) |
|
continue; |
|
|
|
if ( FStrEq( STRING(ent->GetModelName()), szModelName ) ) |
|
return ent; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Iterates the entities with a given target. |
|
// Input : pStartEntity - |
|
// szName - |
|
//----------------------------------------------------------------------------- |
|
// FIXME: obsolete, remove |
|
CBaseEntity *CGlobalEntityList::FindEntityByTarget( CBaseEntity *pStartEntity, const char *szName ) |
|
{ |
|
const CEntInfo *pInfo = pStartEntity ? GetEntInfoPtr( pStartEntity->GetRefEHandle() )->m_pNext : FirstEntInfo(); |
|
|
|
for ( ;pInfo; pInfo = pInfo->m_pNext ) |
|
{ |
|
CBaseEntity *ent = (CBaseEntity *)pInfo->m_pEntity; |
|
if ( !ent ) |
|
{ |
|
DevWarning( "NULL entity in global entity list!\n" ); |
|
continue; |
|
} |
|
|
|
if ( !ent->m_target ) |
|
continue; |
|
|
|
if ( FStrEq( STRING(ent->m_target), szName ) ) |
|
return ent; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Used to iterate all the entities within a sphere. |
|
// Input : pStartEntity - |
|
// vecCenter - |
|
// flRadius - |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CGlobalEntityList::FindEntityInSphere( CBaseEntity *pStartEntity, const Vector &vecCenter, float flRadius ) |
|
{ |
|
const CEntInfo *pInfo = pStartEntity ? GetEntInfoPtr( pStartEntity->GetRefEHandle() )->m_pNext : FirstEntInfo(); |
|
|
|
for ( ;pInfo; pInfo = pInfo->m_pNext ) |
|
{ |
|
CBaseEntity *ent = (CBaseEntity *)pInfo->m_pEntity; |
|
if ( !ent ) |
|
{ |
|
DevWarning( "NULL entity in global entity list!\n" ); |
|
continue; |
|
} |
|
|
|
if ( !ent->edict() ) |
|
continue; |
|
|
|
Vector vecRelativeCenter; |
|
ent->CollisionProp()->WorldToCollisionSpace( vecCenter, &vecRelativeCenter ); |
|
if ( !IsBoxIntersectingSphere( ent->CollisionProp()->OBBMins(), ent->CollisionProp()->OBBMaxs(), vecRelativeCenter, flRadius ) ) |
|
continue; |
|
|
|
return ent; |
|
} |
|
|
|
// nothing found |
|
return NULL; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Finds the nearest entity by name within a radius |
|
// Input : szName - Entity name to search for. |
|
// vecSrc - Center of search radius. |
|
// flRadius - Search radius for classname search, 0 to search everywhere. |
|
// pSearchingEntity - The entity that is doing the search. |
|
// pActivator - The activator entity if this was called from an input |
|
// or Use handler, NULL otherwise. |
|
// Output : Returns a pointer to the found entity, NULL if none. |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CGlobalEntityList::FindEntityByNameNearest( const char *szName, const Vector &vecSrc, float flRadius, CBaseEntity *pSearchingEntity, CBaseEntity *pActivator, CBaseEntity *pCaller ) |
|
{ |
|
CBaseEntity *pEntity = NULL; |
|
|
|
// |
|
// Check for matching class names within the search radius. |
|
// |
|
float flMaxDist2 = flRadius * flRadius; |
|
if (flMaxDist2 == 0) |
|
{ |
|
flMaxDist2 = MAX_TRACE_LENGTH * MAX_TRACE_LENGTH; |
|
} |
|
|
|
CBaseEntity *pSearch = NULL; |
|
while ((pSearch = gEntList.FindEntityByName( pSearch, szName, pSearchingEntity, pActivator, pCaller )) != NULL) |
|
{ |
|
if ( !pSearch->edict() ) |
|
continue; |
|
|
|
float flDist2 = (pSearch->GetAbsOrigin() - vecSrc).LengthSqr(); |
|
|
|
if (flMaxDist2 > flDist2) |
|
{ |
|
pEntity = pSearch; |
|
flMaxDist2 = flDist2; |
|
} |
|
} |
|
|
|
return pEntity; |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Finds the first entity by name within a radius |
|
// Input : pStartEntity - The entity to start from when doing the search. |
|
// szName - Entity name to search for. |
|
// vecSrc - Center of search radius. |
|
// flRadius - Search radius for classname search, 0 to search everywhere. |
|
// pSearchingEntity - The entity that is doing the search. |
|
// pActivator - The activator entity if this was called from an input |
|
// or Use handler, NULL otherwise. |
|
// Output : Returns a pointer to the found entity, NULL if none. |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CGlobalEntityList::FindEntityByNameWithin( CBaseEntity *pStartEntity, const char *szName, const Vector &vecSrc, float flRadius, CBaseEntity *pSearchingEntity, CBaseEntity *pActivator, CBaseEntity *pCaller ) |
|
{ |
|
// |
|
// Check for matching class names within the search radius. |
|
// |
|
CBaseEntity *pEntity = pStartEntity; |
|
float flMaxDist2 = flRadius * flRadius; |
|
if (flMaxDist2 == 0) |
|
{ |
|
return gEntList.FindEntityByName( pEntity, szName, pSearchingEntity, pActivator, pCaller ); |
|
} |
|
|
|
while ((pEntity = gEntList.FindEntityByName( pEntity, szName, pSearchingEntity, pActivator, pCaller )) != NULL) |
|
{ |
|
if ( !pEntity->edict() ) |
|
continue; |
|
|
|
float flDist2 = (pEntity->GetAbsOrigin() - vecSrc).LengthSqr(); |
|
|
|
if (flMaxDist2 > flDist2) |
|
{ |
|
return pEntity; |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Finds the nearest entity by class name withing given search radius. |
|
// Input : szName - Entity name to search for. Treated as a target name first, |
|
// then as an entity class name, ie "info_target". |
|
// vecSrc - Center of search radius. |
|
// flRadius - Search radius for classname search, 0 to search everywhere. |
|
// Output : Returns a pointer to the found entity, NULL if none. |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CGlobalEntityList::FindEntityByClassnameNearest( const char *szName, const Vector &vecSrc, float flRadius ) |
|
{ |
|
CBaseEntity *pEntity = NULL; |
|
|
|
// |
|
// Check for matching class names within the search radius. |
|
// |
|
float flMaxDist2 = flRadius * flRadius; |
|
if (flMaxDist2 == 0) |
|
{ |
|
flMaxDist2 = MAX_TRACE_LENGTH * MAX_TRACE_LENGTH; |
|
} |
|
|
|
CBaseEntity *pSearch = NULL; |
|
while ((pSearch = gEntList.FindEntityByClassname( pSearch, szName )) != NULL) |
|
{ |
|
if ( !pSearch->edict() ) |
|
continue; |
|
|
|
float flDist2 = (pSearch->GetAbsOrigin() - vecSrc).LengthSqr(); |
|
|
|
if (flMaxDist2 > flDist2) |
|
{ |
|
pEntity = pSearch; |
|
flMaxDist2 = flDist2; |
|
} |
|
} |
|
|
|
return pEntity; |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Finds the first entity within radius distance by class name. |
|
// Input : pStartEntity - The entity to start from when doing the search. |
|
// szName - Entity class name, ie "info_target". |
|
// vecSrc - Center of search radius. |
|
// flRadius - Search radius for classname search, 0 to search everywhere. |
|
// Output : Returns a pointer to the found entity, NULL if none. |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CGlobalEntityList::FindEntityByClassnameWithin( CBaseEntity *pStartEntity, const char *szName, const Vector &vecSrc, float flRadius ) |
|
{ |
|
// |
|
// Check for matching class names within the search radius. |
|
// |
|
CBaseEntity *pEntity = pStartEntity; |
|
float flMaxDist2 = flRadius * flRadius; |
|
if (flMaxDist2 == 0) |
|
{ |
|
return gEntList.FindEntityByClassname( pEntity, szName ); |
|
} |
|
|
|
while ((pEntity = gEntList.FindEntityByClassname( pEntity, szName )) != NULL) |
|
{ |
|
if ( !pEntity->edict() ) |
|
continue; |
|
|
|
float flDist2 = (pEntity->GetAbsOrigin() - vecSrc).LengthSqr(); |
|
|
|
if (flMaxDist2 > flDist2) |
|
{ |
|
return pEntity; |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Finds the first entity within an extent by class name. |
|
// Input : pStartEntity - The entity to start from when doing the search. |
|
// szName - Entity class name, ie "info_target". |
|
// vecMins - Search mins. |
|
// vecMaxs - Search maxs. |
|
// Output : Returns a pointer to the found entity, NULL if none. |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CGlobalEntityList::FindEntityByClassnameWithin( CBaseEntity *pStartEntity, const char *szName, const Vector &vecMins, const Vector &vecMaxs ) |
|
{ |
|
// |
|
// Check for matching class names within the search radius. |
|
// |
|
CBaseEntity *pEntity = pStartEntity; |
|
|
|
while ((pEntity = gEntList.FindEntityByClassname( pEntity, szName )) != NULL) |
|
{ |
|
if ( !pEntity->edict() && !pEntity->IsEFlagSet( EFL_SERVER_ONLY ) ) |
|
continue; |
|
|
|
// check if the aabb intersects the search aabb. |
|
Vector entMins, entMaxs; |
|
pEntity->CollisionProp()->WorldSpaceAABB( &entMins, &entMaxs ); |
|
if ( IsBoxIntersectingBox( vecMins, vecMaxs, entMins, entMaxs ) ) |
|
{ |
|
return pEntity; |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Finds an entity by target name or class name. |
|
// Input : pStartEntity - The entity to start from when doing the search. |
|
// szName - Entity name to search for. Treated as a target name first, |
|
// then as an entity class name, ie "info_target". |
|
// vecSrc - Center of search radius. |
|
// flRadius - Search radius for classname search, 0 to search everywhere. |
|
// pSearchingEntity - The entity that is doing the search. |
|
// pActivator - The activator entity if this was called from an input |
|
// or Use handler, NULL otherwise. |
|
// Output : Returns a pointer to the found entity, NULL if none. |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CGlobalEntityList::FindEntityGeneric( CBaseEntity *pStartEntity, const char *szName, CBaseEntity *pSearchingEntity, CBaseEntity *pActivator, CBaseEntity *pCaller ) |
|
{ |
|
CBaseEntity *pEntity = NULL; |
|
|
|
pEntity = gEntList.FindEntityByName( pStartEntity, szName, pSearchingEntity, pActivator, pCaller ); |
|
if (!pEntity) |
|
{ |
|
pEntity = gEntList.FindEntityByClassname( pStartEntity, szName ); |
|
} |
|
|
|
return pEntity; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Finds the first entity by target name or class name within a radius |
|
// Input : pStartEntity - The entity to start from when doing the search. |
|
// szName - Entity name to search for. Treated as a target name first, |
|
// then as an entity class name, ie "info_target". |
|
// vecSrc - Center of search radius. |
|
// flRadius - Search radius for classname search, 0 to search everywhere. |
|
// pSearchingEntity - The entity that is doing the search. |
|
// pActivator - The activator entity if this was called from an input |
|
// or Use handler, NULL otherwise. |
|
// Output : Returns a pointer to the found entity, NULL if none. |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CGlobalEntityList::FindEntityGenericWithin( CBaseEntity *pStartEntity, const char *szName, const Vector &vecSrc, float flRadius, CBaseEntity *pSearchingEntity, CBaseEntity *pActivator, CBaseEntity *pCaller ) |
|
{ |
|
CBaseEntity *pEntity = NULL; |
|
|
|
pEntity = gEntList.FindEntityByNameWithin( pStartEntity, szName, vecSrc, flRadius, pSearchingEntity, pActivator, pCaller ); |
|
if (!pEntity) |
|
{ |
|
pEntity = gEntList.FindEntityByClassnameWithin( pStartEntity, szName, vecSrc, flRadius ); |
|
} |
|
|
|
return pEntity; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Finds the nearest entity by target name or class name within a radius. |
|
// Input : pStartEntity - The entity to start from when doing the search. |
|
// szName - Entity name to search for. Treated as a target name first, |
|
// then as an entity class name, ie "info_target". |
|
// vecSrc - Center of search radius. |
|
// flRadius - Search radius for classname search, 0 to search everywhere. |
|
// pSearchingEntity - The entity that is doing the search. |
|
// pActivator - The activator entity if this was called from an input |
|
// or Use handler, NULL otherwise. |
|
// Output : Returns a pointer to the found entity, NULL if none. |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CGlobalEntityList::FindEntityGenericNearest( const char *szName, const Vector &vecSrc, float flRadius, CBaseEntity *pSearchingEntity, CBaseEntity *pActivator, CBaseEntity *pCaller ) |
|
{ |
|
CBaseEntity *pEntity = NULL; |
|
|
|
pEntity = gEntList.FindEntityByNameNearest( szName, vecSrc, flRadius, pSearchingEntity, pActivator, pCaller ); |
|
if (!pEntity) |
|
{ |
|
pEntity = gEntList.FindEntityByClassnameNearest( szName, vecSrc, flRadius ); |
|
} |
|
|
|
return pEntity; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Find the nearest entity along the facing direction from the given origin |
|
// within the angular threshold (ignores worldspawn) with the |
|
// given classname. |
|
// Input : origin - |
|
// facing - |
|
// threshold - |
|
// classname - |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CGlobalEntityList::FindEntityClassNearestFacing( const Vector &origin, const Vector &facing, float threshold, char *classname) |
|
{ |
|
float bestDot = threshold; |
|
CBaseEntity *best_ent = NULL; |
|
|
|
const CEntInfo *pInfo = FirstEntInfo(); |
|
|
|
for ( ;pInfo; pInfo = pInfo->m_pNext ) |
|
{ |
|
CBaseEntity *ent = (CBaseEntity *)pInfo->m_pEntity; |
|
if ( !ent ) |
|
{ |
|
DevWarning( "NULL entity in global entity list!\n" ); |
|
continue; |
|
} |
|
|
|
// FIXME: why is this skipping pointsize entities? |
|
if (ent->IsPointSized() ) |
|
continue; |
|
|
|
// Make vector to entity |
|
Vector to_ent = (ent->GetAbsOrigin() - origin); |
|
|
|
VectorNormalize( to_ent ); |
|
float dot = DotProduct (facing , to_ent ); |
|
if (dot > bestDot) |
|
{ |
|
if (FClassnameIs(ent,classname)) |
|
{ |
|
// Ignore if worldspawn |
|
if (!FClassnameIs( ent, "worldspawn" ) && !FClassnameIs( ent, "soundent")) |
|
{ |
|
bestDot = dot; |
|
best_ent = ent; |
|
} |
|
} |
|
} |
|
} |
|
return best_ent; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Find the nearest entity along the facing direction from the given origin |
|
// within the angular threshold (ignores worldspawn) |
|
// Input : origin - |
|
// facing - |
|
// threshold - |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CGlobalEntityList::FindEntityNearestFacing( const Vector &origin, const Vector &facing, float threshold) |
|
{ |
|
float bestDot = threshold; |
|
CBaseEntity *best_ent = NULL; |
|
|
|
const CEntInfo *pInfo = FirstEntInfo(); |
|
|
|
for ( ;pInfo; pInfo = pInfo->m_pNext ) |
|
{ |
|
CBaseEntity *ent = (CBaseEntity *)pInfo->m_pEntity; |
|
if ( !ent ) |
|
{ |
|
DevWarning( "NULL entity in global entity list!\n" ); |
|
continue; |
|
} |
|
|
|
// Ignore logical entities |
|
if (!ent->edict()) |
|
continue; |
|
|
|
// Make vector to entity |
|
Vector to_ent = ent->WorldSpaceCenter() - origin; |
|
VectorNormalize(to_ent); |
|
|
|
float dot = DotProduct( facing, to_ent ); |
|
if (dot <= bestDot) |
|
continue; |
|
|
|
// Ignore if worldspawn |
|
if (!FStrEq( STRING(ent->m_iClassname), "worldspawn") && !FStrEq( STRING(ent->m_iClassname), "soundent")) |
|
{ |
|
bestDot = dot; |
|
best_ent = ent; |
|
} |
|
} |
|
return best_ent; |
|
} |
|
|
|
|
|
void CGlobalEntityList::OnAddEntity( IHandleEntity *pEnt, CBaseHandle handle ) |
|
{ |
|
int i = handle.GetEntryIndex(); |
|
|
|
// record current list details |
|
m_iNumEnts++; |
|
if ( i > m_iHighestEnt ) |
|
m_iHighestEnt = i; |
|
|
|
// If it's a CBaseEntity, notify the listeners. |
|
CBaseEntity *pBaseEnt = static_cast<IServerUnknown*>(pEnt)->GetBaseEntity(); |
|
if ( pBaseEnt->edict() ) |
|
m_iNumEdicts++; |
|
|
|
// NOTE: Must be a CBaseEntity on server |
|
Assert( pBaseEnt ); |
|
//DevMsg(2,"Created %s\n", pBaseEnt->GetClassname() ); |
|
for ( i = m_entityListeners.Count()-1; i >= 0; i-- ) |
|
{ |
|
m_entityListeners[i]->OnEntityCreated( pBaseEnt ); |
|
} |
|
} |
|
|
|
|
|
void CGlobalEntityList::OnRemoveEntity( IHandleEntity *pEnt, CBaseHandle handle ) |
|
{ |
|
#ifdef DBGFLAG_ASSERT |
|
if ( !g_fInCleanupDelete ) |
|
{ |
|
int i; |
|
for ( i = 0; i < g_DeleteList.Count(); i++ ) |
|
{ |
|
if ( g_DeleteList[i]->GetEntityHandle() == pEnt ) |
|
{ |
|
g_DeleteList.FastRemove( i ); |
|
Msg( "ERROR: Entity being destroyed but previously threaded on g_DeleteList\n" ); |
|
break; |
|
} |
|
} |
|
} |
|
#endif |
|
|
|
CBaseEntity *pBaseEnt = static_cast<IServerUnknown*>(pEnt)->GetBaseEntity(); |
|
if ( pBaseEnt->edict() ) |
|
m_iNumEdicts--; |
|
|
|
m_iNumEnts--; |
|
} |
|
|
|
void CGlobalEntityList::NotifyCreateEntity( CBaseEntity *pEnt ) |
|
{ |
|
if ( !pEnt ) |
|
return; |
|
|
|
//DevMsg(2,"Deleted %s\n", pBaseEnt->GetClassname() ); |
|
for ( int i = m_entityListeners.Count()-1; i >= 0; i-- ) |
|
{ |
|
m_entityListeners[i]->OnEntityCreated( pEnt ); |
|
} |
|
} |
|
|
|
void CGlobalEntityList::NotifySpawn( CBaseEntity *pEnt ) |
|
{ |
|
if ( !pEnt ) |
|
return; |
|
|
|
//DevMsg(2,"Deleted %s\n", pBaseEnt->GetClassname() ); |
|
for ( int i = m_entityListeners.Count()-1; i >= 0; i-- ) |
|
{ |
|
m_entityListeners[i]->OnEntitySpawned( pEnt ); |
|
} |
|
} |
|
|
|
// NOTE: This doesn't happen in OnRemoveEntity() specifically because |
|
// listeners may want to reference the object as it's being deleted |
|
// OnRemoveEntity isn't called until the destructor and all data is invalid. |
|
void CGlobalEntityList::NotifyRemoveEntity( CBaseHandle hEnt ) |
|
{ |
|
CBaseEntity *pBaseEnt = GetBaseEntity( hEnt ); |
|
if ( !pBaseEnt ) |
|
return; |
|
|
|
//DevMsg(2,"Deleted %s\n", pBaseEnt->GetClassname() ); |
|
for ( int i = m_entityListeners.Count()-1; i >= 0; i-- ) |
|
{ |
|
m_entityListeners[i]->OnEntityDeleted( pBaseEnt ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// NOTIFY LIST |
|
// |
|
// Allows entities to get events fired when another entity changes |
|
//----------------------------------------------------------------------------- |
|
struct entitynotify_t |
|
{ |
|
CBaseEntity *pNotify; |
|
CBaseEntity *pWatched; |
|
}; |
|
class CNotifyList : public INotify, public IEntityListener |
|
{ |
|
public: |
|
// INotify |
|
void AddEntity( CBaseEntity *pNotify, CBaseEntity *pWatched ); |
|
void RemoveEntity( CBaseEntity *pNotify, CBaseEntity *pWatched ); |
|
void ReportNamedEvent( CBaseEntity *pEntity, const char *pEventName ); |
|
void ClearEntity( CBaseEntity *pNotify ); |
|
void ReportSystemEvent( CBaseEntity *pEntity, notify_system_event_t eventType, const notify_system_event_params_t ¶ms ); |
|
|
|
// IEntityListener |
|
virtual void OnEntityCreated( CBaseEntity *pEntity ); |
|
virtual void OnEntityDeleted( CBaseEntity *pEntity ); |
|
|
|
// Called from CEntityListSystem |
|
void LevelInitPreEntity(); |
|
void LevelShutdownPreEntity(); |
|
|
|
private: |
|
CUtlVector<entitynotify_t> m_notifyList; |
|
}; |
|
|
|
void CNotifyList::AddEntity( CBaseEntity *pNotify, CBaseEntity *pWatched ) |
|
{ |
|
// OPTIMIZE: Also flag pNotify for faster "RemoveAllNotify" ? |
|
pWatched->AddEFlags( EFL_NOTIFY ); |
|
int index = m_notifyList.AddToTail(); |
|
entitynotify_t ¬ify = m_notifyList[index]; |
|
notify.pNotify = pNotify; |
|
notify.pWatched = pWatched; |
|
} |
|
|
|
// Remove noitfication for an entity |
|
void CNotifyList::RemoveEntity( CBaseEntity *pNotify, CBaseEntity *pWatched ) |
|
{ |
|
for ( int i = m_notifyList.Count(); --i >= 0; ) |
|
{ |
|
if ( m_notifyList[i].pNotify == pNotify && m_notifyList[i].pWatched == pWatched) |
|
{ |
|
m_notifyList.FastRemove(i); |
|
} |
|
} |
|
} |
|
|
|
|
|
void CNotifyList::ReportNamedEvent( CBaseEntity *pEntity, const char *pInputName ) |
|
{ |
|
variant_t emptyVariant; |
|
|
|
if ( !pEntity->IsEFlagSet(EFL_NOTIFY) ) |
|
return; |
|
|
|
for ( int i = 0; i < m_notifyList.Count(); i++ ) |
|
{ |
|
if ( m_notifyList[i].pWatched == pEntity ) |
|
{ |
|
m_notifyList[i].pNotify->AcceptInput( pInputName, pEntity, pEntity, emptyVariant, 0 ); |
|
} |
|
} |
|
} |
|
|
|
void CNotifyList::LevelInitPreEntity() |
|
{ |
|
gEntList.AddListenerEntity( this ); |
|
} |
|
|
|
void CNotifyList::LevelShutdownPreEntity( void ) |
|
{ |
|
gEntList.RemoveListenerEntity( this ); |
|
m_notifyList.Purge(); |
|
} |
|
|
|
void CNotifyList::OnEntityCreated( CBaseEntity *pEntity ) |
|
{ |
|
} |
|
|
|
void CNotifyList::OnEntityDeleted( CBaseEntity *pEntity ) |
|
{ |
|
ReportDestroyEvent( pEntity ); |
|
ClearEntity( pEntity ); |
|
} |
|
|
|
|
|
// UNDONE: Slow linear search? |
|
void CNotifyList::ClearEntity( CBaseEntity *pNotify ) |
|
{ |
|
for ( int i = m_notifyList.Count(); --i >= 0; ) |
|
{ |
|
if ( m_notifyList[i].pNotify == pNotify || m_notifyList[i].pWatched == pNotify) |
|
{ |
|
m_notifyList.FastRemove(i); |
|
} |
|
} |
|
} |
|
|
|
void CNotifyList::ReportSystemEvent( CBaseEntity *pEntity, notify_system_event_t eventType, const notify_system_event_params_t ¶ms ) |
|
{ |
|
if ( !pEntity->IsEFlagSet(EFL_NOTIFY) ) |
|
return; |
|
|
|
for ( int i = 0; i < m_notifyList.Count(); i++ ) |
|
{ |
|
if ( m_notifyList[i].pWatched == pEntity ) |
|
{ |
|
m_notifyList[i].pNotify->NotifySystemEvent( pEntity, eventType, params ); |
|
} |
|
} |
|
} |
|
|
|
static CNotifyList g_NotifyList; |
|
INotify *g_pNotify = &g_NotifyList; |
|
|
|
class CEntityTouchManager : public IEntityListener |
|
{ |
|
public: |
|
// called by CEntityListSystem |
|
void LevelInitPreEntity() |
|
{ |
|
gEntList.AddListenerEntity( this ); |
|
Clear(); |
|
} |
|
void LevelShutdownPostEntity() |
|
{ |
|
gEntList.RemoveListenerEntity( this ); |
|
Clear(); |
|
} |
|
void FrameUpdatePostEntityThink(); |
|
|
|
void Clear() |
|
{ |
|
m_updateList.Purge(); |
|
} |
|
|
|
// IEntityListener |
|
virtual void OnEntityCreated( CBaseEntity *pEntity ) {} |
|
virtual void OnEntityDeleted( CBaseEntity *pEntity ) |
|
{ |
|
if ( !pEntity->GetCheckUntouch() ) |
|
return; |
|
int index = m_updateList.Find( pEntity ); |
|
if ( m_updateList.IsValidIndex(index) ) |
|
{ |
|
m_updateList.FastRemove( index ); |
|
} |
|
} |
|
void AddEntity( CBaseEntity *pEntity ) |
|
{ |
|
if ( pEntity->IsMarkedForDeletion() ) |
|
return; |
|
m_updateList.AddToTail( pEntity ); |
|
} |
|
|
|
private: |
|
CUtlVector<CBaseEntity *> m_updateList; |
|
}; |
|
|
|
static CEntityTouchManager g_TouchManager; |
|
|
|
void EntityTouch_Add( CBaseEntity *pEntity ) |
|
{ |
|
g_TouchManager.AddEntity( pEntity ); |
|
} |
|
|
|
|
|
void CEntityTouchManager::FrameUpdatePostEntityThink() |
|
{ |
|
VPROF( "CEntityTouchManager::FrameUpdatePostEntityThink" ); |
|
// Loop through all entities again, checking their untouch if flagged to do so |
|
|
|
int count = m_updateList.Count(); |
|
if ( count ) |
|
{ |
|
// copy off the list |
|
CBaseEntity **ents = (CBaseEntity **)stackalloc( sizeof(CBaseEntity *) * count ); |
|
memcpy( ents, m_updateList.Base(), sizeof(CBaseEntity *) * count ); |
|
// clear it |
|
m_updateList.RemoveAll(); |
|
|
|
// now update those ents |
|
for ( int i = 0; i < count; i++ ) |
|
{ |
|
//Assert( ents[i]->GetCheckUntouch() ); |
|
if ( ents[i]->GetCheckUntouch() ) |
|
{ |
|
ents[i]->PhysicsCheckForEntityUntouch(); |
|
} |
|
} |
|
stackfree( ents ); |
|
} |
|
} |
|
|
|
class CRespawnEntitiesFilter : public IMapEntityFilter |
|
{ |
|
public: |
|
virtual bool ShouldCreateEntity( const char *pClassname ) |
|
{ |
|
// Create everything but the world |
|
return Q_stricmp( pClassname, "worldspawn" ) != 0; |
|
} |
|
|
|
virtual CBaseEntity* CreateNextEntity( const char *pClassname ) |
|
{ |
|
return CreateEntityByName( pClassname ); |
|
} |
|
}; |
|
|
|
// One hook to rule them all... |
|
// Since most of the little list managers in here only need one or two of the game |
|
// system callbacks, this hook is a game system that passes them the appropriate callbacks |
|
class CEntityListSystem : public CAutoGameSystemPerFrame |
|
{ |
|
public: |
|
CEntityListSystem( char const *name ) : CAutoGameSystemPerFrame( name ) |
|
{ |
|
m_bRespawnAllEntities = false; |
|
} |
|
void LevelInitPreEntity() |
|
{ |
|
g_NotifyList.LevelInitPreEntity(); |
|
g_TouchManager.LevelInitPreEntity(); |
|
g_AimManager.LevelInitPreEntity(); |
|
g_SimThinkManager.LevelInitPreEntity(); |
|
#ifdef HL2_DLL |
|
OverrideMoveCache_LevelInitPreEntity(); |
|
#endif // HL2_DLL |
|
} |
|
void LevelShutdownPreEntity() |
|
{ |
|
g_NotifyList.LevelShutdownPreEntity(); |
|
} |
|
void LevelShutdownPostEntity() |
|
{ |
|
g_TouchManager.LevelShutdownPostEntity(); |
|
g_AimManager.LevelShutdownPostEntity(); |
|
g_SimThinkManager.LevelShutdownPostEntity(); |
|
#ifdef HL2_DLL |
|
OverrideMoveCache_LevelShutdownPostEntity(); |
|
#endif // HL2_DLL |
|
CBaseEntityClassList *pClassList = s_pClassLists; |
|
while ( pClassList ) |
|
{ |
|
pClassList->LevelShutdownPostEntity(); |
|
pClassList = pClassList->m_pNextClassList; |
|
} |
|
} |
|
|
|
void FrameUpdatePostEntityThink() |
|
{ |
|
g_TouchManager.FrameUpdatePostEntityThink(); |
|
|
|
if ( m_bRespawnAllEntities ) |
|
{ |
|
m_bRespawnAllEntities = false; |
|
|
|
// Don't change globalstate owing to deletion here |
|
GlobalEntity_EnableStateUpdates( false ); |
|
|
|
// Remove all entities |
|
int nPlayerIndex = -1; |
|
CBaseEntity *pEnt = gEntList.FirstEnt(); |
|
while ( pEnt ) |
|
{ |
|
CBaseEntity *pNextEnt = gEntList.NextEnt( pEnt ); |
|
if ( pEnt->IsPlayer() ) |
|
{ |
|
nPlayerIndex = pEnt->entindex(); |
|
} |
|
if ( !pEnt->IsEFlagSet( EFL_KEEP_ON_RECREATE_ENTITIES ) ) |
|
{ |
|
UTIL_Remove( pEnt ); |
|
} |
|
pEnt = pNextEnt; |
|
} |
|
|
|
gEntList.CleanupDeleteList(); |
|
|
|
GlobalEntity_EnableStateUpdates( true ); |
|
|
|
// Allows us to immediately re-use the edict indices we just freed to avoid edict overflow |
|
engine->AllowImmediateEdictReuse(); |
|
|
|
// Reset node counter used during load |
|
CNodeEnt::m_nNodeCount = 0; |
|
|
|
CRespawnEntitiesFilter filter; |
|
MapEntity_ParseAllEntities( engine->GetMapEntitiesString(), &filter, true ); |
|
|
|
// Allocate a CBasePlayer for pev, and call spawn |
|
if ( nPlayerIndex >= 0 ) |
|
{ |
|
edict_t *pEdict = engine->PEntityOfEntIndex( nPlayerIndex ); |
|
ClientPutInServer( pEdict, "unnamed" ); |
|
ClientActive( pEdict, false ); |
|
|
|
CBasePlayer *pPlayer = ( CBasePlayer * )CBaseEntity::Instance( pEdict ); |
|
SceneManager_ClientActive( pPlayer ); |
|
} |
|
} |
|
} |
|
|
|
bool m_bRespawnAllEntities; |
|
}; |
|
|
|
static CEntityListSystem g_EntityListSystem( "CEntityListSystem" ); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Respawns all entities in the level |
|
//----------------------------------------------------------------------------- |
|
void RespawnEntities() |
|
{ |
|
g_EntityListSystem.m_bRespawnAllEntities = true; |
|
} |
|
|
|
static ConCommand restart_entities( "respawn_entities", RespawnEntities, "Respawn all the entities in the map.", FCVAR_CHEAT | FCVAR_SPONLY ); |
|
|
|
class CSortedEntityList |
|
{ |
|
public: |
|
CSortedEntityList() : m_sortedList(), m_emptyCount(0) {} |
|
|
|
typedef CBaseEntity *ENTITYPTR; |
|
class CEntityReportLess |
|
{ |
|
public: |
|
bool Less( const ENTITYPTR &src1, const ENTITYPTR &src2, void *pCtx ) |
|
{ |
|
if ( stricmp( src1->GetClassname(), src2->GetClassname() ) < 0 ) |
|
return true; |
|
|
|
return false; |
|
} |
|
}; |
|
|
|
void AddEntityToList( CBaseEntity *pEntity ) |
|
{ |
|
if ( !pEntity ) |
|
{ |
|
m_emptyCount++; |
|
} |
|
else |
|
{ |
|
m_sortedList.Insert( pEntity ); |
|
} |
|
} |
|
void ReportEntityList() |
|
{ |
|
const char *pLastClass = ""; |
|
int count = 0; |
|
int edicts = 0; |
|
for ( int i = 0; i < m_sortedList.Count(); i++ ) |
|
{ |
|
CBaseEntity *pEntity = m_sortedList[i]; |
|
if ( !pEntity ) |
|
continue; |
|
|
|
if ( pEntity->edict() ) |
|
edicts++; |
|
|
|
const char *pClassname = pEntity->GetClassname(); |
|
if ( !FStrEq( pClassname, pLastClass ) ) |
|
{ |
|
if ( count ) |
|
{ |
|
Msg("Class: %s (%d)\n", pLastClass, count ); |
|
} |
|
|
|
pLastClass = pClassname; |
|
count = 1; |
|
} |
|
else |
|
count++; |
|
} |
|
if ( pLastClass[0] != 0 && count ) |
|
{ |
|
Msg("Class: %s (%d)\n", pLastClass, count ); |
|
} |
|
if ( m_sortedList.Count() ) |
|
{ |
|
Msg("Total %d entities (%d empty, %d edicts)\n", m_sortedList.Count(), m_emptyCount, edicts ); |
|
} |
|
} |
|
private: |
|
CUtlSortVector< CBaseEntity *, CEntityReportLess > m_sortedList; |
|
int m_emptyCount; |
|
}; |
|
|
|
|
|
|
|
CON_COMMAND(report_entities, "Lists all entities") |
|
{ |
|
if ( !UTIL_IsCommandIssuedByServerAdmin() ) |
|
return; |
|
|
|
CSortedEntityList list; |
|
CBaseEntity *pEntity = gEntList.FirstEnt(); |
|
while ( pEntity ) |
|
{ |
|
list.AddEntityToList( pEntity ); |
|
pEntity = gEntList.NextEnt( pEntity ); |
|
} |
|
list.ReportEntityList(); |
|
} |
|
|
|
|
|
CON_COMMAND(report_touchlinks, "Lists all touchlinks") |
|
{ |
|
if ( !UTIL_IsCommandIssuedByServerAdmin() ) |
|
return; |
|
|
|
CSortedEntityList list; |
|
CBaseEntity *pEntity = gEntList.FirstEnt(); |
|
const char *pClassname = NULL; |
|
if ( args.ArgC() > 1 ) |
|
{ |
|
pClassname = args.Arg(1); |
|
} |
|
while ( pEntity ) |
|
{ |
|
if ( !pClassname || FClassnameIs(pEntity, pClassname) ) |
|
{ |
|
touchlink_t *root = ( touchlink_t * )pEntity->GetDataObject( TOUCHLINK ); |
|
if ( root ) |
|
{ |
|
touchlink_t *link = root->nextLink; |
|
while ( link != root ) |
|
{ |
|
list.AddEntityToList( link->entityTouched ); |
|
link = link->nextLink; |
|
} |
|
} |
|
} |
|
pEntity = gEntList.NextEnt( pEntity ); |
|
} |
|
list.ReportEntityList(); |
|
} |
|
|
|
CON_COMMAND(report_simthinklist, "Lists all simulating/thinking entities") |
|
{ |
|
if ( !UTIL_IsCommandIssuedByServerAdmin() ) |
|
return; |
|
|
|
CBaseEntity *pTmp[NUM_ENT_ENTRIES]; |
|
int count = SimThink_ListCopy( pTmp, ARRAYSIZE(pTmp) ); |
|
|
|
CSortedEntityList list; |
|
for ( int i = 0; i < count; i++ ) |
|
{ |
|
if ( !pTmp[i] ) |
|
continue; |
|
|
|
list.AddEntityToList( pTmp[i] ); |
|
} |
|
list.ReportEntityList(); |
|
} |
|
|
|
|