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.
2238 lines
57 KiB
2238 lines
57 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//===========================================================================// |
|
|
|
#include "cbase.h" |
|
#include "engine/IEngineSound.h" |
|
#include "mempool.h" |
|
#include "movevars_shared.h" |
|
#include "utlrbtree.h" |
|
#include "tier0/vprof.h" |
|
#include "entitydatainstantiator.h" |
|
#include "positionwatcher.h" |
|
#include "movetype_push.h" |
|
#include "vphysicsupdateai.h" |
|
#include "igamesystem.h" |
|
#include "utlmultilist.h" |
|
#include "tier1/callqueue.h" |
|
|
|
#ifdef PORTAL |
|
#include "portal_util_shared.h" |
|
#endif |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
// memory pool for storing links between entities |
|
static CUtlMemoryPool g_EdictTouchLinks( sizeof(touchlink_t), MAX_EDICTS, CUtlMemoryPool::GROW_NONE, "g_EdictTouchLinks"); |
|
static CUtlMemoryPool g_EntityGroundLinks( sizeof( groundlink_t ), MAX_EDICTS, CUtlMemoryPool::GROW_NONE, "g_EntityGroundLinks"); |
|
|
|
struct watcher_t |
|
{ |
|
EHANDLE hWatcher; |
|
IWatcherCallback *pWatcherCallback; |
|
}; |
|
|
|
static CUtlMultiList<watcher_t, unsigned short> g_WatcherList; |
|
class CWatcherList |
|
{ |
|
public: |
|
//CWatcherList(); NOTE: Dataobj doesn't support constructors - it zeros the memory |
|
~CWatcherList(); // frees the positionwatcher_t's to the pool |
|
void Init(); |
|
|
|
void NotifyPositionChanged( CBaseEntity *pEntity ); |
|
void NotifyVPhysicsStateChanged( IPhysicsObject *pPhysics, CBaseEntity *pEntity, bool bAwake ); |
|
|
|
void AddToList( CBaseEntity *pWatcher ); |
|
void RemoveWatcher( CBaseEntity *pWatcher ); |
|
|
|
private: |
|
int GetCallbackObjects( IWatcherCallback **pList, int listMax ); |
|
|
|
unsigned short Find( CBaseEntity *pEntity ); |
|
unsigned short m_list; |
|
}; |
|
|
|
int linksallocated = 0; |
|
int groundlinksallocated = 0; |
|
|
|
// Prints warnings if any entity think functions take longer than this many milliseconds |
|
#ifdef _DEBUG |
|
#define DEF_THINK_LIMIT "20" |
|
#else |
|
#define DEF_THINK_LIMIT "10" |
|
#endif |
|
|
|
ConVar think_limit( "think_limit", DEF_THINK_LIMIT, FCVAR_REPLICATED, "Maximum think time in milliseconds, warning is printed if this is exceeded." ); |
|
#ifndef CLIENT_DLL |
|
ConVar debug_touchlinks( "debug_touchlinks", "0", 0, "Spew touch link activity" ); |
|
#define DebugTouchlinks() debug_touchlinks.GetBool() |
|
#else |
|
#define DebugTouchlinks() false |
|
#endif |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Portal-specific hack designed to eliminate re-entrancy in touch functions |
|
//----------------------------------------------------------------------------- |
|
class CPortalTouchScope |
|
{ |
|
public: |
|
CPortalTouchScope(); |
|
~CPortalTouchScope(); |
|
|
|
public: |
|
static int m_nDepth; |
|
static CCallQueue m_CallQueue; |
|
}; |
|
|
|
int CPortalTouchScope::m_nDepth = 0; |
|
CCallQueue CPortalTouchScope::m_CallQueue; |
|
|
|
CCallQueue *GetPortalCallQueue() |
|
{ |
|
return ( CPortalTouchScope::m_nDepth > 0 ) ? &CPortalTouchScope::m_CallQueue : NULL; |
|
} |
|
|
|
CPortalTouchScope::CPortalTouchScope() |
|
{ |
|
++m_nDepth; |
|
} |
|
|
|
CPortalTouchScope::~CPortalTouchScope() |
|
{ |
|
Assert( m_nDepth >= 1 ); |
|
if ( --m_nDepth == 0 ) |
|
{ |
|
m_CallQueue.CallQueued(); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: System for hanging objects off of CBaseEntity, etc. |
|
// Externalized data objects ( see sharreddefs.h for enum ) |
|
//----------------------------------------------------------------------------- |
|
class CDataObjectAccessSystem : public CAutoGameSystem |
|
{ |
|
public: |
|
|
|
enum |
|
{ |
|
MAX_ACCESSORS = 32, |
|
}; |
|
|
|
CDataObjectAccessSystem() |
|
{ |
|
// Cast to int to make it clear that we know we are comparing different enum types. |
|
COMPILE_TIME_ASSERT( (int)NUM_DATAOBJECT_TYPES <= (int)MAX_ACCESSORS ); |
|
|
|
Q_memset( m_Accessors, 0, sizeof( m_Accessors ) ); |
|
} |
|
|
|
virtual bool Init() |
|
{ |
|
AddDataAccessor( TOUCHLINK, new CEntityDataInstantiator< touchlink_t > ); |
|
AddDataAccessor( GROUNDLINK, new CEntityDataInstantiator< groundlink_t > ); |
|
AddDataAccessor( STEPSIMULATION, new CEntityDataInstantiator< StepSimulationData > ); |
|
AddDataAccessor( MODELSCALE, new CEntityDataInstantiator< ModelScale > ); |
|
AddDataAccessor( POSITIONWATCHER, new CEntityDataInstantiator< CWatcherList > ); |
|
AddDataAccessor( PHYSICSPUSHLIST, new CEntityDataInstantiator< physicspushlist_t > ); |
|
AddDataAccessor( VPHYSICSUPDATEAI, new CEntityDataInstantiator< vphysicsupdateai_t > ); |
|
AddDataAccessor( VPHYSICSWATCHER, new CEntityDataInstantiator< CWatcherList > ); |
|
|
|
return true; |
|
} |
|
|
|
virtual void Shutdown() |
|
{ |
|
for ( int i = 0; i < MAX_ACCESSORS; i++ ) |
|
{ |
|
delete m_Accessors[ i ]; |
|
m_Accessors[ i ] = 0; |
|
} |
|
} |
|
|
|
void *GetDataObject( int type, const CBaseEntity *instance ) |
|
{ |
|
if ( !IsValidType( type ) ) |
|
{ |
|
Assert( !"Bogus type" ); |
|
return NULL; |
|
} |
|
return m_Accessors[ type ]->GetDataObject( instance ); |
|
} |
|
|
|
void *CreateDataObject( int type, CBaseEntity *instance ) |
|
{ |
|
if ( !IsValidType( type ) ) |
|
{ |
|
Assert( !"Bogus type" ); |
|
return NULL; |
|
} |
|
|
|
return m_Accessors[ type ]->CreateDataObject( instance ); |
|
} |
|
|
|
void DestroyDataObject( int type, CBaseEntity *instance ) |
|
{ |
|
if ( !IsValidType( type ) ) |
|
{ |
|
Assert( !"Bogus type" ); |
|
return; |
|
} |
|
|
|
m_Accessors[ type ]->DestroyDataObject( instance ); |
|
} |
|
|
|
private: |
|
|
|
bool IsValidType( int type ) const |
|
{ |
|
if ( type < 0 || type >= MAX_ACCESSORS ) |
|
return false; |
|
|
|
if ( m_Accessors[ type ] == NULL ) |
|
return false; |
|
return true; |
|
} |
|
|
|
void AddDataAccessor( int type, IEntityDataInstantiator *instantiator ) |
|
{ |
|
if ( type < 0 || type >= MAX_ACCESSORS ) |
|
{ |
|
Assert( !"AddDataAccessor with out of range type!!!\n" ); |
|
return; |
|
} |
|
|
|
Assert( instantiator ); |
|
|
|
if ( m_Accessors[ type ] != NULL ) |
|
{ |
|
Assert( !"AddDataAccessor, duplicate adds!!!\n" ); |
|
return; |
|
} |
|
|
|
m_Accessors[ type ] = instantiator; |
|
} |
|
|
|
IEntityDataInstantiator *m_Accessors[ MAX_ACCESSORS ]; |
|
}; |
|
|
|
static CDataObjectAccessSystem g_DataObjectAccessSystem; |
|
|
|
bool CBaseEntity::HasDataObjectType( int type ) const |
|
{ |
|
Assert( type >= 0 && type < NUM_DATAOBJECT_TYPES ); |
|
return ( m_fDataObjectTypes & (1<<type) ) ? true : false; |
|
} |
|
|
|
void CBaseEntity::AddDataObjectType( int type ) |
|
{ |
|
Assert( type >= 0 && type < NUM_DATAOBJECT_TYPES ); |
|
m_fDataObjectTypes |= (1<<type); |
|
} |
|
|
|
void CBaseEntity::RemoveDataObjectType( int type ) |
|
{ |
|
Assert( type >= 0 && type < NUM_DATAOBJECT_TYPES ); |
|
m_fDataObjectTypes &= ~(1<<type); |
|
} |
|
|
|
void *CBaseEntity::GetDataObject( int type ) |
|
{ |
|
Assert( type >= 0 && type < NUM_DATAOBJECT_TYPES ); |
|
if ( !HasDataObjectType( type ) ) |
|
return NULL; |
|
return g_DataObjectAccessSystem.GetDataObject( type, this ); |
|
} |
|
|
|
void *CBaseEntity::CreateDataObject( int type ) |
|
{ |
|
Assert( type >= 0 && type < NUM_DATAOBJECT_TYPES ); |
|
AddDataObjectType( type ); |
|
return g_DataObjectAccessSystem.CreateDataObject( type, this ); |
|
} |
|
|
|
void CBaseEntity::DestroyDataObject( int type ) |
|
{ |
|
Assert( type >= 0 && type < NUM_DATAOBJECT_TYPES ); |
|
if ( !HasDataObjectType( type ) ) |
|
return; |
|
g_DataObjectAccessSystem.DestroyDataObject( type, this ); |
|
RemoveDataObjectType( type ); |
|
} |
|
|
|
void CWatcherList::Init() |
|
{ |
|
m_list = g_WatcherList.CreateList(); |
|
} |
|
|
|
CWatcherList::~CWatcherList() |
|
{ |
|
g_WatcherList.DestroyList( m_list ); |
|
} |
|
|
|
int CWatcherList::GetCallbackObjects( IWatcherCallback **pList, int listMax ) |
|
{ |
|
int index = 0; |
|
unsigned short next = g_WatcherList.InvalidIndex(); |
|
for ( unsigned short node = g_WatcherList.Head( m_list ); node != g_WatcherList.InvalidIndex(); node = next ) |
|
{ |
|
next = g_WatcherList.Next( node ); |
|
watcher_t *pNode = &g_WatcherList.Element(node); |
|
if ( pNode->hWatcher.Get() ) |
|
{ |
|
pList[index] = pNode->pWatcherCallback; |
|
index++; |
|
if ( index >= listMax ) |
|
{ |
|
Assert(0); |
|
return index; |
|
} |
|
} |
|
else |
|
{ |
|
g_WatcherList.Remove( m_list, node ); |
|
} |
|
} |
|
return index; |
|
} |
|
|
|
void CWatcherList::NotifyPositionChanged( CBaseEntity *pEntity ) |
|
{ |
|
IWatcherCallback *pCallbacks[1024]; // HACKHACK: Assumes this list is big enough |
|
int count = GetCallbackObjects( pCallbacks, ARRAYSIZE(pCallbacks) ); |
|
for ( int i = 0; i < count; i++ ) |
|
{ |
|
IPositionWatcher *pWatcher = assert_cast<IPositionWatcher *>(pCallbacks[i]); |
|
if ( pWatcher ) |
|
{ |
|
pWatcher->NotifyPositionChanged(pEntity); |
|
} |
|
} |
|
} |
|
|
|
void CWatcherList::NotifyVPhysicsStateChanged( IPhysicsObject *pPhysics, CBaseEntity *pEntity, bool bAwake ) |
|
{ |
|
IWatcherCallback *pCallbacks[1024]; // HACKHACK: Assumes this list is big enough! |
|
int count = GetCallbackObjects( pCallbacks, ARRAYSIZE(pCallbacks) ); |
|
for ( int i = 0; i < count; i++ ) |
|
{ |
|
IVPhysicsWatcher *pWatcher = assert_cast<IVPhysicsWatcher *>(pCallbacks[i]); |
|
if ( pWatcher ) |
|
{ |
|
pWatcher->NotifyVPhysicsStateChanged(pPhysics, pEntity, bAwake); |
|
} |
|
} |
|
} |
|
|
|
unsigned short CWatcherList::Find( CBaseEntity *pEntity ) |
|
{ |
|
unsigned short next = g_WatcherList.InvalidIndex(); |
|
for ( unsigned short node = g_WatcherList.Head( m_list ); node != g_WatcherList.InvalidIndex(); node = next ) |
|
{ |
|
next = g_WatcherList.Next( node ); |
|
watcher_t *pNode = &g_WatcherList.Element(node); |
|
if ( pNode->hWatcher.Get() == pEntity ) |
|
{ |
|
return node; |
|
} |
|
} |
|
return g_WatcherList.InvalidIndex(); |
|
} |
|
|
|
void CWatcherList::RemoveWatcher( CBaseEntity *pEntity ) |
|
{ |
|
unsigned short node = Find( pEntity ); |
|
if ( node != g_WatcherList.InvalidIndex() ) |
|
{ |
|
g_WatcherList.Remove( m_list, node ); |
|
} |
|
} |
|
|
|
|
|
void CWatcherList::AddToList( CBaseEntity *pWatcher ) |
|
{ |
|
unsigned short node = Find( pWatcher ); |
|
if ( node == g_WatcherList.InvalidIndex() ) |
|
{ |
|
watcher_t watcher; |
|
watcher.hWatcher = pWatcher; |
|
// save this separately so we can use the EHANDLE to test for deletion |
|
watcher.pWatcherCallback = dynamic_cast<IWatcherCallback *> (pWatcher); |
|
|
|
if ( watcher.pWatcherCallback ) |
|
{ |
|
g_WatcherList.AddToTail( m_list, watcher ); |
|
} |
|
} |
|
} |
|
|
|
static void AddWatcherToEntity( CBaseEntity *pWatcher, CBaseEntity *pEntity, int watcherType ) |
|
{ |
|
CWatcherList *pList = (CWatcherList *)pEntity->GetDataObject(watcherType); |
|
if ( !pList ) |
|
{ |
|
pList = ( CWatcherList * )pEntity->CreateDataObject( watcherType ); |
|
pList->Init(); |
|
} |
|
|
|
pList->AddToList( pWatcher ); |
|
} |
|
|
|
static void RemoveWatcherFromEntity( CBaseEntity *pWatcher, CBaseEntity *pEntity, int watcherType ) |
|
{ |
|
CWatcherList *pList = (CWatcherList *)pEntity->GetDataObject(watcherType); |
|
if ( pList ) |
|
{ |
|
pList->RemoveWatcher( pWatcher ); |
|
} |
|
} |
|
|
|
void WatchPositionChanges( CBaseEntity *pWatcher, CBaseEntity *pMovingEntity ) |
|
{ |
|
AddWatcherToEntity( pWatcher, pMovingEntity, POSITIONWATCHER ); |
|
} |
|
|
|
void RemovePositionWatcher( CBaseEntity *pWatcher, CBaseEntity *pMovingEntity ) |
|
{ |
|
RemoveWatcherFromEntity( pWatcher, pMovingEntity, POSITIONWATCHER ); |
|
} |
|
|
|
void ReportPositionChanged( CBaseEntity *pMovedEntity ) |
|
{ |
|
CWatcherList *pList = (CWatcherList *)pMovedEntity->GetDataObject(POSITIONWATCHER); |
|
if ( pList ) |
|
{ |
|
pList->NotifyPositionChanged( pMovedEntity ); |
|
} |
|
} |
|
|
|
void WatchVPhysicsStateChanges( CBaseEntity *pWatcher, CBaseEntity *pPhysicsEntity ) |
|
{ |
|
AddWatcherToEntity( pWatcher, pPhysicsEntity, VPHYSICSWATCHER ); |
|
} |
|
|
|
void RemoveVPhysicsStateWatcher( CBaseEntity *pWatcher, CBaseEntity *pPhysicsEntity ) |
|
{ |
|
AddWatcherToEntity( pWatcher, pPhysicsEntity, VPHYSICSWATCHER ); |
|
} |
|
|
|
void ReportVPhysicsStateChanged( IPhysicsObject *pPhysics, CBaseEntity *pEntity, bool bAwake ) |
|
{ |
|
CWatcherList *pList = (CWatcherList *)pEntity->GetDataObject(VPHYSICSWATCHER); |
|
if ( pList ) |
|
{ |
|
pList->NotifyVPhysicsStateChanged( pPhysics, pEntity, bAwake ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::DestroyAllDataObjects( void ) |
|
{ |
|
int i; |
|
for ( i = 0; i < NUM_DATAOBJECT_TYPES; i++ ) |
|
{ |
|
if ( HasDataObjectType( i ) ) |
|
{ |
|
DestroyDataObject( i ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// For debugging |
|
//----------------------------------------------------------------------------- |
|
|
|
#ifdef GAME_DLL |
|
|
|
void SpewLinks() |
|
{ |
|
int nCount = 0; |
|
for ( CBaseEntity *pClass = gEntList.FirstEnt(); pClass != NULL; pClass = gEntList.NextEnt(pClass) ) |
|
{ |
|
if ( pClass /*&& !pClass->IsDormant()*/ ) |
|
{ |
|
touchlink_t *root = ( touchlink_t * )pClass->GetDataObject( TOUCHLINK ); |
|
if ( root ) |
|
{ |
|
|
|
// check if the edict is already in the list |
|
for ( touchlink_t *link = root->nextLink; link != root; link = link->nextLink ) |
|
{ |
|
++nCount; |
|
Msg("[%d] (%d) Link %d (%s) -> %d (%s)\n", nCount, pClass->IsDormant(), |
|
pClass->entindex(), pClass->GetClassname(), |
|
link->entityTouched->entindex(), link->entityTouched->GetClassname() ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
#endif |
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns the actual gravity |
|
//----------------------------------------------------------------------------- |
|
static inline float GetActualGravity( CBaseEntity *pEnt ) |
|
{ |
|
float ent_gravity = pEnt->GetGravity(); |
|
if ( ent_gravity == 0.0f ) |
|
{ |
|
ent_gravity = 1.0f; |
|
} |
|
|
|
return ent_gravity * GetCurrentGravity(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : inline touchlink_t |
|
//----------------------------------------------------------------------------- |
|
inline touchlink_t *AllocTouchLink( void ) |
|
{ |
|
touchlink_t *link = (touchlink_t*)g_EdictTouchLinks.Alloc( sizeof(touchlink_t) ); |
|
if ( link ) |
|
{ |
|
++linksallocated; |
|
} |
|
else |
|
{ |
|
DevWarning( "AllocTouchLink: failed to allocate touchlink_t.\n" ); |
|
} |
|
|
|
return link; |
|
} |
|
|
|
static touchlink_t *g_pNextLink = NULL; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *link - |
|
// Output : inline void |
|
//----------------------------------------------------------------------------- |
|
inline void FreeTouchLink( touchlink_t *link ) |
|
{ |
|
if ( link ) |
|
{ |
|
if ( link == g_pNextLink ) |
|
{ |
|
g_pNextLink = link->nextLink; |
|
} |
|
--linksallocated; |
|
link->prevLink = link->nextLink = NULL; |
|
} |
|
|
|
// Necessary to catch crashes |
|
g_EdictTouchLinks.Free( link ); |
|
} |
|
|
|
#ifdef STAGING_ONLY |
|
#ifndef CLIENT_DLL |
|
ConVar sv_groundlink_debug( "sv_groundlink_debug", "0", FCVAR_NONE, "Enable logging of alloc/free operations for debugging." ); |
|
#endif |
|
#endif // STAGING_ONLY |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : inline groundlink_t |
|
//----------------------------------------------------------------------------- |
|
inline groundlink_t *AllocGroundLink( void ) |
|
{ |
|
groundlink_t *link = (groundlink_t*)g_EntityGroundLinks.Alloc( sizeof(groundlink_t) ); |
|
if ( link ) |
|
{ |
|
++groundlinksallocated; |
|
} |
|
else |
|
{ |
|
DevMsg( "AllocGroundLink: failed to allocate groundlink_t.!!! groundlinksallocated=%d g_EntityGroundLinks.Count()=%d\n", groundlinksallocated, g_EntityGroundLinks.Count() ); |
|
} |
|
|
|
#ifdef STAGING_ONLY |
|
#ifndef CLIENT_DLL |
|
if ( sv_groundlink_debug.GetBool() ) |
|
{ |
|
UTIL_LogPrintf( "Groundlink Alloc: %p at %d\n", link, groundlinksallocated ); |
|
} |
|
#endif |
|
#endif // STAGING_ONLY |
|
|
|
return link; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *link - |
|
// Output : inline void |
|
//----------------------------------------------------------------------------- |
|
inline void FreeGroundLink( groundlink_t *link ) |
|
{ |
|
#ifdef STAGING_ONLY |
|
#ifndef CLIENT_DLL |
|
if ( sv_groundlink_debug.GetBool() ) |
|
{ |
|
UTIL_LogPrintf( "Groundlink Free: %p at %d\n", link, groundlinksallocated ); |
|
} |
|
#endif |
|
#endif // STAGING_ONLY |
|
|
|
if ( link ) |
|
{ |
|
--groundlinksallocated; |
|
} |
|
|
|
g_EntityGroundLinks.Free( link ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CBaseEntity::IsCurrentlyTouching( void ) const |
|
{ |
|
if ( HasDataObjectType( TOUCHLINK ) ) |
|
{ |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
static bool g_bCleanupDatObject = true; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Checks to see if any entities that have been touching this one |
|
// have stopped touching it, and notify the entity if so. |
|
// Called at the end of a frame, after all the entities have run |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PhysicsCheckForEntityUntouch( void ) |
|
{ |
|
Assert( g_pNextLink == NULL ); |
|
|
|
touchlink_t *link; |
|
|
|
touchlink_t *root = ( touchlink_t * )GetDataObject( TOUCHLINK ); |
|
if ( root ) |
|
{ |
|
#ifdef PORTAL |
|
CPortalTouchScope scope; |
|
#endif |
|
bool saveCleanup = g_bCleanupDatObject; |
|
g_bCleanupDatObject = false; |
|
|
|
link = root->nextLink; |
|
while ( link != root ) |
|
{ |
|
g_pNextLink = link->nextLink; |
|
|
|
// these touchlinks are not polled. The ents are touching due to an outside |
|
// system that will add/delete them as necessary (vphysics in this case) |
|
if ( link->touchStamp == TOUCHSTAMP_EVENT_DRIVEN ) |
|
{ |
|
// refresh the touch call |
|
PhysicsTouch( link->entityTouched ); |
|
} |
|
else |
|
{ |
|
// check to see if the touch stamp is up to date |
|
if ( link->touchStamp != touchStamp ) |
|
{ |
|
// stamp is out of data, so entities are no longer touching |
|
// remove self from other entities touch list |
|
PhysicsNotifyOtherOfUntouch( this, link->entityTouched ); |
|
|
|
// remove other entity from this list |
|
PhysicsRemoveToucher( this, link ); |
|
} |
|
} |
|
|
|
link = g_pNextLink; |
|
} |
|
|
|
g_bCleanupDatObject = saveCleanup; |
|
|
|
// Nothing left in list, destroy root |
|
if ( root->nextLink == root && |
|
root->prevLink == root ) |
|
{ |
|
DestroyDataObject( TOUCHLINK ); |
|
} |
|
} |
|
|
|
g_pNextLink = NULL; |
|
|
|
SetCheckUntouch( false ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: notifies an entity than another touching entity has moved out of contact. |
|
// Input : *other - the entity to be acted upon |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PhysicsNotifyOtherOfUntouch( CBaseEntity *ent, CBaseEntity *other ) |
|
{ |
|
if ( !other ) |
|
return; |
|
|
|
// loop through ed's touch list, looking for the notifier |
|
// remove and call untouch if found |
|
touchlink_t *root = ( touchlink_t * )other->GetDataObject( TOUCHLINK ); |
|
if ( root ) |
|
{ |
|
touchlink_t *link = root->nextLink; |
|
while ( link != root ) |
|
{ |
|
if ( link->entityTouched == ent ) |
|
{ |
|
PhysicsRemoveToucher( other, link ); |
|
|
|
// Check for complete removal |
|
if ( g_bCleanupDatObject && |
|
root->nextLink == root && |
|
root->prevLink == root ) |
|
{ |
|
other->DestroyDataObject( TOUCHLINK ); |
|
} |
|
return; |
|
} |
|
|
|
link = link->nextLink; |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: removes a toucher from the list |
|
// Input : *link - the link to remove |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PhysicsRemoveToucher( CBaseEntity *otherEntity, touchlink_t *link ) |
|
{ |
|
// Every start Touch gets a corresponding end touch |
|
if ( (link->flags & FTOUCHLINK_START_TOUCH) && |
|
link->entityTouched != NULL && |
|
otherEntity != NULL ) |
|
{ |
|
otherEntity->EndTouch( link->entityTouched ); |
|
} |
|
|
|
link->nextLink->prevLink = link->prevLink; |
|
link->prevLink->nextLink = link->nextLink; |
|
|
|
if ( DebugTouchlinks() ) |
|
Msg( "remove 0x%p: %s-%s (%d-%d) [%d in play, %d max]\n", link, link->entityTouched->GetDebugName(), otherEntity->GetDebugName(), link->entityTouched->entindex(), otherEntity->entindex(), linksallocated, g_EdictTouchLinks.PeakCount() ); |
|
FreeTouchLink( link ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Clears all touches from the list |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PhysicsRemoveTouchedList( CBaseEntity *ent ) |
|
{ |
|
#ifdef PORTAL |
|
CPortalTouchScope scope; |
|
#endif |
|
|
|
touchlink_t *link, *nextLink; |
|
|
|
touchlink_t *root = ( touchlink_t * )ent->GetDataObject( TOUCHLINK ); |
|
if ( root ) |
|
{ |
|
link = root->nextLink; |
|
bool saveCleanup = g_bCleanupDatObject; |
|
g_bCleanupDatObject = false; |
|
while ( link && link != root ) |
|
{ |
|
nextLink = link->nextLink; |
|
|
|
// notify the other entity that this ent has gone away |
|
PhysicsNotifyOtherOfUntouch( ent, link->entityTouched ); |
|
|
|
// kill it |
|
if ( DebugTouchlinks() ) |
|
Msg( "remove 0x%p: %s-%s (%d-%d) [%d in play, %d max]\n", link, ent->GetDebugName(), link->entityTouched->GetDebugName(), ent->entindex(), link->entityTouched->entindex(), linksallocated, g_EdictTouchLinks.PeakCount() ); |
|
FreeTouchLink( link ); |
|
link = nextLink; |
|
} |
|
|
|
g_bCleanupDatObject = saveCleanup; |
|
ent->DestroyDataObject( TOUCHLINK ); |
|
} |
|
|
|
ent->touchStamp = 0; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *other - |
|
// Output : groundlink_t |
|
//----------------------------------------------------------------------------- |
|
groundlink_t *CBaseEntity::AddEntityToGroundList( CBaseEntity *other ) |
|
{ |
|
groundlink_t *link; |
|
|
|
if ( this == other ) |
|
return NULL; |
|
|
|
// check if the edict is already in the list |
|
groundlink_t *root = ( groundlink_t * )GetDataObject( GROUNDLINK ); |
|
if ( root ) |
|
{ |
|
for ( link = root->nextLink; link != root; link = link->nextLink ) |
|
{ |
|
if ( link->entity == other ) |
|
{ |
|
// no more to do |
|
return link; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
root = ( groundlink_t * )CreateDataObject( GROUNDLINK ); |
|
root->prevLink = root->nextLink = root; |
|
} |
|
|
|
// entity is not in list, so it's a new touch |
|
// add it to the touched list and then call the touch function |
|
|
|
// build new link |
|
link = AllocGroundLink(); |
|
if ( !link ) |
|
return NULL; |
|
|
|
link->entity = other; |
|
// add it to the list |
|
link->nextLink = root->nextLink; |
|
link->prevLink = root; |
|
link->prevLink->nextLink = link; |
|
link->nextLink->prevLink = link; |
|
|
|
PhysicsStartGroundContact( other ); |
|
|
|
return link; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Called whenever two entities come in contact |
|
// Input : *pentOther - the entity who it has touched |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PhysicsStartGroundContact( CBaseEntity *pentOther ) |
|
{ |
|
if ( !pentOther ) |
|
return; |
|
|
|
if ( !(IsMarkedForDeletion() || pentOther->IsMarkedForDeletion()) ) |
|
{ |
|
pentOther->StartGroundContact( this ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: notifies an entity than another touching entity has moved out of contact. |
|
// Input : *other - the entity to be acted upon |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PhysicsNotifyOtherOfGroundRemoval( CBaseEntity *ent, CBaseEntity *other ) |
|
{ |
|
if ( !other ) |
|
return; |
|
|
|
// loop through ed's touch list, looking for the notifier |
|
// remove and call untouch if found |
|
groundlink_t *root = ( groundlink_t * )other->GetDataObject( GROUNDLINK ); |
|
if ( root ) |
|
{ |
|
groundlink_t *link = root->nextLink; |
|
while ( link != root ) |
|
{ |
|
if ( link->entity == ent ) |
|
{ |
|
PhysicsRemoveGround( other, link ); |
|
|
|
if ( root->nextLink == root && |
|
root->prevLink == root ) |
|
{ |
|
other->DestroyDataObject( GROUNDLINK ); |
|
} |
|
return; |
|
} |
|
|
|
link = link->nextLink; |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: removes a toucher from the list |
|
// Input : *link - the link to remove |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PhysicsRemoveGround( CBaseEntity *other, groundlink_t *link ) |
|
{ |
|
// Every start Touch gets a corresponding end touch |
|
if ( link->entity != NULL ) |
|
{ |
|
CBaseEntity *linkEntity = link->entity; |
|
CBaseEntity *otherEntity = other; |
|
if ( linkEntity && otherEntity ) |
|
{ |
|
linkEntity->EndGroundContact( otherEntity ); |
|
} |
|
} |
|
|
|
link->nextLink->prevLink = link->prevLink; |
|
link->prevLink->nextLink = link->nextLink; |
|
FreeGroundLink( link ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: static method to remove ground list for an entity |
|
// Input : *ent - |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PhysicsRemoveGroundList( CBaseEntity *ent ) |
|
{ |
|
groundlink_t *link, *nextLink; |
|
|
|
groundlink_t *root = ( groundlink_t * )ent->GetDataObject( GROUNDLINK ); |
|
if ( root ) |
|
{ |
|
link = root->nextLink; |
|
while ( link && link != root ) |
|
{ |
|
nextLink = link->nextLink; |
|
|
|
// notify the other entity that this ent has gone away |
|
PhysicsNotifyOtherOfGroundRemoval( ent, link->entity ); |
|
|
|
// kill it |
|
FreeGroundLink( link ); |
|
|
|
link = nextLink; |
|
} |
|
|
|
ent->DestroyDataObject( GROUNDLINK ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Called every frame that two entities are touching |
|
// Input : *pentOther - the entity who it has touched |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PhysicsTouch( CBaseEntity *pentOther ) |
|
{ |
|
if ( pentOther ) |
|
{ |
|
if ( !(IsMarkedForDeletion() || pentOther->IsMarkedForDeletion()) ) |
|
{ |
|
Touch( pentOther ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Called whenever two entities come in contact |
|
// Input : *pentOther - the entity who it has touched |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PhysicsStartTouch( CBaseEntity *pentOther ) |
|
{ |
|
if ( pentOther ) |
|
{ |
|
if ( !(IsMarkedForDeletion() || pentOther->IsMarkedForDeletion()) ) |
|
{ |
|
StartTouch( pentOther ); |
|
Touch( pentOther ); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Marks in an entity that it is touching another entity, and calls |
|
// it's Touch() function if it is a new touch. |
|
// Stamps the touch link with the new time so that when we check for |
|
// untouch we know things haven't changed. |
|
// Input : *other - entity that it is in contact with |
|
//----------------------------------------------------------------------------- |
|
touchlink_t *CBaseEntity::PhysicsMarkEntityAsTouched( CBaseEntity *other ) |
|
{ |
|
touchlink_t *link; |
|
|
|
if ( this == other ) |
|
return NULL; |
|
|
|
// Entities in hierarchy should not interact |
|
if ( (this->GetMoveParent() == other) || (this == other->GetMoveParent()) ) |
|
return NULL; |
|
|
|
// check if either entity doesn't generate touch functions |
|
if ( (GetFlags() | other->GetFlags()) & FL_DONTTOUCH ) |
|
return NULL; |
|
|
|
// Pure triggers should not touch each other |
|
if ( IsSolidFlagSet( FSOLID_TRIGGER ) && other->IsSolidFlagSet( FSOLID_TRIGGER ) ) |
|
{ |
|
if (!IsSolid() && !other->IsSolid()) |
|
return NULL; |
|
} |
|
|
|
// Don't do touching if marked for deletion |
|
if ( other->IsMarkedForDeletion() ) |
|
{ |
|
return NULL; |
|
} |
|
|
|
if ( IsMarkedForDeletion() ) |
|
{ |
|
return NULL; |
|
} |
|
|
|
#ifdef PORTAL |
|
CPortalTouchScope scope; |
|
#endif |
|
|
|
// check if the edict is already in the list |
|
touchlink_t *root = ( touchlink_t * )GetDataObject( TOUCHLINK ); |
|
if ( root ) |
|
{ |
|
for ( link = root->nextLink; link != root; link = link->nextLink ) |
|
{ |
|
if ( link->entityTouched == other ) |
|
{ |
|
// update stamp |
|
link->touchStamp = touchStamp; |
|
|
|
if ( !CBaseEntity::sm_bDisableTouchFuncs ) |
|
{ |
|
PhysicsTouch( other ); |
|
} |
|
|
|
// no more to do |
|
return link; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
// Allocate the root object |
|
root = ( touchlink_t * )CreateDataObject( TOUCHLINK ); |
|
root->nextLink = root->prevLink = root; |
|
} |
|
|
|
// entity is not in list, so it's a new touch |
|
// add it to the touched list and then call the touch function |
|
|
|
// build new link |
|
link = AllocTouchLink(); |
|
if ( DebugTouchlinks() ) |
|
Msg( "add 0x%p: %s-%s (%d-%d) [%d in play, %d max]\n", link, GetDebugName(), other->GetDebugName(), entindex(), other->entindex(), linksallocated, g_EdictTouchLinks.PeakCount() ); |
|
if ( !link ) |
|
return NULL; |
|
|
|
link->touchStamp = touchStamp; |
|
link->entityTouched = other; |
|
link->flags = 0; |
|
// add it to the list |
|
link->nextLink = root->nextLink; |
|
link->prevLink = root; |
|
link->prevLink->nextLink = link; |
|
link->nextLink->prevLink = link; |
|
|
|
// non-solid entities don't get touched |
|
bool bShouldTouch = (IsSolid() && !IsSolidFlagSet(FSOLID_VOLUME_CONTENTS)) || IsSolidFlagSet(FSOLID_TRIGGER); |
|
if ( bShouldTouch && !other->IsSolidFlagSet(FSOLID_TRIGGER) ) |
|
{ |
|
link->flags |= FTOUCHLINK_START_TOUCH; |
|
if ( !CBaseEntity::sm_bDisableTouchFuncs ) |
|
{ |
|
PhysicsStartTouch( other ); |
|
} |
|
} |
|
|
|
return link; |
|
} |
|
|
|
static trace_t g_TouchTrace; |
|
const trace_t &CBaseEntity::GetTouchTrace( void ) |
|
{ |
|
return g_TouchTrace; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Marks the fact that two edicts are in contact |
|
// Input : *other - other entity |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PhysicsMarkEntitiesAsTouching( CBaseEntity *other, trace_t &trace ) |
|
{ |
|
g_TouchTrace = trace; |
|
PhysicsMarkEntityAsTouched( other ); |
|
other->PhysicsMarkEntityAsTouched( this ); |
|
} |
|
|
|
void CBaseEntity::PhysicsMarkEntitiesAsTouchingEventDriven( CBaseEntity *other, trace_t &trace ) |
|
{ |
|
g_TouchTrace = trace; |
|
g_TouchTrace.m_pEnt = other; |
|
|
|
touchlink_t *link; |
|
link = this->PhysicsMarkEntityAsTouched( other ); |
|
if ( link ) |
|
{ |
|
// mark these links as event driven so they aren't untouched the next frame |
|
// when the physics doesn't refresh them |
|
link->touchStamp = TOUCHSTAMP_EVENT_DRIVEN; |
|
} |
|
g_TouchTrace.m_pEnt = this; |
|
link = other->PhysicsMarkEntityAsTouched( this ); |
|
if ( link ) |
|
{ |
|
link->touchStamp = TOUCHSTAMP_EVENT_DRIVEN; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Two entities have touched, so run their touch functions |
|
// Input : *other - |
|
// *ptrace - |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PhysicsImpact( CBaseEntity *other, trace_t &trace ) |
|
{ |
|
if ( !other ) |
|
{ |
|
return; |
|
} |
|
|
|
// If either of the entities is flagged to be deleted, |
|
// don't call the touch functions |
|
if ( ( GetFlags() | other->GetFlags() ) & FL_KILLME ) |
|
{ |
|
return; |
|
} |
|
|
|
PhysicsMarkEntitiesAsTouching( other, trace ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns the mask of what is solid for the given entity |
|
// Output : unsigned int |
|
//----------------------------------------------------------------------------- |
|
unsigned int CBaseEntity::PhysicsSolidMaskForEntity( void ) const |
|
{ |
|
return MASK_SOLID; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes the water level + type |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::UpdateWaterState() |
|
{ |
|
// FIXME: This computation is nonsensical for rigid child attachments |
|
// Should we just grab the type + level of the parent? |
|
// Probably for rigid children anyways... |
|
|
|
// Compute the point to check for water state |
|
Vector point; |
|
CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.5f, 0.0f ), &point ); |
|
|
|
SetWaterLevel( 0 ); |
|
SetWaterType( CONTENTS_EMPTY ); |
|
int cont = UTIL_PointContents (point); |
|
|
|
if (( cont & MASK_WATER ) == 0) |
|
return; |
|
|
|
SetWaterType( cont ); |
|
SetWaterLevel( 1 ); |
|
|
|
// point sized entities are always fully submerged |
|
if ( IsPointSized() ) |
|
{ |
|
SetWaterLevel( 3 ); |
|
} |
|
else |
|
{ |
|
// Check the exact center of the box |
|
point[2] = WorldSpaceCenter().z; |
|
|
|
int midcont = UTIL_PointContents (point); |
|
if ( midcont & MASK_WATER ) |
|
{ |
|
// Now check where the eyes are... |
|
SetWaterLevel( 2 ); |
|
point[2] = EyePosition().z; |
|
|
|
int eyecont = UTIL_PointContents (point); |
|
if ( eyecont & MASK_WATER ) |
|
{ |
|
SetWaterLevel( 3 ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Check if entity is in the water and applies any current to velocity |
|
// and sets appropriate water flags |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CBaseEntity::PhysicsCheckWater( void ) |
|
{ |
|
if (GetMoveParent()) |
|
return GetWaterLevel() > 1; |
|
|
|
int cont = GetWaterType(); |
|
|
|
// If we're not in water + don't have a current, we're done |
|
if ( ( cont & (MASK_WATER | MASK_CURRENT) ) != (MASK_WATER | MASK_CURRENT) ) |
|
return GetWaterLevel() > 1; |
|
|
|
// Compute current direction |
|
Vector v( 0, 0, 0 ); |
|
if ( cont & CONTENTS_CURRENT_0 ) |
|
{ |
|
v[0] += 1; |
|
} |
|
if ( cont & CONTENTS_CURRENT_90 ) |
|
{ |
|
v[1] += 1; |
|
} |
|
if ( cont & CONTENTS_CURRENT_180 ) |
|
{ |
|
v[0] -= 1; |
|
} |
|
if ( cont & CONTENTS_CURRENT_270 ) |
|
{ |
|
v[1] -= 1; |
|
} |
|
if ( cont & CONTENTS_CURRENT_UP ) |
|
{ |
|
v[2] += 1; |
|
} |
|
if ( cont & CONTENTS_CURRENT_DOWN ) |
|
{ |
|
v[2] -= 1; |
|
} |
|
|
|
// The deeper we are, the stronger the current. |
|
Vector newBaseVelocity; |
|
VectorMA (GetBaseVelocity(), 50.0*GetWaterLevel(), v, newBaseVelocity); |
|
SetBaseVelocity( newBaseVelocity ); |
|
|
|
return GetWaterLevel() > 1; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Bounds velocity |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PhysicsCheckVelocity( void ) |
|
{ |
|
Vector origin = GetAbsOrigin(); |
|
Vector vecAbsVelocity = GetAbsVelocity(); |
|
|
|
bool bReset = false; |
|
for ( int i=0 ; i<3 ; i++ ) |
|
{ |
|
if ( IS_NAN(vecAbsVelocity[i]) ) |
|
{ |
|
Msg( "Got a NaN velocity on %s\n", GetClassname() ); |
|
vecAbsVelocity[i] = 0; |
|
bReset = true; |
|
} |
|
if ( IS_NAN(origin[i]) ) |
|
{ |
|
Msg( "Got a NaN origin on %s\n", GetClassname() ); |
|
origin[i] = 0; |
|
bReset = true; |
|
} |
|
|
|
if ( vecAbsVelocity[i] > sv_maxvelocity.GetFloat() ) |
|
{ |
|
#ifdef _DEBUG |
|
DevWarning( 2, "Got a velocity too high on %s\n", GetClassname() ); |
|
#endif |
|
vecAbsVelocity[i] = sv_maxvelocity.GetFloat(); |
|
bReset = true; |
|
} |
|
else if ( vecAbsVelocity[i] < -sv_maxvelocity.GetFloat() ) |
|
{ |
|
#ifdef _DEBUG |
|
DevWarning( 2, "Got a velocity too low on %s\n", GetClassname() ); |
|
#endif |
|
vecAbsVelocity[i] = -sv_maxvelocity.GetFloat(); |
|
bReset = true; |
|
} |
|
} |
|
|
|
if (bReset) |
|
{ |
|
SetAbsOrigin( origin ); |
|
SetAbsVelocity( vecAbsVelocity ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Applies gravity to falling objects |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PhysicsAddGravityMove( Vector &move ) |
|
{ |
|
Vector vecAbsVelocity = GetAbsVelocity(); |
|
|
|
move.x = (vecAbsVelocity.x + GetBaseVelocity().x ) * gpGlobals->frametime; |
|
move.y = (vecAbsVelocity.y + GetBaseVelocity().y ) * gpGlobals->frametime; |
|
|
|
if ( GetFlags() & FL_ONGROUND ) |
|
{ |
|
move.z = GetBaseVelocity().z * gpGlobals->frametime; |
|
return; |
|
} |
|
|
|
// linear acceleration due to gravity |
|
float newZVelocity = vecAbsVelocity.z - GetActualGravity( this ) * gpGlobals->frametime; |
|
|
|
move.z = ((vecAbsVelocity.z + newZVelocity) / 2.0 + GetBaseVelocity().z ) * gpGlobals->frametime; |
|
|
|
Vector vecBaseVelocity = GetBaseVelocity(); |
|
vecBaseVelocity.z = 0.0f; |
|
SetBaseVelocity( vecBaseVelocity ); |
|
|
|
vecAbsVelocity.z = newZVelocity; |
|
SetAbsVelocity( vecAbsVelocity ); |
|
|
|
// Bound velocity |
|
PhysicsCheckVelocity(); |
|
} |
|
|
|
|
|
#define STOP_EPSILON 0.1 |
|
//----------------------------------------------------------------------------- |
|
// Purpose: Slide off of the impacting object. Returns the blocked flags (1 = floor, 2 = step / wall) |
|
// Input : in - |
|
// normal - |
|
// out - |
|
// overbounce - |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int CBaseEntity::PhysicsClipVelocity( const Vector& in, const Vector& normal, Vector& out, float overbounce ) |
|
{ |
|
float backoff; |
|
float change; |
|
float angle; |
|
int i, blocked; |
|
|
|
blocked = 0; |
|
|
|
angle = normal[ 2 ]; |
|
|
|
if ( angle > 0 ) |
|
{ |
|
blocked |= 1; // floor |
|
} |
|
if ( !angle ) |
|
{ |
|
blocked |= 2; // step |
|
} |
|
|
|
backoff = DotProduct (in, normal) * overbounce; |
|
|
|
for ( i=0 ; i<3 ; i++ ) |
|
{ |
|
change = normal[i]*backoff; |
|
out[i] = in[i] - change; |
|
if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) |
|
{ |
|
out[i] = 0; |
|
} |
|
} |
|
|
|
return blocked; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::ResolveFlyCollisionBounce( trace_t &trace, Vector &vecVelocity, float flMinTotalElasticity ) |
|
{ |
|
#ifdef HL1_DLL |
|
flMinTotalElasticity = 0.3f; |
|
#endif//HL1_DLL |
|
|
|
// Get the impact surface's elasticity. |
|
float flSurfaceElasticity; |
|
physprops->GetPhysicsProperties( trace.surface.surfaceProps, NULL, NULL, NULL, &flSurfaceElasticity ); |
|
|
|
float flTotalElasticity = GetElasticity() * flSurfaceElasticity; |
|
if ( flMinTotalElasticity > 0.9f ) |
|
{ |
|
flMinTotalElasticity = 0.9f; |
|
} |
|
flTotalElasticity = clamp( flTotalElasticity, flMinTotalElasticity, 0.9f ); |
|
|
|
// NOTE: A backoff of 2.0f is a reflection |
|
Vector vecAbsVelocity; |
|
PhysicsClipVelocity( GetAbsVelocity(), trace.plane.normal, vecAbsVelocity, 2.0f ); |
|
vecAbsVelocity *= flTotalElasticity; |
|
|
|
// Get the total velocity (player + conveyors, etc.) |
|
VectorAdd( vecAbsVelocity, GetBaseVelocity(), vecVelocity ); |
|
float flSpeedSqr = DotProduct( vecVelocity, vecVelocity ); |
|
|
|
// Stop if on ground. |
|
if ( trace.plane.normal.z > 0.7f ) // Floor |
|
{ |
|
// Verify that we have an entity. |
|
CBaseEntity *pEntity = trace.m_pEnt; |
|
Assert( pEntity ); |
|
|
|
// Are we on the ground? |
|
if ( vecVelocity.z < ( GetActualGravity( this ) * gpGlobals->frametime ) ) |
|
{ |
|
vecAbsVelocity.z = 0.0f; |
|
|
|
// Recompute speedsqr based on the new absvel |
|
VectorAdd( vecAbsVelocity, GetBaseVelocity(), vecVelocity ); |
|
flSpeedSqr = DotProduct( vecVelocity, vecVelocity ); |
|
} |
|
|
|
SetAbsVelocity( vecAbsVelocity ); |
|
|
|
if ( flSpeedSqr < ( 30 * 30 ) ) |
|
{ |
|
if ( pEntity->IsStandable() ) |
|
{ |
|
SetGroundEntity( pEntity ); |
|
} |
|
|
|
// Reset velocities. |
|
SetAbsVelocity( vec3_origin ); |
|
SetLocalAngularVelocity( vec3_angle ); |
|
} |
|
else |
|
{ |
|
Vector vecDelta = GetBaseVelocity() - vecAbsVelocity; |
|
Vector vecBaseDir = GetBaseVelocity(); |
|
VectorNormalize( vecBaseDir ); |
|
float flScale = vecDelta.Dot( vecBaseDir ); |
|
|
|
VectorScale( vecAbsVelocity, ( 1.0f - trace.fraction ) * gpGlobals->frametime, vecVelocity ); |
|
VectorMA( vecVelocity, ( 1.0f - trace.fraction ) * gpGlobals->frametime, GetBaseVelocity() * flScale, vecVelocity ); |
|
PhysicsPushEntity( vecVelocity, &trace ); |
|
} |
|
} |
|
else |
|
{ |
|
// If we get *too* slow, we'll stick without ever coming to rest because |
|
// we'll get pushed down by gravity faster than we can escape from the wall. |
|
if ( flSpeedSqr < ( 30 * 30 ) ) |
|
{ |
|
// Reset velocities. |
|
SetAbsVelocity( vec3_origin ); |
|
SetLocalAngularVelocity( vec3_angle ); |
|
} |
|
else |
|
{ |
|
SetAbsVelocity( vecAbsVelocity ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::ResolveFlyCollisionSlide( trace_t &trace, Vector &vecVelocity ) |
|
{ |
|
// Get the impact surface's friction. |
|
float flSurfaceFriction; |
|
physprops->GetPhysicsProperties( trace.surface.surfaceProps, NULL, NULL, &flSurfaceFriction, NULL ); |
|
|
|
// A backoff of 1.0 is a slide. |
|
float flBackOff = 1.0f; |
|
Vector vecAbsVelocity; |
|
PhysicsClipVelocity( GetAbsVelocity(), trace.plane.normal, vecAbsVelocity, flBackOff ); |
|
|
|
if ( trace.plane.normal.z <= 0.7 ) // Floor |
|
{ |
|
SetAbsVelocity( vecAbsVelocity ); |
|
return; |
|
} |
|
|
|
// Stop if on ground. |
|
// Get the total velocity (player + conveyors, etc.) |
|
VectorAdd( vecAbsVelocity, GetBaseVelocity(), vecVelocity ); |
|
float flSpeedSqr = DotProduct( vecVelocity, vecVelocity ); |
|
|
|
// Verify that we have an entity. |
|
CBaseEntity *pEntity = trace.m_pEnt; |
|
Assert( pEntity ); |
|
|
|
// Are we on the ground? |
|
if ( vecVelocity.z < ( GetActualGravity( this ) * gpGlobals->frametime ) ) |
|
{ |
|
vecAbsVelocity.z = 0.0f; |
|
|
|
// Recompute speedsqr based on the new absvel |
|
VectorAdd( vecAbsVelocity, GetBaseVelocity(), vecVelocity ); |
|
flSpeedSqr = DotProduct( vecVelocity, vecVelocity ); |
|
} |
|
SetAbsVelocity( vecAbsVelocity ); |
|
|
|
if ( flSpeedSqr < ( 30 * 30 ) ) |
|
{ |
|
if ( pEntity->IsStandable() ) |
|
{ |
|
SetGroundEntity( pEntity ); |
|
} |
|
|
|
// Reset velocities. |
|
SetAbsVelocity( vec3_origin ); |
|
SetLocalAngularVelocity( vec3_angle ); |
|
} |
|
else |
|
{ |
|
vecAbsVelocity += GetBaseVelocity(); |
|
vecAbsVelocity *= ( 1.0f - trace.fraction ) * gpGlobals->frametime * flSurfaceFriction; |
|
PhysicsPushEntity( vecAbsVelocity, &trace ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::ResolveFlyCollisionCustom( trace_t &trace, Vector &vecVelocity ) |
|
{ |
|
// Stop if on ground. |
|
if ( trace.plane.normal.z > 0.7 ) // Floor |
|
{ |
|
// Get the total velocity (player + conveyors, etc.) |
|
VectorAdd( GetAbsVelocity(), GetBaseVelocity(), vecVelocity ); |
|
|
|
// Verify that we have an entity. |
|
CBaseEntity *pEntity = trace.m_pEnt; |
|
Assert( pEntity ); |
|
|
|
// Are we on the ground? |
|
if ( vecVelocity.z < ( GetActualGravity( this ) * gpGlobals->frametime ) ) |
|
{ |
|
Vector vecAbsVelocity = GetAbsVelocity(); |
|
vecAbsVelocity.z = 0.0f; |
|
SetAbsVelocity( vecAbsVelocity ); |
|
} |
|
|
|
if ( pEntity->IsStandable() ) |
|
{ |
|
SetGroundEntity( pEntity ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Performs the collision resolution for fliers. |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PerformFlyCollisionResolution( trace_t &trace, Vector &move ) |
|
{ |
|
switch( GetMoveCollide() ) |
|
{ |
|
case MOVECOLLIDE_FLY_CUSTOM: |
|
{ |
|
ResolveFlyCollisionCustom( trace, move ); |
|
break; |
|
} |
|
|
|
case MOVECOLLIDE_FLY_BOUNCE: |
|
{ |
|
ResolveFlyCollisionBounce( trace, move ); |
|
break; |
|
} |
|
|
|
case MOVECOLLIDE_FLY_SLIDE: |
|
case MOVECOLLIDE_DEFAULT: |
|
// NOTE: The default fly collision state is the same as a slide (for backward capatability). |
|
{ |
|
ResolveFlyCollisionSlide( trace, move ); |
|
break; |
|
} |
|
|
|
default: |
|
{ |
|
// Invalid MOVECOLLIDE_<type> |
|
Assert( 0 ); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Checks if an object has passed into or out of water and sets water info, alters velocity, plays splash sounds, etc. |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PhysicsCheckWaterTransition( void ) |
|
{ |
|
int oldcont = GetWaterType(); |
|
UpdateWaterState(); |
|
int cont = GetWaterType(); |
|
|
|
// We can exit right out if we're a child... don't bother with this... |
|
if (GetMoveParent()) |
|
return; |
|
|
|
if ( cont & MASK_WATER ) |
|
{ |
|
if (oldcont == CONTENTS_EMPTY) |
|
{ |
|
#ifndef CLIENT_DLL |
|
Splash(); |
|
#endif // !CLIENT_DLL |
|
|
|
// just crossed into water |
|
EmitSound( "BaseEntity.EnterWater" ); |
|
|
|
if ( !IsEFlagSet( EFL_NO_WATER_VELOCITY_CHANGE ) ) |
|
{ |
|
Vector vecAbsVelocity = GetAbsVelocity(); |
|
vecAbsVelocity[2] *= 0.5; |
|
SetAbsVelocity( vecAbsVelocity ); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
if ( oldcont != CONTENTS_EMPTY ) |
|
{ |
|
// just crossed out of water |
|
EmitSound( "BaseEntity.ExitWater" ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes new angles based on the angular velocity |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::SimulateAngles( float flFrameTime ) |
|
{ |
|
// move angles |
|
QAngle angles; |
|
VectorMA ( GetLocalAngles(), flFrameTime, GetLocalAngularVelocity(), angles ); |
|
SetLocalAngles( angles ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Toss, bounce, and fly movement. When onground, do nothing. |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PhysicsToss( void ) |
|
{ |
|
trace_t trace; |
|
Vector move; |
|
|
|
PhysicsCheckWater(); |
|
|
|
// regular thinking |
|
if ( !PhysicsRunThink() ) |
|
return; |
|
|
|
// Moving upward, off the ground, or resting on a client/monster, remove FL_ONGROUND |
|
if ( GetAbsVelocity()[2] > 0 || !GetGroundEntity() || !GetGroundEntity()->IsStandable() ) |
|
{ |
|
SetGroundEntity( NULL ); |
|
} |
|
|
|
// Check to see if entity is on the ground at rest |
|
if ( GetFlags() & FL_ONGROUND ) |
|
{ |
|
if ( VectorCompare( GetAbsVelocity(), vec3_origin ) ) |
|
{ |
|
// Clear rotation if not moving (even if on a conveyor) |
|
SetLocalAngularVelocity( vec3_angle ); |
|
if ( VectorCompare( GetBaseVelocity(), vec3_origin ) ) |
|
return; |
|
} |
|
} |
|
|
|
PhysicsCheckVelocity(); |
|
|
|
// add gravity |
|
if ( GetMoveType() == MOVETYPE_FLYGRAVITY && !(GetFlags() & FL_FLY) ) |
|
{ |
|
PhysicsAddGravityMove( move ); |
|
} |
|
else |
|
{ |
|
// Base velocity is not properly accounted for since this entity will move again after the bounce without |
|
// taking it into account |
|
Vector vecAbsVelocity = GetAbsVelocity(); |
|
vecAbsVelocity += GetBaseVelocity(); |
|
VectorScale(vecAbsVelocity, gpGlobals->frametime, move); |
|
PhysicsCheckVelocity( ); |
|
} |
|
|
|
// move angles |
|
SimulateAngles( gpGlobals->frametime ); |
|
|
|
// move origin |
|
PhysicsPushEntity( move, &trace ); |
|
|
|
#if !defined( CLIENT_DLL ) |
|
if ( VPhysicsGetObject() ) |
|
{ |
|
VPhysicsGetObject()->UpdateShadow( GetAbsOrigin(), vec3_angle, true, gpGlobals->frametime ); |
|
} |
|
#endif |
|
|
|
PhysicsCheckVelocity(); |
|
|
|
if (trace.allsolid ) |
|
{ |
|
// entity is trapped in another solid |
|
// UNDONE: does this entity needs to be removed? |
|
SetAbsVelocity(vec3_origin); |
|
SetLocalAngularVelocity(vec3_angle); |
|
return; |
|
} |
|
|
|
#if !defined( CLIENT_DLL ) |
|
if (IsEdictFree()) |
|
return; |
|
#endif |
|
|
|
if (trace.fraction != 1.0f) |
|
{ |
|
PerformFlyCollisionResolution( trace, move ); |
|
} |
|
|
|
// check for in water |
|
PhysicsCheckWaterTransition(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Simulation in local space of rigid children |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PhysicsRigidChild( void ) |
|
{ |
|
VPROF("CBaseEntity::PhysicsRigidChild"); |
|
// NOTE: rigidly attached children do simulation in local space |
|
// Collision impulses will be handled either not at all, or by |
|
// forwarding the information to the highest move parent |
|
|
|
Vector vecPrevOrigin = GetAbsOrigin(); |
|
|
|
// regular thinking |
|
if ( !PhysicsRunThink() ) |
|
return; |
|
|
|
VPROF_SCOPE_BEGIN("CBaseEntity::PhysicsRigidChild-2"); |
|
|
|
#if !defined( CLIENT_DLL ) |
|
// Cause touch functions to be called |
|
PhysicsTouchTriggers( &vecPrevOrigin ); |
|
|
|
// We have to do this regardless owing to hierarchy |
|
if ( VPhysicsGetObject() ) |
|
{ |
|
int solidType = GetSolid(); |
|
bool bAxisAligned = ( solidType == SOLID_BBOX || solidType == SOLID_NONE ) ? true : false; |
|
VPhysicsGetObject()->UpdateShadow( GetAbsOrigin(), bAxisAligned ? vec3_angle : GetAbsAngles(), true, gpGlobals->frametime ); |
|
} |
|
#endif |
|
|
|
VPROF_SCOPE_END(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes the base velocity |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::UpdateBaseVelocity( void ) |
|
{ |
|
#if !defined( CLIENT_DLL ) |
|
if ( GetFlags() & FL_ONGROUND ) |
|
{ |
|
CBaseEntity *groundentity = GetGroundEntity(); |
|
if ( groundentity ) |
|
{ |
|
// On conveyor belt that's moving? |
|
if ( groundentity->GetFlags() & FL_CONVEYOR ) |
|
{ |
|
Vector vecNewBaseVelocity; |
|
groundentity->GetGroundVelocityToApply( vecNewBaseVelocity ); |
|
if ( GetFlags() & FL_BASEVELOCITY ) |
|
{ |
|
vecNewBaseVelocity += GetBaseVelocity(); |
|
} |
|
AddFlag( FL_BASEVELOCITY ); |
|
SetBaseVelocity( vecNewBaseVelocity ); |
|
} |
|
} |
|
} |
|
#endif |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Runs a frame of physics for a specific edict (and all it's children) |
|
// Input : *ent - the thinking edict |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PhysicsSimulate( void ) |
|
{ |
|
VPROF( "CBaseEntity::PhysicsSimulate" ); |
|
// NOTE: Players override PhysicsSimulate and drive through their CUserCmds at that point instead of |
|
// processng through this function call!!! They shouldn't chain to here ever. |
|
// Make sure not to simulate this guy twice per frame |
|
if (m_nSimulationTick == gpGlobals->tickcount) |
|
return; |
|
|
|
m_nSimulationTick = gpGlobals->tickcount; |
|
|
|
Assert( !IsPlayer() ); |
|
|
|
// If we've got a moveparent, we must simulate that first. |
|
CBaseEntity *pMoveParent = GetMoveParent(); |
|
|
|
if ( (GetMoveType() == MOVETYPE_NONE && !pMoveParent) || (GetMoveType() == MOVETYPE_VPHYSICS ) ) |
|
{ |
|
PhysicsNone(); |
|
return; |
|
} |
|
|
|
// If ground entity goes away, make sure FL_ONGROUND is valid |
|
if ( !GetGroundEntity() ) |
|
{ |
|
RemoveFlag( FL_ONGROUND ); |
|
} |
|
|
|
if (pMoveParent) |
|
{ |
|
VPROF( "CBaseEntity::PhysicsSimulate-MoveParent" ); |
|
pMoveParent->PhysicsSimulate(); |
|
} |
|
else |
|
{ |
|
VPROF( "CBaseEntity::PhysicsSimulate-BaseVelocity" ); |
|
|
|
UpdateBaseVelocity(); |
|
|
|
if ( ((GetFlags() & FL_BASEVELOCITY) == 0) && (GetBaseVelocity() != vec3_origin) ) |
|
{ |
|
// Apply momentum (add in half of the previous frame of velocity first) |
|
// BUGBUG: This will break with PhysicsStep() because of the timestep difference |
|
Vector vecAbsVelocity; |
|
VectorMA( GetAbsVelocity(), 1.0 + (gpGlobals->frametime*0.5), GetBaseVelocity(), vecAbsVelocity ); |
|
SetAbsVelocity( vecAbsVelocity ); |
|
SetBaseVelocity( vec3_origin ); |
|
} |
|
RemoveFlag( FL_BASEVELOCITY ); |
|
} |
|
|
|
switch( GetMoveType() ) |
|
{ |
|
case MOVETYPE_PUSH: |
|
{ |
|
VPROF( "CBaseEntity::PhysicsSimulate-MOVETYPE_PUSH" ); |
|
PhysicsPusher(); |
|
} |
|
break; |
|
|
|
|
|
case MOVETYPE_VPHYSICS: |
|
{ |
|
} |
|
break; |
|
|
|
case MOVETYPE_NONE: |
|
{ |
|
VPROF( "CBaseEntity::PhysicsSimulate-MOVETYPE_NONE" ); |
|
Assert(pMoveParent); |
|
PhysicsRigidChild(); |
|
} |
|
break; |
|
|
|
case MOVETYPE_NOCLIP: |
|
{ |
|
VPROF( "CBaseEntity::PhysicsSimulate-MOVETYPE_NOCLIP" ); |
|
PhysicsNoclip(); |
|
} |
|
break; |
|
|
|
case MOVETYPE_STEP: |
|
{ |
|
VPROF( "CBaseEntity::PhysicsSimulate-MOVETYPE_STEP" ); |
|
PhysicsStep(); |
|
} |
|
break; |
|
|
|
case MOVETYPE_FLY: |
|
case MOVETYPE_FLYGRAVITY: |
|
{ |
|
VPROF( "CBaseEntity::PhysicsSimulate-MOVETYPE_FLY" ); |
|
PhysicsToss(); |
|
} |
|
break; |
|
|
|
case MOVETYPE_CUSTOM: |
|
{ |
|
VPROF( "CBaseEntity::PhysicsSimulate-MOVETYPE_CUSTOM" ); |
|
PhysicsCustom(); |
|
} |
|
break; |
|
|
|
default: |
|
Warning( "PhysicsSimulate: %s bad movetype %d", GetClassname(), GetMoveType() ); |
|
Assert(0); |
|
break; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Runs thinking code if time. There is some play in the exact time the think |
|
// function will be called, because it is called before any movement is done |
|
// in a frame. Not used for pushmove objects, because they must be exact. |
|
// Returns false if the entity removed itself. |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CBaseEntity::PhysicsRunThink( thinkmethods_t thinkMethod ) |
|
{ |
|
if ( IsEFlagSet( EFL_NO_THINK_FUNCTION ) ) |
|
return true; |
|
|
|
bool bAlive = true; |
|
|
|
// Don't fire the base if we're avoiding it |
|
if ( thinkMethod != THINK_FIRE_ALL_BUT_BASE ) |
|
{ |
|
bAlive = PhysicsRunSpecificThink( -1, &CBaseEntity::Think ); |
|
if ( !bAlive ) |
|
return false; |
|
} |
|
|
|
// Are we just firing the base think? |
|
if ( thinkMethod == THINK_FIRE_BASE_ONLY ) |
|
return bAlive; |
|
|
|
// Fire the rest of 'em |
|
for ( int i = 0; i < m_aThinkFunctions.Count(); i++ ) |
|
{ |
|
#ifdef _DEBUG |
|
// Set the context |
|
m_iCurrentThinkContext = i; |
|
#endif |
|
|
|
bAlive = PhysicsRunSpecificThink( i, m_aThinkFunctions[i].m_pfnThink ); |
|
|
|
#ifdef _DEBUG |
|
// Clear our context |
|
m_iCurrentThinkContext = NO_THINK_CONTEXT; |
|
#endif |
|
|
|
if ( !bAlive ) |
|
return false; |
|
} |
|
|
|
return bAlive; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: For testing if all thinks are occuring at the same time |
|
//----------------------------------------------------------------------------- |
|
struct ThinkSync |
|
{ |
|
float thinktime; |
|
int thinktick; |
|
CUtlVector< EHANDLE > entities; |
|
|
|
ThinkSync() |
|
{ |
|
thinktime = 0; |
|
} |
|
|
|
ThinkSync( const ThinkSync& src ) |
|
{ |
|
thinktime = src.thinktime; |
|
thinktick = src.thinktick; |
|
int c = src.entities.Count(); |
|
for ( int i = 0; i < c; i++ ) |
|
{ |
|
entities.AddToTail( src.entities[ i ] ); |
|
} |
|
} |
|
}; |
|
|
|
#if !defined( CLIENT_DLL ) |
|
static ConVar sv_thinktimecheck( "sv_thinktimecheck", "0", 0, "Check for thinktimes all on same timestamp." ); |
|
#endif |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: For testing if all thinks are occuring at the same time |
|
//----------------------------------------------------------------------------- |
|
class CThinkSyncTester |
|
{ |
|
public: |
|
CThinkSyncTester() : |
|
m_Thinkers( 0, 0, ThinkLessFunc ) |
|
{ |
|
m_nLastFrameCount = -1; |
|
m_bShouldCheck = false; |
|
} |
|
|
|
void EntityThinking( int framecount, CBaseEntity *ent, float thinktime, int thinktick ) |
|
{ |
|
#if !defined( CLIENT_DLL ) |
|
if ( m_nLastFrameCount != framecount ) |
|
{ |
|
if ( m_bShouldCheck ) |
|
{ |
|
// Report |
|
Report(); |
|
m_Thinkers.RemoveAll(); |
|
m_nLastFrameCount = framecount; |
|
} |
|
|
|
m_bShouldCheck = sv_thinktimecheck.GetBool(); |
|
} |
|
|
|
if ( !m_bShouldCheck ) |
|
return; |
|
|
|
ThinkSync *p = FindOrAddItem( ent, thinktime ); |
|
if ( !p ) |
|
{ |
|
Assert( 0 ); |
|
} |
|
|
|
p->thinktime = thinktime; |
|
p->thinktick = thinktick; |
|
EHANDLE h; |
|
h = ent; |
|
p->entities.AddToTail( h ); |
|
#endif |
|
} |
|
|
|
private: |
|
|
|
static bool ThinkLessFunc( const ThinkSync& item1, const ThinkSync& item2 ) |
|
{ |
|
return item1.thinktime < item2.thinktime; |
|
} |
|
|
|
ThinkSync *FindOrAddItem( CBaseEntity *ent, float thinktime ) |
|
{ |
|
ThinkSync item; |
|
item.thinktime = thinktime; |
|
|
|
int idx = m_Thinkers.Find( item ); |
|
if ( idx == m_Thinkers.InvalidIndex() ) |
|
{ |
|
idx = m_Thinkers.Insert( item ); |
|
} |
|
|
|
return &m_Thinkers[ idx ]; |
|
} |
|
|
|
void Report() |
|
{ |
|
if ( m_Thinkers.Count() == 0 ) |
|
return; |
|
|
|
Msg( "-----------------\nThink report frame %i\n", gpGlobals->tickcount ); |
|
|
|
for ( int i = m_Thinkers.FirstInorder(); |
|
i != m_Thinkers.InvalidIndex(); |
|
i = m_Thinkers.NextInorder( i ) ) |
|
{ |
|
ThinkSync *p = &m_Thinkers[ i ]; |
|
Assert( p ); |
|
if ( !p ) |
|
continue; |
|
|
|
int ecount = p->entities.Count(); |
|
if ( !ecount ) |
|
{ |
|
continue; |
|
} |
|
|
|
Msg( "thinktime %f, %i entities\n", p->thinktime, ecount ); |
|
for ( int j =0; j < ecount; j++ ) |
|
{ |
|
EHANDLE h = p->entities[ j ]; |
|
int lastthinktick = 0; |
|
int nextthinktick = 0; |
|
CBaseEntity *e = h.Get(); |
|
if ( e ) |
|
{ |
|
lastthinktick = e->m_nLastThinkTick; |
|
nextthinktick = e->m_nNextThinkTick; |
|
} |
|
|
|
Msg( " %p : %30s (last %5i/next %5i)\n", h.Get(), h.Get() ? h->GetClassname() : "NULL", |
|
lastthinktick, nextthinktick ); |
|
} |
|
} |
|
} |
|
|
|
CUtlRBTree< ThinkSync > m_Thinkers; |
|
int m_nLastFrameCount; |
|
bool m_bShouldCheck; |
|
}; |
|
|
|
static CThinkSyncTester g_ThinkChecker; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CBaseEntity::PhysicsRunSpecificThink( int nContextIndex, BASEPTR thinkFunc ) |
|
{ |
|
int thinktick = GetNextThinkTick( nContextIndex ); |
|
|
|
if ( thinktick <= 0 || thinktick > gpGlobals->tickcount ) |
|
return true; |
|
|
|
float thinktime = thinktick * TICK_INTERVAL; |
|
|
|
// Don't let things stay in the past. |
|
// it is possible to start that way |
|
// by a trigger with a local time. |
|
if ( thinktime < gpGlobals->curtime ) |
|
{ |
|
thinktime = gpGlobals->curtime; |
|
} |
|
|
|
// Only do this on the game server |
|
#if !defined( CLIENT_DLL ) |
|
g_ThinkChecker.EntityThinking( gpGlobals->tickcount, this, thinktime, m_nNextThinkTick ); |
|
#endif |
|
|
|
SetNextThink( nContextIndex, TICK_NEVER_THINK ); |
|
|
|
PhysicsDispatchThink( thinkFunc ); |
|
|
|
SetLastThink( nContextIndex, gpGlobals->curtime ); |
|
|
|
// Return whether entity is still valid |
|
return ( !IsMarkedForDeletion() ); |
|
} |
|
|
|
void CBaseEntity::SetGroundEntity( CBaseEntity *ground ) |
|
{ |
|
if ( m_hGroundEntity.Get() == ground ) |
|
return; |
|
|
|
#ifdef GAME_DLL |
|
// this can happen in-between updates to the held object controller (physcannon, +USE) |
|
// so trap it here and release held objects when they become player ground |
|
if ( ground && IsPlayer() && ground->GetMoveType()== MOVETYPE_VPHYSICS ) |
|
{ |
|
CBasePlayer *pPlayer = ToBasePlayer(this); |
|
IPhysicsObject *pPhysGround = ground->VPhysicsGetObject(); |
|
if ( pPhysGround && pPlayer ) |
|
{ |
|
if ( pPhysGround->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) |
|
{ |
|
pPlayer->ForceDropOfCarriedPhysObjects( ground ); |
|
} |
|
} |
|
} |
|
#endif |
|
|
|
CBaseEntity *oldGround = m_hGroundEntity; |
|
m_hGroundEntity = ground; |
|
|
|
// Just starting to touch |
|
if ( !oldGround && ground ) |
|
{ |
|
ground->AddEntityToGroundList( this ); |
|
} |
|
// Just stopping touching |
|
else if ( oldGround && !ground ) |
|
{ |
|
PhysicsNotifyOtherOfGroundRemoval( this, oldGround ); |
|
} |
|
// Changing out to new ground entity |
|
else |
|
{ |
|
PhysicsNotifyOtherOfGroundRemoval( this, oldGround ); |
|
ground->AddEntityToGroundList( this ); |
|
} |
|
|
|
// HACK/PARANOID: This is redundant with the code above, but in case we get out of sync groundlist entries ever, |
|
// this will force the appropriate flags |
|
if ( ground ) |
|
{ |
|
AddFlag( FL_ONGROUND ); |
|
} |
|
else |
|
{ |
|
RemoveFlag( FL_ONGROUND ); |
|
} |
|
} |
|
|
|
CBaseEntity *CBaseEntity::GetGroundEntity( void ) |
|
{ |
|
return m_hGroundEntity; |
|
} |
|
|
|
void CBaseEntity::StartGroundContact( CBaseEntity *ground ) |
|
{ |
|
AddFlag( FL_ONGROUND ); |
|
// Msg( "+++ %s starting contact with ground %s\n", GetClassname(), ground->GetClassname() ); |
|
} |
|
|
|
void CBaseEntity::EndGroundContact( CBaseEntity *ground ) |
|
{ |
|
RemoveFlag( FL_ONGROUND ); |
|
// Msg( "--- %s ending contact with ground %s\n", GetClassname(), ground->GetClassname() ); |
|
} |
|
|
|
|
|
void CBaseEntity::SetGroundChangeTime( float flTime ) |
|
{ |
|
m_flGroundChangeTime = flTime; |
|
} |
|
|
|
float CBaseEntity::GetGroundChangeTime( void ) |
|
{ |
|
return m_flGroundChangeTime; |
|
} |
|
|
|
|
|
|
|
// Remove this as ground entity for all object resting on this object |
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::WakeRestingObjects() |
|
{ |
|
// Unset this as ground entity for everything resting on this object |
|
// This calls endgroundcontact for everything on the list |
|
PhysicsRemoveGroundList( this ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *ent - |
|
//----------------------------------------------------------------------------- |
|
bool CBaseEntity::HasNPCsOnIt( void ) |
|
{ |
|
groundlink_t *link; |
|
groundlink_t *root = ( groundlink_t * )GetDataObject( GROUNDLINK ); |
|
if ( root ) |
|
{ |
|
for ( link = root->nextLink; link != root; link = link->nextLink ) |
|
{ |
|
if ( link->entity && link->entity->MyNPCPointer() ) |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
}
|
|
|