From 39ed87570bdb2f86969d4be821c94b722dc71179 Mon Sep 17 00:00:00 2001 From: Joe Ludwig Date: Wed, 26 Jun 2013 15:22:04 -0700 Subject: First version of the SOurce SDK 2013 --- mp/src/game/shared/physics_main_shared.cpp | 2238 ++++++++++++++++++++++++++++ 1 file changed, 2238 insertions(+) create mode 100644 mp/src/game/shared/physics_main_shared.cpp (limited to 'mp/src/game/shared/physics_main_shared.cpp') diff --git a/mp/src/game/shared/physics_main_shared.cpp b/mp/src/game/shared/physics_main_shared.cpp new file mode 100644 index 00000000..99cfc311 --- /dev/null +++ b/mp/src/game/shared/physics_main_shared.cpp @@ -0,0 +1,2238 @@ +//========= 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 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<= 0 && type < NUM_DATAOBJECT_TYPES ); + m_fDataObjectTypes |= (1<= 0 && type < NUM_DATAOBJECT_TYPES ); + m_fDataObjectTypes &= ~(1<= 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(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(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 (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_ + 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; +} -- cgit v1.2.3