source-engine-2018-hl2_src/game/shared/physics_main_shared.cpp

2239 lines
57 KiB
C++
Raw Permalink Normal View History

2020-04-23 00:56:21 +08:00
//========= 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;
}