You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2178 lines
65 KiB
2178 lines
65 KiB
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: Physics simulation for non-havok/ipion objects |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
|
|
#include "cbase.h" |
|
#ifdef _WIN32 |
|
#include "typeinfo.h" |
|
// BUGBUG: typeinfo stomps some of the warning settings (in yvals.h) |
|
#pragma warning(disable:4244) |
|
#elif POSIX |
|
#include <typeinfo> |
|
#else |
|
#error "need typeinfo defined" |
|
#endif |
|
|
|
#include "player.h" |
|
#include "ai_basenpc.h" |
|
#include "gamerules.h" |
|
#include "vphysics_interface.h" |
|
#include "mempool.h" |
|
#include "entitylist.h" |
|
#include "engine/IEngineSound.h" |
|
#include "datacache/imdlcache.h" |
|
#include "ispatialpartition.h" |
|
#include "tier0/vprof.h" |
|
#include "movevars_shared.h" |
|
#include "hierarchy.h" |
|
#include "trains.h" |
|
#include "vphysicsupdateai.h" |
|
#include "pushentity.h" |
|
#include "igamemovement.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
extern IGameMovement *g_pGameMovement; |
|
|
|
extern ConVar think_limit; |
|
ConVar vprof_think_limit( "vprof_think_limit", "0" ); |
|
|
|
ConVar vprof_scope_entity_thinks( "vprof_scope_entity_thinks", "0" ); |
|
ConVar vprof_scope_entity_gamephys( "vprof_scope_entity_gamephys", "0" ); |
|
|
|
ConVar npc_vphysics ( "npc_vphysics","0"); |
|
//----------------------------------------------------------------------------- |
|
// helper method for trace hull as used by physics... |
|
//----------------------------------------------------------------------------- |
|
static void Physics_TraceEntity( CBaseEntity* pBaseEntity, const Vector &vecAbsStart, |
|
const Vector &vecAbsEnd, unsigned int mask, trace_t *ptr ) |
|
{ |
|
// FIXME: I really am not sure the best way of doing this |
|
// The TraceHull code below for shots will make sure the object passes |
|
// through shields which do not block that damage type. It will also |
|
// send messages to the shields that they've been hit. |
|
if (pBaseEntity->GetDamageType() != DMG_GENERIC) |
|
{ |
|
GameRules()->WeaponTraceEntity( pBaseEntity, vecAbsStart, vecAbsEnd, mask, ptr ); |
|
} |
|
else |
|
{ |
|
UTIL_TraceEntity( pBaseEntity, vecAbsStart, vecAbsEnd, mask, ptr ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Does not change the entities velocity at all |
|
// Input : push - |
|
// Output : trace_t |
|
//----------------------------------------------------------------------------- |
|
static void PhysicsCheckSweep( CBaseEntity *pEntity, const Vector& vecAbsStart, const Vector &vecAbsDelta, trace_t *pTrace ) |
|
{ |
|
unsigned int mask = pEntity->PhysicsSolidMaskForEntity(); |
|
|
|
Vector vecAbsEnd; |
|
VectorAdd( vecAbsStart, vecAbsDelta, vecAbsEnd ); |
|
|
|
// Set collision type |
|
if ( !pEntity->IsSolid() || pEntity->IsSolidFlagSet( FSOLID_VOLUME_CONTENTS) ) |
|
{ |
|
if ( pEntity->GetMoveParent() ) |
|
{ |
|
UTIL_ClearTrace( *pTrace ); |
|
return; |
|
} |
|
|
|
// don't collide with monsters |
|
mask &= ~CONTENTS_MONSTER; |
|
} |
|
|
|
Physics_TraceEntity( pEntity, vecAbsStart, vecAbsEnd, mask, pTrace ); |
|
} |
|
|
|
CPhysicsPushedEntities s_PushedEntities; |
|
|
|
CPhysicsPushedEntities *g_pPushedEntities = &s_PushedEntities; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CPhysicsPushedEntities::CPhysicsPushedEntities( void ) : m_rgPusher(8, 8), m_rgMoved(32, 32) |
|
{ |
|
m_flMoveTime = -1.0f; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Store off entity and copy original origin to temporary array |
|
//----------------------------------------------------------------------------- |
|
void CPhysicsPushedEntities::AddEntity( CBaseEntity *ent ) |
|
{ |
|
int i = m_rgMoved.AddToTail(); |
|
m_rgMoved[i].m_pEntity = ent; |
|
m_rgMoved[i].m_vecStartAbsOrigin = ent->GetAbsOrigin(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Unlink + relink the pusher list so we can actually do the push |
|
//----------------------------------------------------------------------------- |
|
void CPhysicsPushedEntities::UnlinkPusherList( int *pPusherHandles ) |
|
{ |
|
for ( int i = m_rgPusher.Count(); --i >= 0; ) |
|
{ |
|
pPusherHandles[i] = partition->HideElement( m_rgPusher[i].m_pEntity->CollisionProp()->GetPartitionHandle() ); |
|
} |
|
} |
|
|
|
void CPhysicsPushedEntities::RelinkPusherList( int *pPusherHandles ) |
|
{ |
|
for ( int i = m_rgPusher.Count(); --i >= 0; ) |
|
{ |
|
partition->UnhideElement( m_rgPusher[i].m_pEntity->CollisionProp()->GetPartitionHandle(), pPusherHandles[i] ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Compute the direction to move the rotation blocker |
|
//----------------------------------------------------------------------------- |
|
void CPhysicsPushedEntities::ComputeRotationalPushDirection( CBaseEntity *pBlocker, const RotatingPushMove_t &rotPushMove, Vector *pMove, CBaseEntity *pRoot ) |
|
{ |
|
// calculate destination position |
|
// "start" is relative to the *root* pusher, world orientation |
|
Vector start = pBlocker->CollisionProp()->GetCollisionOrigin(); |
|
if ( pRoot->GetSolid() == SOLID_VPHYSICS ) |
|
{ |
|
// HACKHACK: Use move dir to guess which corner of the box determines contact and rotate the box so |
|
// that corner remains in the same local position. |
|
// BUGBUG: This will break, but not as badly as the previous solution!!! |
|
Vector vecAbsMins, vecAbsMaxs; |
|
pBlocker->CollisionProp()->WorldSpaceAABB( &vecAbsMins, &vecAbsMaxs ); |
|
start.x = (pMove->x < 0) ? vecAbsMaxs.x : vecAbsMins.x; |
|
start.y = (pMove->y < 0) ? vecAbsMaxs.y : vecAbsMins.y; |
|
start.z = (pMove->z < 0) ? vecAbsMaxs.z : vecAbsMins.z; |
|
|
|
CBasePlayer *pPlayer = ToBasePlayer(pBlocker); |
|
if ( pPlayer ) |
|
{ |
|
// notify the player physics code so it can use vphysics to keep players from getting stuck |
|
pPlayer->SetPhysicsFlag( PFLAG_GAMEPHYSICS_ROTPUSH, true ); |
|
} |
|
} |
|
|
|
// org is pusher local coordinate of start |
|
Vector local; |
|
// transform starting point into local space |
|
VectorITransform( start, rotPushMove.startLocalToWorld, local ); |
|
// rotate local org into world space at end of rotation |
|
Vector end; |
|
VectorTransform( local, rotPushMove.endLocalToWorld, end ); |
|
|
|
// move is the difference (in world space) that the move will push this object |
|
VectorSubtract( end, start, *pMove ); |
|
} |
|
|
|
class CTraceFilterPushFinal : public CTraceFilterSimple |
|
{ |
|
DECLARE_CLASS( CTraceFilterPushFinal, CTraceFilterSimple ); |
|
|
|
public: |
|
CTraceFilterPushFinal( CBaseEntity *pEntity, int nCollisionGroup ) |
|
: CTraceFilterSimple( pEntity, nCollisionGroup ) |
|
{ |
|
|
|
} |
|
|
|
bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) |
|
{ |
|
Assert( dynamic_cast<CBaseEntity*>(pHandleEntity) ); |
|
CBaseEntity *pTestEntity = static_cast<CBaseEntity*>(pHandleEntity); |
|
|
|
// UNDONE: This should really filter to just the pushing entities |
|
if ( pTestEntity->GetMoveType() == MOVETYPE_VPHYSICS && |
|
pTestEntity->VPhysicsGetObject() && pTestEntity->VPhysicsGetObject()->IsMoveable() ) |
|
return false; |
|
|
|
return BaseClass::ShouldHitEntity( pHandleEntity, contentsMask ); |
|
} |
|
|
|
}; |
|
|
|
bool CPhysicsPushedEntities::IsPushedPositionValid( CBaseEntity *pBlocker ) |
|
{ |
|
CTraceFilterPushFinal pushFilter(pBlocker, pBlocker->GetCollisionGroup() ); |
|
|
|
trace_t trace; |
|
UTIL_TraceEntity( pBlocker, pBlocker->GetAbsOrigin(), pBlocker->GetAbsOrigin(), pBlocker->PhysicsSolidMaskForEntity(), &pushFilter, &trace ); |
|
|
|
return !trace.startsolid; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Speculatively checks to see if all entities in this list can be pushed |
|
//----------------------------------------------------------------------------- |
|
bool CPhysicsPushedEntities::SpeculativelyCheckPush( PhysicsPushedInfo_t &info, const Vector &vecAbsPush, bool bRotationalPush, CBaseEntity *pRoot ) |
|
{ |
|
CBaseEntity *pBlocker = info.m_pEntity; |
|
|
|
// See if it's possible to move the entity, but disable all pushers in the hierarchy first |
|
int *pPusherHandles = (int*)stackalloc( m_rgPusher.Count() * sizeof(int) ); |
|
UnlinkPusherList( pPusherHandles ); |
|
CTraceFilterPushMove pushFilter(pBlocker, pBlocker->GetCollisionGroup() ); |
|
|
|
Vector pushDestPosition = pBlocker->GetAbsOrigin() + vecAbsPush; |
|
UTIL_TraceEntity( pBlocker, pBlocker->GetAbsOrigin(), pushDestPosition, |
|
pBlocker->PhysicsSolidMaskForEntity(), &pushFilter, &info.m_Trace ); |
|
|
|
RelinkPusherList(pPusherHandles); |
|
info.m_bPusherIsGround = false; |
|
if ( pBlocker->GetGroundEntity() && pBlocker->GetGroundEntity()->GetRootMoveParent() == m_rgPusher[0].m_pEntity ) |
|
{ |
|
info.m_bPusherIsGround = true; |
|
} |
|
|
|
Vector blockerOrigin = pBlocker->GetAbsOrigin(); |
|
|
|
bool bIsUnblockable = (m_bIsUnblockableByPlayer && (pBlocker->IsPlayer() || pBlocker->MyNPCPointer())) ? true : false; |
|
if ( bIsUnblockable ) |
|
{ |
|
pBlocker->SetAbsOrigin( pushDestPosition ); |
|
} |
|
else |
|
{ |
|
if( pRoot && !pRoot->CanPushEntity(pBlocker) ) |
|
{ |
|
// Without this bit, this function assumes all pushes are valid, and it is only checking |
|
// to see if the pushed guy would be okay in the new location. The first check should |
|
// be if we are allowed to push them at all. (But this can't be first because other code |
|
// is also assuming that the only failure could be from a second entity, so we need |
|
// to have gone through the trace or info.trace.pEnt will crash for them. |
|
info.m_bBlocked = true; |
|
return false; |
|
} |
|
|
|
// Move the blocker into its new position |
|
if ( info.m_Trace.fraction ) |
|
{ |
|
pBlocker->SetAbsOrigin( info.m_Trace.endpos ); |
|
} |
|
|
|
// We're not blocked if the blocker is point-sized or non-solid |
|
if ( pBlocker->IsPointSized() || !pBlocker->IsSolid() || |
|
pBlocker->IsSolidFlagSet( FSOLID_VOLUME_CONTENTS ) ) |
|
{ |
|
return true; |
|
} |
|
|
|
if ( (!bRotationalPush) && (info.m_Trace.fraction == 1.0) ) |
|
{ |
|
//Assert( pBlocker->PhysicsTestEntityPosition() == false ); |
|
if ( !IsPushedPositionValid(pBlocker) ) |
|
{ |
|
Warning("Interpenetrating entities! (%s and %s)\n", |
|
pBlocker->GetClassname(), m_rgPusher[0].m_pEntity->GetClassname() ); |
|
} |
|
|
|
return true; |
|
} |
|
} |
|
|
|
// Check to see if we're still blocked by the pushers |
|
// FIXME: If the trace fraction == 0 can we early out also? |
|
info.m_bBlocked = !IsPushedPositionValid(pBlocker); |
|
|
|
if ( !info.m_bBlocked ) |
|
return true; |
|
|
|
// if the player is blocking the train try nudging him around to fix accumulated error |
|
if ( bIsUnblockable ) |
|
{ |
|
Vector org = pBlocker->GetAbsOrigin(); |
|
for ( int checkCount = 0; checkCount < 4; checkCount++ ) |
|
{ |
|
Vector move; |
|
MatrixGetColumn( m_rgPusher[0].m_pEntity->EntityToWorldTransform(), checkCount>>1, move ); |
|
|
|
// alternate movements 1/2" in each direction |
|
float factor = ( checkCount & 1 ) ? -0.5f : 0.5f; |
|
pBlocker->SetAbsOrigin( org + move * factor ); |
|
info.m_bBlocked = !IsPushedPositionValid(pBlocker); |
|
if ( !info.m_bBlocked ) |
|
{ |
|
DevMsg(1, "Fixing player blocking train!\n"); |
|
return true; |
|
} |
|
} |
|
pBlocker->SetAbsOrigin( pushDestPosition ); |
|
//DevMsg(1, "Ignoring player blocking train!\n"); |
|
return true; |
|
} |
|
else |
|
{ |
|
// If a player is blocking us, try nudging him around to fix accumulated errors |
|
Vector org = pBlocker->GetAbsOrigin(); |
|
CBaseEntity *ground = pBlocker->GetGroundEntity(); |
|
if ( ground && !ground->IsWorld() ) |
|
{ |
|
Vector toCenter = ground->GetAbsOrigin() - org; |
|
toCenter.z = 0; |
|
if ( !toCenter.IsZero() ) |
|
{ |
|
toCenter.NormalizeInPlace(); |
|
pBlocker->SetAbsOrigin( org + toCenter * 16.0f ); |
|
info.m_bBlocked = !IsPushedPositionValid(pBlocker); |
|
if ( !info.m_bBlocked ) |
|
{ |
|
DevMsg(1, "Fixing player blocking train by moving to center!\n"); |
|
return true; |
|
} |
|
} |
|
} |
|
|
|
if ( pBlocker->IsPlayer() ) |
|
{ |
|
pBlocker->SetAbsOrigin( blockerOrigin ); |
|
g_pGameMovement->UnblockPusher( ToBasePlayer( pBlocker ), m_rgPusher[0].m_pEntity ); // this checks validity |
|
info.m_bBlocked = ( pBlocker->GetAbsOrigin() == blockerOrigin ) || !IsPushedPositionValid(pBlocker); |
|
if ( !info.m_bBlocked ) |
|
{ |
|
DevMsg(1, "Fixing player blocking train via gamemovement!\n"); |
|
return true; |
|
} |
|
else |
|
{ |
|
DevMsg(2, "Blocked by player on train!\n" ); |
|
return false; |
|
} |
|
} |
|
else |
|
{ |
|
for ( int checkCount = 0; checkCount < 4; checkCount++ ) |
|
{ |
|
Vector move; |
|
MatrixGetColumn( m_rgPusher[0].m_pEntity->EntityToWorldTransform(), checkCount>>1, move ); |
|
|
|
// alternate movements 1/2" in each direction |
|
float factor = ( checkCount & 1 ) ? -0.5f : 0.5f; |
|
pBlocker->SetAbsOrigin( org + move * factor ); |
|
info.m_bBlocked = !IsPushedPositionValid(pBlocker); |
|
if ( !info.m_bBlocked ) |
|
{ |
|
DevMsg(1, "Fixing player blocking train!\n"); |
|
return true; |
|
} |
|
} |
|
} |
|
|
|
pBlocker->SetAbsOrigin( org ); // restore origin... |
|
|
|
DevMsg(2, "Blocked by player on train!\n"); |
|
return false; |
|
} |
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Speculatively checks to see if all entities in this list can be pushed |
|
//----------------------------------------------------------------------------- |
|
bool CPhysicsPushedEntities::SpeculativelyCheckRotPush( const RotatingPushMove_t &rotPushMove, CBaseEntity *pRoot ) |
|
{ |
|
Vector vecAbsPush; |
|
m_nBlocker = -1; |
|
for (int i = m_rgMoved.Count(); --i >= 0; ) |
|
{ |
|
ComputeRotationalPushDirection( m_rgMoved[i].m_pEntity, rotPushMove, &vecAbsPush, pRoot ); |
|
if (!SpeculativelyCheckPush( m_rgMoved[i], vecAbsPush, true, pRoot )) |
|
{ |
|
m_nBlocker = i; |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Speculatively checks to see if all entities in this list can be pushed |
|
//----------------------------------------------------------------------------- |
|
bool CPhysicsPushedEntities::SpeculativelyCheckLinearPush( const Vector &vecAbsPush ) |
|
{ |
|
m_nBlocker = -1; |
|
for (int i = m_rgMoved.Count(); --i >= 0; ) |
|
{ |
|
if (!SpeculativelyCheckPush( m_rgMoved[i], vecAbsPush, false, NULL )) |
|
{ |
|
m_nBlocker = i; |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Causes all entities in the list to touch triggers from their prev position |
|
//----------------------------------------------------------------------------- |
|
void CPhysicsPushedEntities::FinishPushers() |
|
{ |
|
// We succeeded! Now that we know the final location of all entities, |
|
// touch triggers + update physics objects + do other fixup |
|
for ( int i = m_rgPusher.Count(); --i >= 0; ) |
|
{ |
|
PhysicsPusherInfo_t &info = m_rgPusher[i]; |
|
|
|
// Cause touch functions to be called |
|
// FIXME: Need to make moved entities not touch triggers until we know we're ok |
|
// FIXME: it'd be better for the engine to just have a touch method |
|
info.m_pEntity->PhysicsTouchTriggers( &info.m_vecStartAbsOrigin ); |
|
|
|
info.m_pEntity->UpdatePhysicsShadowToCurrentPosition( gpGlobals->frametime ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Causes all entities in the list to touch triggers from their prev position |
|
//----------------------------------------------------------------------------- |
|
void CPhysicsPushedEntities::FinishRotPushedEntity( CBaseEntity *pPushedEntity, const RotatingPushMove_t &rotPushMove ) |
|
{ |
|
// Impart angular velocity of push onto pushed objects |
|
if ( pPushedEntity->IsPlayer() ) |
|
{ |
|
QAngle angVel = pPushedEntity->GetLocalAngularVelocity(); |
|
angVel[1] = rotPushMove.amove[1]; |
|
pPushedEntity->SetLocalAngularVelocity(angVel); |
|
|
|
// Look up associated client |
|
CBasePlayer *player = ( CBasePlayer * )pPushedEntity; |
|
if ( player->IsNetClient() ) // don't fixup angles on bots - they don't ever reset anglechange |
|
{ |
|
player->pl.fixangle = FIXANGLE_RELATIVE; |
|
// Because we can run multiple ticks per server frame, accumulate a total offset here instead of straight |
|
// setting it. The engine will reset anglechange to 0 when the message is actually sent to the client |
|
player->pl.anglechange += rotPushMove.amove; |
|
} |
|
} |
|
else |
|
{ |
|
QAngle angles = pPushedEntity->GetAbsAngles(); |
|
|
|
// only rotate YAW with pushing. Freely rotateable entities should either use VPHYSICS |
|
// or be set up as children |
|
angles.y += rotPushMove.amove.y; |
|
pPushedEntity->SetAbsAngles( angles ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Causes all entities in the list to touch triggers from their prev position |
|
//----------------------------------------------------------------------------- |
|
void CPhysicsPushedEntities::FinishPush( bool bIsRotPush, const RotatingPushMove_t *pRotPushMove ) |
|
{ |
|
FinishPushers(); |
|
|
|
for ( int i = m_rgMoved.Count(); --i >= 0; ) |
|
{ |
|
PhysicsPushedInfo_t &info = m_rgMoved[i]; |
|
CBaseEntity *pPushedEntity = info.m_pEntity; |
|
|
|
// Cause touch functions to be called |
|
// FIXME: it'd be better for the engine to just have a touch method |
|
info.m_pEntity->PhysicsTouchTriggers( &info.m_vecStartAbsOrigin ); |
|
info.m_pEntity->UpdatePhysicsShadowToCurrentPosition( gpGlobals->frametime ); |
|
CAI_BaseNPC *pNPC = info.m_pEntity->MyNPCPointer(); |
|
if ( info.m_bPusherIsGround && pNPC ) |
|
{ |
|
pNPC->NotifyPushMove(); |
|
} |
|
|
|
|
|
// Register physics impacts... |
|
if (info.m_Trace.m_pEnt) |
|
{ |
|
pPushedEntity->PhysicsImpact( info.m_Trace.m_pEnt, info.m_Trace ); |
|
} |
|
|
|
if (bIsRotPush) |
|
{ |
|
FinishRotPushedEntity( pPushedEntity, *pRotPushMove ); |
|
} |
|
} |
|
} |
|
|
|
// save initial state when beginning a push sequence |
|
void CPhysicsPushedEntities::BeginPush( CBaseEntity *pRoot ) |
|
{ |
|
m_rgMoved.RemoveAll(); |
|
m_rgPusher.RemoveAll(); |
|
|
|
m_rootPusherStartLocalOrigin = pRoot->GetLocalOrigin(); |
|
m_rootPusherStartLocalAngles = pRoot->GetLocalAngles(); |
|
m_rootPusherStartLocaltime = pRoot->GetLocalTime(); |
|
} |
|
|
|
// store off a list of what has changed - so vphysicsUpdate can undo this if the object gets blocked |
|
void CPhysicsPushedEntities::StoreMovedEntities( physicspushlist_t &list ) |
|
{ |
|
list.localMoveTime = m_rootPusherStartLocaltime; |
|
list.localOrigin = m_rootPusherStartLocalOrigin; |
|
list.localAngles = m_rootPusherStartLocalAngles; |
|
list.pushedCount = CountMovedEntities(); |
|
Assert(list.pushedCount < ARRAYSIZE(list.pushedEnts)); |
|
if ( list.pushedCount > ARRAYSIZE(list.pushedEnts) ) |
|
{ |
|
list.pushedCount = ARRAYSIZE(list.pushedEnts); |
|
} |
|
for ( int i = 0; i < list.pushedCount; i++ ) |
|
{ |
|
list.pushedEnts[i] = m_rgMoved[i].m_pEntity; |
|
list.pushVec[i] = m_rgMoved[i].m_pEntity->GetAbsOrigin() - m_rgMoved[i].m_vecStartAbsOrigin; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Registers a blockage |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CPhysicsPushedEntities::RegisterBlockage() |
|
{ |
|
Assert( m_nBlocker >= 0 ); |
|
|
|
// Generate a PhysicsImpact against the blocker... |
|
PhysicsPushedInfo_t &info = m_rgMoved[m_nBlocker]; |
|
if ( info.m_Trace.m_pEnt ) |
|
{ |
|
info.m_pEntity->PhysicsImpact( info.m_Trace.m_pEnt, info.m_Trace ); |
|
} |
|
|
|
// This is the dude |
|
return info.m_pEntity; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Restore entities that might have been moved |
|
// Input : fromrotation - if the move is from a rotation, then angular move must also be reverted |
|
// *amove - |
|
//----------------------------------------------------------------------------- |
|
void CPhysicsPushedEntities::RestoreEntities( ) |
|
{ |
|
// Reset all of the pushed entities to get them back into place also |
|
for ( int i = m_rgMoved.Count(); --i >= 0; ) |
|
{ |
|
m_rgMoved[ i ].m_pEntity->SetAbsOrigin( m_rgMoved[ i ].m_vecStartAbsOrigin ); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: This is a trace filter that only hits an exclusive list of entities |
|
//----------------------------------------------------------------------------- |
|
class CTraceFilterAgainstEntityList : public ITraceFilter |
|
{ |
|
public: |
|
virtual bool ShouldHitEntity( IHandleEntity *pEntity, int contentsMask ) |
|
{ |
|
for ( int i = m_entityList.Count()-1; i >= 0; --i ) |
|
{ |
|
if ( m_entityList[i] == pEntity ) |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
virtual TraceType_t GetTraceType() const |
|
{ |
|
return TRACE_ENTITIES_ONLY; |
|
} |
|
|
|
void AddEntityToHit( IHandleEntity *pEntity ) |
|
{ |
|
m_entityList.AddToTail(pEntity); |
|
} |
|
|
|
CUtlVector<IHandleEntity *> m_entityList; |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Generates a list of potential blocking entities |
|
//----------------------------------------------------------------------------- |
|
class CPushBlockerEnum : public IPartitionEnumerator |
|
{ |
|
public: |
|
CPushBlockerEnum( CPhysicsPushedEntities *pPushedEntities ) : m_pPushedEntities(pPushedEntities) |
|
{ |
|
// All elements are part of the same hierarchy, so they all have |
|
// the same root, so it doesn't matter which one we grab |
|
m_pRootHighestParent = m_pPushedEntities->m_rgPusher[0].m_pEntity->GetRootMoveParent(); |
|
++s_nEnumCount; |
|
|
|
m_collisionGroupCount = 0; |
|
for ( int i = m_pPushedEntities->m_rgPusher.Count(); --i >= 0; ) |
|
{ |
|
if ( !m_pPushedEntities->m_rgPusher[i].m_pEntity->IsSolid() ) |
|
continue; |
|
|
|
m_pushersOnly.AddEntityToHit( m_pPushedEntities->m_rgPusher[i].m_pEntity ); |
|
int collisionGroup = m_pPushedEntities->m_rgPusher[i].m_pEntity->GetCollisionGroup(); |
|
AddCollisionGroup(collisionGroup); |
|
} |
|
|
|
} |
|
|
|
virtual IterationRetval_t EnumElement( IHandleEntity *pHandleEntity ) |
|
{ |
|
CBaseEntity *pCheck = GetPushableEntity( pHandleEntity ); |
|
if ( !pCheck ) |
|
return ITERATION_CONTINUE; |
|
|
|
// Mark it as seen |
|
pCheck->m_nPushEnumCount = s_nEnumCount; |
|
m_pPushedEntities->AddEntity( pCheck ); |
|
|
|
return ITERATION_CONTINUE; |
|
} |
|
|
|
private: |
|
|
|
inline void AddCollisionGroup(int collisionGroup) |
|
{ |
|
for ( int i = 0; i < m_collisionGroupCount; i++ ) |
|
{ |
|
if ( m_collisionGroups[i] == collisionGroup ) |
|
return; |
|
} |
|
if ( m_collisionGroupCount < ARRAYSIZE(m_collisionGroups) ) |
|
{ |
|
m_collisionGroups[m_collisionGroupCount] = collisionGroup; |
|
m_collisionGroupCount++; |
|
} |
|
} |
|
|
|
bool IsStandingOnPusher( CBaseEntity *pCheck ) |
|
{ |
|
CBaseEntity *pGroundEnt = pCheck->GetGroundEntity(); |
|
if ( pCheck->GetFlags() & FL_ONGROUND || pGroundEnt ) |
|
{ |
|
for ( int i = m_pPushedEntities->m_rgPusher.Count(); --i >= 0; ) |
|
{ |
|
if (m_pPushedEntities->m_rgPusher[i].m_pEntity == pGroundEnt) |
|
{ |
|
return true; |
|
} |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
bool IntersectsPushers( CBaseEntity *pTest ) |
|
{ |
|
trace_t tr; |
|
|
|
ICollideable *pCollision = pTest->GetCollideable(); |
|
enginetrace->SweepCollideable( pCollision, pTest->GetAbsOrigin(), pTest->GetAbsOrigin(), pCollision->GetCollisionAngles(), |
|
pTest->PhysicsSolidMaskForEntity(), &m_pushersOnly, &tr ); |
|
|
|
return tr.startsolid; |
|
} |
|
|
|
CBaseEntity *GetPushableEntity( IHandleEntity *pHandleEntity ) |
|
{ |
|
CBaseEntity *pCheck = gEntList.GetBaseEntity( pHandleEntity->GetRefEHandle() ); |
|
if ( !pCheck ) |
|
return NULL; |
|
|
|
// Don't bother if we've already seen this one... |
|
if (pCheck->m_nPushEnumCount == s_nEnumCount) |
|
return NULL; |
|
|
|
if ( !pCheck->IsSolid() ) |
|
return NULL; |
|
|
|
if ( pCheck->GetMoveType() == MOVETYPE_PUSH || |
|
pCheck->GetMoveType() == MOVETYPE_NONE || |
|
pCheck->GetMoveType() == MOVETYPE_VPHYSICS || |
|
pCheck->GetMoveType() == MOVETYPE_NOCLIP ) |
|
{ |
|
return NULL; |
|
} |
|
|
|
bool bCollide = false; |
|
for ( int i = 0; i < m_collisionGroupCount; i++ ) |
|
{ |
|
if ( g_pGameRules->ShouldCollide( pCheck->GetCollisionGroup(), m_collisionGroups[i] ) ) |
|
{ |
|
bCollide = true; |
|
break; |
|
} |
|
} |
|
if ( !bCollide ) |
|
return NULL; |
|
// We're not pushing stuff we're hierarchically attached to |
|
CBaseEntity *pCheckHighestParent = pCheck->GetRootMoveParent(); |
|
if (pCheckHighestParent == m_pRootHighestParent) |
|
return NULL; |
|
|
|
// If we're standing on the pusher or any rigidly attached child |
|
// of the pusher, we don't need to bother checking for interpenetration |
|
if ( !IsStandingOnPusher(pCheck) ) |
|
{ |
|
// Our surrounding boxes are touching. But we may well not be colliding.... |
|
// see if the ent's bbox is inside the pusher's final position |
|
if ( !IntersectsPushers( pCheck ) ) |
|
return NULL; |
|
} |
|
|
|
// NOTE: This is pretty tricky here. If a rigidly attached child comes into |
|
// contact with a pusher, we *cannot* push the child. Instead, we must push |
|
// the highest parent of that child. |
|
return pCheckHighestParent; |
|
} |
|
|
|
private: |
|
static int s_nEnumCount; |
|
CPhysicsPushedEntities *m_pPushedEntities; |
|
CBaseEntity *m_pRootHighestParent; |
|
CTraceFilterAgainstEntityList m_pushersOnly; |
|
int m_collisionGroups[8]; |
|
int m_collisionGroupCount; |
|
}; |
|
|
|
int CPushBlockerEnum::s_nEnumCount = 0; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Generates a list of potential blocking entities |
|
//----------------------------------------------------------------------------- |
|
void CPhysicsPushedEntities::GenerateBlockingEntityList() |
|
{ |
|
VPROF("CPhysicsPushedEntities::GenerateBlockingEntityList"); |
|
|
|
m_rgMoved.RemoveAll(); |
|
CPushBlockerEnum blockerEnum( this ); |
|
|
|
for ( int i = m_rgPusher.Count(); --i >= 0; ) |
|
{ |
|
CBaseEntity *pPusher = m_rgPusher[i].m_pEntity; |
|
|
|
// Don't bother if the pusher isn't solid |
|
if ( !pPusher->IsSolid() || pPusher->IsSolidFlagSet( FSOLID_VOLUME_CONTENTS ) ) |
|
{ |
|
continue; |
|
} |
|
|
|
Vector vecAbsMins, vecAbsMaxs; |
|
pPusher->CollisionProp()->WorldSpaceAABB( &vecAbsMins, &vecAbsMaxs ); |
|
partition->EnumerateElementsInBox( PARTITION_ENGINE_NON_STATIC_EDICTS, vecAbsMins, vecAbsMaxs, false, &blockerEnum ); |
|
|
|
//Go back throught the generated list. |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Generates a list of potential blocking entities |
|
//----------------------------------------------------------------------------- |
|
void CPhysicsPushedEntities::GenerateBlockingEntityListAddBox( const Vector &vecMoved ) |
|
{ |
|
VPROF("CPhysicsPushedEntities::GenerateBlockingEntityListAddBox"); |
|
|
|
m_rgMoved.RemoveAll(); |
|
CPushBlockerEnum blockerEnum( this ); |
|
|
|
for ( int i = m_rgPusher.Count(); --i >= 0; ) |
|
{ |
|
CBaseEntity *pPusher = m_rgPusher[i].m_pEntity; |
|
|
|
// Don't bother if the pusher isn't solid |
|
if ( !pPusher->IsSolid() || pPusher->IsSolidFlagSet( FSOLID_VOLUME_CONTENTS ) ) |
|
{ |
|
continue; |
|
} |
|
|
|
Vector vecAbsMins, vecAbsMaxs; |
|
pPusher->CollisionProp()->WorldSpaceAABB( &vecAbsMins, &vecAbsMaxs ); |
|
for ( int iAxis = 0; iAxis < 3; ++iAxis ) |
|
{ |
|
if ( vecMoved[iAxis] >= 0.0f ) |
|
{ |
|
vecAbsMins[iAxis] -= vecMoved[iAxis]; |
|
} |
|
else |
|
{ |
|
vecAbsMaxs[iAxis] -= vecMoved[iAxis]; |
|
} |
|
} |
|
|
|
partition->EnumerateElementsInBox( PARTITION_ENGINE_NON_STATIC_EDICTS, vecAbsMins, vecAbsMaxs, false, &blockerEnum ); |
|
|
|
//Go back throught the generated list. |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Gets a list of all entities hierarchically attached to the root |
|
//----------------------------------------------------------------------------- |
|
void CPhysicsPushedEntities::SetupAllInHierarchy( CBaseEntity *pParent ) |
|
{ |
|
if (!pParent) |
|
return; |
|
|
|
VPROF("CPhysicsPushedEntities::SetupAllInHierarchy"); |
|
|
|
// Make sure to snack the position +before+ relink because applying the |
|
// rotation (which occurs in relink) will put it at the final location |
|
// NOTE: The root object at this point is actually at its final position. |
|
// We'll fix that up later |
|
int i = m_rgPusher.AddToTail(); |
|
m_rgPusher[i].m_pEntity = pParent; |
|
m_rgPusher[i].m_vecStartAbsOrigin = pParent->GetAbsOrigin(); |
|
|
|
CBaseEntity *pChild; |
|
for ( pChild = pParent->FirstMoveChild(); pChild != NULL; pChild = pChild->NextMovePeer() ) |
|
{ |
|
SetupAllInHierarchy( pChild ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Rotates the root entity, fills in the pushmove structure |
|
//----------------------------------------------------------------------------- |
|
void CPhysicsPushedEntities::RotateRootEntity( CBaseEntity *pRoot, float movetime, RotatingPushMove_t &rotation ) |
|
{ |
|
VPROF("CPhysicsPushedEntities::RotateRootEntity"); |
|
|
|
rotation.amove = pRoot->GetLocalAngularVelocity() * movetime; |
|
rotation.origin = pRoot->GetAbsOrigin(); |
|
|
|
// Knowing the initial + ending basis is needed for determining |
|
// which corner we're pushing |
|
MatrixCopy( pRoot->EntityToWorldTransform(), rotation.startLocalToWorld ); |
|
|
|
// rotate the pusher to it's final position |
|
QAngle angles = pRoot->GetLocalAngles(); |
|
angles += pRoot->GetLocalAngularVelocity() * movetime; |
|
pRoot->SetLocalAngles( angles ); |
|
|
|
// Compute the change in absangles |
|
MatrixCopy( pRoot->EntityToWorldTransform(), rotation.endLocalToWorld ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Tries to rotate an entity hierarchy, returns the blocker if any |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CPhysicsPushedEntities::PerformRotatePush( CBaseEntity *pRoot, float movetime ) |
|
{ |
|
VPROF("CPhysicsPushedEntities::PerformRotatePush"); |
|
|
|
m_bIsUnblockableByPlayer = (pRoot->GetFlags() & FL_UNBLOCKABLE_BY_PLAYER) ? true : false; |
|
// Build a list of this entity + all its children because we're going to try to move them all |
|
// This will also make sure each entity is linked in the appropriate place |
|
// with correct absboxes |
|
m_rgPusher.RemoveAll(); |
|
SetupAllInHierarchy( pRoot ); |
|
|
|
// save where we rotated from, in case we're blocked |
|
QAngle angPrevAngles = pRoot->GetLocalAngles(); |
|
|
|
// Apply the rotation |
|
RotatingPushMove_t rotPushMove; |
|
RotateRootEntity( pRoot, movetime, rotPushMove ); |
|
|
|
// Next generate a list of all entities that could potentially be intersecting with |
|
// any of the children in their new locations... |
|
GenerateBlockingEntityList( ); |
|
|
|
// Now we have a unique list of things that could potentially block our push |
|
// and need to be pushed out of the way. Lets try to push them all out of the way. |
|
// If we fail, undo it all |
|
if (!SpeculativelyCheckRotPush( rotPushMove, pRoot )) |
|
{ |
|
CBaseEntity *pBlocker = RegisterBlockage(); |
|
pRoot->SetLocalAngles( angPrevAngles ); |
|
RestoreEntities( ); |
|
return pBlocker; |
|
} |
|
|
|
FinishPush( true, &rotPushMove ); |
|
return NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Linearly moves the root entity |
|
//----------------------------------------------------------------------------- |
|
void CPhysicsPushedEntities::LinearlyMoveRootEntity( CBaseEntity *pRoot, float movetime, Vector *pAbsPushVector ) |
|
{ |
|
VPROF("CPhysicsPushedEntities::LinearlyMoveRootEntity"); |
|
|
|
// move the pusher to it's final position |
|
Vector move = pRoot->GetLocalVelocity() * movetime; |
|
Vector origin = pRoot->GetLocalOrigin(); |
|
origin += move; |
|
pRoot->SetLocalOrigin( origin ); |
|
|
|
// Store off the abs push vector |
|
*pAbsPushVector = pRoot->GetAbsVelocity() * movetime; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Tries to linearly push an entity hierarchy, returns the blocker if any |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CPhysicsPushedEntities::PerformLinearPush( CBaseEntity *pRoot, float movetime ) |
|
{ |
|
VPROF("CPhysicsPushedEntities::PerformLinearPush"); |
|
|
|
m_flMoveTime = movetime; |
|
|
|
m_bIsUnblockableByPlayer = (pRoot->GetFlags() & FL_UNBLOCKABLE_BY_PLAYER) ? true : false; |
|
// Build a list of this entity + all its children because we're going to try to move them all |
|
// This will also make sure each entity is linked in the appropriate place |
|
// with correct absboxes |
|
m_rgPusher.RemoveAll(); |
|
SetupAllInHierarchy( pRoot ); |
|
|
|
// save where we started from, in case we're blocked |
|
Vector vecPrevOrigin = pRoot->GetLocalOrigin(); |
|
|
|
// Move the root (and all children) into its new position |
|
Vector vecAbsPush; |
|
LinearlyMoveRootEntity( pRoot, movetime, &vecAbsPush ); |
|
|
|
// Next generate a list of all entities that could potentially be intersecting with |
|
// any of the children in their new locations... |
|
GenerateBlockingEntityListAddBox( vecAbsPush ); |
|
|
|
// Now we have a unique list of things that could potentially block our push |
|
// and need to be pushed out of the way. Lets try to push them all out of the way. |
|
// If we fail, undo it all |
|
if (!SpeculativelyCheckLinearPush( vecAbsPush )) |
|
{ |
|
CBaseEntity *pBlocker = RegisterBlockage(); |
|
pRoot->SetLocalOrigin( vecPrevOrigin ); |
|
RestoreEntities(); |
|
return pBlocker; |
|
} |
|
|
|
FinishPush( ); |
|
return NULL; |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
// CBaseEntity methods |
|
// |
|
//----------------------------------------------------------------------------- |
|
|
|
// create a macro that is true if we are allowed to debug traces during thinks, and compiles out to nothing otherwise. |
|
#define THINK_TRACE_COUNTER_COMPILE_FUNCTIONS_SERVER |
|
#include "engine/thinktracecounter.h" |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Called when it's time for a physically moved objects (plats, doors, etc) |
|
// to run it's game code. |
|
// All other entity thinking is done during worldspawn's think |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PhysicsDispatchThink( BASEPTR thinkFunc ) |
|
{ |
|
VPROF_ENTER_SCOPE( ( !vprof_scope_entity_thinks.GetBool() ) ? |
|
"CBaseEntity::PhysicsDispatchThink" : |
|
EntityFactoryDictionary()->GetCannonicalName( GetClassname() ) ); |
|
|
|
float thinkLimit = think_limit.GetFloat(); |
|
|
|
float startTime = 0.0; |
|
|
|
if ( IsDormant() ) |
|
{ |
|
Warning( "Dormant entity %s (%s) is thinking!!\n", GetClassname(), GetDebugName() ); |
|
Assert(0); |
|
} |
|
|
|
if ( thinkLimit ) |
|
{ |
|
startTime = Plat_FloatTime(); |
|
} |
|
|
|
if ( thinkFunc ) |
|
{ |
|
#ifdef THINK_TRACE_COUNTER_COMPILED |
|
static ConVarRef think_trace_limit( "think_trace_limit" ); |
|
const int tracelimit = abs(think_trace_limit.GetInt()); |
|
const bool bThinkTraceAllowed = DEBUG_THINK_TRACE_COUNTER_ALLOWED(); |
|
if ( bThinkTraceAllowed ) |
|
{ |
|
enginetrace->GetSetDebugTraceCounter( tracelimit, kTRACE_COUNTER_SET ); |
|
} |
|
|
|
(this->*thinkFunc)(); |
|
|
|
if ( bThinkTraceAllowed ) |
|
{ |
|
enginetrace->GetSetDebugTraceCounter( 0, kTRACE_COUNTER_SET ); |
|
} |
|
#else |
|
(this->*thinkFunc)(); |
|
#endif |
|
} |
|
|
|
if ( thinkLimit ) |
|
{ |
|
// calculate running time of the AI in milliseconds |
|
float time = ( Plat_FloatTime() - startTime ) * 1000.0f; |
|
if ( time > thinkLimit ) |
|
{ |
|
#ifdef VPROF_ENABLED |
|
if ( vprof_think_limit.GetBool() ) |
|
{ |
|
g_VProfSignalSpike = true; |
|
} |
|
#endif |
|
// If its an NPC print out the shedule/task that took so long |
|
CAI_BaseNPC *pNPC = MyNPCPointer(); |
|
if (pNPC && pNPC->GetCurSchedule()) |
|
{ |
|
pNPC->ReportOverThinkLimit( time ); |
|
} |
|
else |
|
{ |
|
#ifdef _WIN32 |
|
Msg( "%s(%s) thinking for %.02f ms!!!\n", GetClassname(), typeid(this).raw_name(), time ); |
|
#elif POSIX |
|
Msg( "%s(%s) thinking for %.02f ms!!!\n", GetClassname(), typeid(this).name(), time ); |
|
#else |
|
#error "typeinfo" |
|
#endif |
|
} |
|
} |
|
} |
|
|
|
VPROF_EXIT_SCOPE(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Does not change the entities velocity at all |
|
// Input : push - |
|
// Output : trace_t |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PhysicsCheckSweep( const Vector& vecAbsStart, const Vector &vecAbsDelta, trace_t *pTrace ) |
|
{ |
|
::PhysicsCheckSweep( this, vecAbsStart, vecAbsDelta, pTrace ); |
|
} |
|
|
|
|
|
|
|
#define MAX_CLIP_PLANES 5 |
|
//----------------------------------------------------------------------------- |
|
// Purpose: The basic solid body movement attempt/clip that slides along multiple planes |
|
// Input : time - Amount of time to try moving for |
|
// *steptrace - if not NULL, the trace results of any vertical wall hit will be stored |
|
// Output : int - the clipflags if the velocity was modified (hit something solid) |
|
// 1 = floor |
|
// 2 = wall / step |
|
// 4 = dead stop |
|
//----------------------------------------------------------------------------- |
|
int CBaseEntity::PhysicsTryMove( float flTime, trace_t *steptrace ) |
|
{ |
|
VPROF("CBaseEntity::PhysicsTryMove"); |
|
|
|
int bumpcount, numbumps; |
|
Vector dir; |
|
float d; |
|
int numplanes; |
|
Vector planes[MAX_CLIP_PLANES]; |
|
Vector primal_velocity, original_velocity, new_velocity; |
|
int i, j; |
|
trace_t trace; |
|
Vector end; |
|
float time_left; |
|
int blocked; |
|
|
|
unsigned int mask = PhysicsSolidMaskForEntity(); |
|
|
|
new_velocity.Init(); |
|
|
|
numbumps = 4; |
|
|
|
Vector vecAbsVelocity = GetAbsVelocity(); |
|
|
|
blocked = 0; |
|
VectorCopy (vecAbsVelocity, original_velocity); |
|
VectorCopy (vecAbsVelocity, primal_velocity); |
|
numplanes = 0; |
|
|
|
time_left = flTime; |
|
|
|
for (bumpcount=0 ; bumpcount<numbumps ; bumpcount++) |
|
{ |
|
if (vecAbsVelocity == vec3_origin) |
|
break; |
|
|
|
VectorMA( GetAbsOrigin(), time_left, vecAbsVelocity, end ); |
|
|
|
Physics_TraceEntity( this, GetAbsOrigin(), end, mask, &trace ); |
|
|
|
if (trace.startsolid) |
|
{ // entity is trapped in another solid |
|
SetAbsVelocity(vec3_origin); |
|
return 4; |
|
} |
|
|
|
if (trace.fraction > 0) |
|
{ // actually covered some distance |
|
SetAbsOrigin( trace.endpos ); |
|
VectorCopy (vecAbsVelocity, original_velocity); |
|
numplanes = 0; |
|
} |
|
|
|
if (trace.fraction == 1) |
|
break; // moved the entire distance |
|
|
|
if (!trace.m_pEnt) |
|
{ |
|
SetAbsVelocity( vecAbsVelocity ); |
|
Warning( "PhysicsTryMove: !trace.u.ent" ); |
|
Assert(0); |
|
return 4; |
|
} |
|
|
|
if (trace.plane.normal[2] > 0.7) |
|
{ |
|
blocked |= 1; // floor |
|
if (CanStandOn( trace.m_pEnt )) |
|
{ |
|
// keep track of time when changing ground entity |
|
if (GetGroundEntity() != trace.m_pEnt) |
|
{ |
|
SetGroundChangeTime( gpGlobals->curtime + (flTime - (1 - trace.fraction) * time_left) ); |
|
} |
|
|
|
SetGroundEntity( trace.m_pEnt ); |
|
} |
|
} |
|
if (!trace.plane.normal[2]) |
|
{ |
|
blocked |= 2; // step |
|
if (steptrace) |
|
*steptrace = trace; // save for player extrafriction |
|
} |
|
|
|
// run the impact function |
|
Vector vecCurrentVelocity = GetAbsVelocity(); |
|
PhysicsImpact( trace.m_pEnt, trace ); |
|
// Removed by the impact function |
|
if ( IsMarkedForDeletion() || IsEdictFree() ) |
|
break; |
|
|
|
time_left -= time_left * trace.fraction; |
|
|
|
// clipped to another plane |
|
if (numplanes >= MAX_CLIP_PLANES) |
|
{ // this shouldn't really happen |
|
SetAbsVelocity(vec3_origin); |
|
return blocked; |
|
} |
|
|
|
VectorCopy (trace.plane.normal, planes[numplanes]); |
|
numplanes++; |
|
|
|
// if physics impact or entity's touch function changed our velocity, then update to use that |
|
if ( GetAbsVelocity() != vecCurrentVelocity ) |
|
{ |
|
vecAbsVelocity = GetAbsVelocity(); |
|
original_velocity = GetAbsVelocity(); |
|
} |
|
else if ( GetMoveType() == MOVETYPE_WALK && (!(GetFlags() & FL_ONGROUND) || GetFriction()!=1) ) // reflect player velocity |
|
{ |
|
// modify original_velocity so it parallels all of the clip planes |
|
for ( i = 0; i < numplanes; i++ ) |
|
{ |
|
if ( planes[i][2] > 0.7 ) |
|
{// floor or slope |
|
PhysicsClipVelocity( original_velocity, planes[i], new_velocity, 1 ); |
|
VectorCopy( new_velocity, original_velocity ); |
|
} |
|
else |
|
{ |
|
PhysicsClipVelocity( original_velocity, planes[i], new_velocity, 1.0 + sv_bounce.GetFloat() * (1-GetFriction()) ); |
|
} |
|
} |
|
|
|
VectorCopy( new_velocity, vecAbsVelocity ); |
|
VectorCopy( new_velocity, original_velocity ); |
|
} |
|
else |
|
{ |
|
for (i=0 ; i<numplanes ; i++) |
|
{ |
|
PhysicsClipVelocity (original_velocity, planes[i], new_velocity, 1); |
|
for (j=0 ; j<numplanes ; j++) |
|
if (j != i) |
|
{ |
|
if (DotProduct (new_velocity, planes[j]) < 0) |
|
break; // not ok |
|
} |
|
if (j == numplanes) |
|
break; |
|
} |
|
|
|
if (i != numplanes) |
|
{ |
|
// go along this plane |
|
VectorCopy (new_velocity, vecAbsVelocity); |
|
} |
|
else |
|
{ |
|
// go along the crease |
|
if (numplanes != 2) |
|
{ |
|
// Msg( "clip velocity, numplanes == %i\n",numplanes); |
|
SetAbsVelocity( vecAbsVelocity ); |
|
return blocked; |
|
} |
|
CrossProduct (planes[0], planes[1], dir); |
|
d = DotProduct (dir, vecAbsVelocity); |
|
VectorScale (dir, d, vecAbsVelocity); |
|
} |
|
|
|
// |
|
// if original velocity is against the original velocity, stop dead |
|
// to avoid tiny oscillations in sloping corners |
|
// |
|
if (DotProduct (vecAbsVelocity, primal_velocity) <= 0) |
|
{ |
|
SetAbsVelocity(vec3_origin); |
|
return blocked; |
|
} |
|
} |
|
} |
|
|
|
SetAbsVelocity( vecAbsVelocity ); |
|
return blocked; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Applies 1/2 gravity to falling movetype step objects |
|
// Simulation should be done assuming average velocity over the time |
|
// interval. Since that would effect a lot of code, and since most of |
|
// that code is going away, it's easier to just add in the average effect |
|
// of gravity on the velocity over the interval at the beginning of similation, |
|
// then add it in again at the end of simulation so that the final velocity is |
|
// correct for the entire interval. |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PhysicsAddHalfGravity( float timestep ) |
|
{ |
|
VPROF("CBaseEntity::PhysicsAddHalfGravity"); |
|
float ent_gravity; |
|
|
|
if ( GetGravity() ) |
|
{ |
|
ent_gravity = GetGravity(); |
|
} |
|
else |
|
{ |
|
ent_gravity = 1.0; |
|
} |
|
|
|
// Add 1/2 of the total gravitational effects over this timestep |
|
Vector vecAbsVelocity = GetAbsVelocity(); |
|
vecAbsVelocity[2] -= ( 0.5 * ent_gravity * sv_gravity.GetFloat() * timestep ); |
|
vecAbsVelocity[2] += GetBaseVelocity()[2] * gpGlobals->frametime; |
|
SetAbsVelocity( vecAbsVelocity ); |
|
|
|
Vector vecNewBaseVelocity = GetBaseVelocity(); |
|
vecNewBaseVelocity[2] = 0; |
|
SetBaseVelocity( vecNewBaseVelocity ); |
|
|
|
// Bound velocity |
|
PhysicsCheckVelocity(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Does not change the entities velocity at all |
|
// Input : push - |
|
// Output : trace_t |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PhysicsPushEntity( const Vector& push, trace_t *pTrace ) |
|
{ |
|
VPROF("CBaseEntity::PhysicsPushEntity"); |
|
|
|
if ( GetMoveParent() ) |
|
{ |
|
Warning( "pushing entity (%s) that has parent (%s)!\n", GetDebugName(), GetMoveParent()->GetDebugName() ); |
|
Assert(0); |
|
} |
|
|
|
// NOTE: absorigin and origin must be equal because there is no moveparent |
|
Vector prevOrigin; |
|
VectorCopy( GetAbsOrigin(), prevOrigin ); |
|
|
|
::PhysicsCheckSweep( this, prevOrigin, push, pTrace ); |
|
|
|
if ( pTrace->fraction ) |
|
{ |
|
SetAbsOrigin( pTrace->endpos ); |
|
|
|
// FIXME(ywb): Should we try to enable this here |
|
// WakeRestingObjects(); |
|
} |
|
|
|
// Passing in the previous abs origin here will cause the relinker |
|
// to test the swept ray from previous to current location for trigger intersections |
|
PhysicsTouchTriggers( &prevOrigin ); |
|
|
|
if ( pTrace->m_pEnt ) |
|
{ |
|
PhysicsImpact( pTrace->m_pEnt, *pTrace ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: See if entity is inside another entity, if so, returns true if so, fills in *ppEntity if ppEntity is not NULL |
|
// Input : **ppEntity - optional return pointer to entity we are inside of |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CBaseEntity::PhysicsTestEntityPosition( CBaseEntity **ppEntity /*=NULL*/ ) |
|
{ |
|
VPROF("CBaseEntity::PhysicsTestEntityPosition"); |
|
|
|
trace_t trace; |
|
|
|
unsigned int mask = PhysicsSolidMaskForEntity(); |
|
|
|
Physics_TraceEntity( this, GetAbsOrigin(), GetAbsOrigin(), mask, &trace ); |
|
|
|
if ( trace.startsolid ) |
|
{ |
|
if ( ppEntity ) |
|
{ |
|
*ppEntity = trace.m_pEnt; |
|
} |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CBaseEntity::PhysicsPushMove( float movetime ) |
|
{ |
|
VPROF("CBaseEntity::PhysicsPushMove"); |
|
|
|
// If this entity isn't moving, just update the time. |
|
IncrementLocalTime( movetime ); |
|
|
|
if ( GetLocalVelocity() == vec3_origin ) |
|
{ |
|
return NULL; |
|
} |
|
|
|
// Now check that the entire hierarchy can rotate into the new location |
|
CBaseEntity *pBlocker = g_pPushedEntities->PerformLinearPush( this, movetime ); |
|
if ( pBlocker ) |
|
{ |
|
IncrementLocalTime( -movetime ); |
|
} |
|
return pBlocker; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Tries to rotate, returns success or failure |
|
// Input : movetime - |
|
// Output : bool |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CBaseEntity::PhysicsPushRotate( float movetime ) |
|
{ |
|
VPROF("CBaseEntity::PhysicsPushRotate"); |
|
|
|
IncrementLocalTime( movetime ); |
|
|
|
// Not rotating |
|
if ( GetLocalAngularVelocity() == vec3_angle ) |
|
{ |
|
return NULL; |
|
} |
|
|
|
// Now check that the entire hierarchy can rotate into the new location |
|
CBaseEntity *pBlocker = g_pPushedEntities->PerformRotatePush( this, movetime ); |
|
if ( pBlocker ) |
|
{ |
|
IncrementLocalTime( -movetime ); |
|
} |
|
|
|
return pBlocker; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Block of icky shared code from PhysicsParent + PhysicsPusher |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PerformPush( float movetime ) |
|
{ |
|
VPROF("CBaseEntity::PerformPush"); |
|
// NOTE: Use handle index because the previous blocker could have been deleted |
|
int hPrevBlocker = m_pBlocker.ToInt(); |
|
CBaseEntity *pBlocker; |
|
g_pPushedEntities->BeginPush( this ); |
|
if (movetime > 0) |
|
{ |
|
if ( GetLocalAngularVelocity() != vec3_angle ) |
|
{ |
|
if ( GetLocalVelocity() != vec3_origin ) |
|
{ |
|
// NOTE: Both PhysicsPushRotate + PhysicsPushMove |
|
// will attempt to advance local time. Choose the one that's |
|
// the greater of the two from push + move |
|
|
|
// FIXME: Should we really be doing them both simultaneously?? |
|
// FIXME: Choose the *greater* of the two?!? That's strange... |
|
float flInitialLocalTime = m_flLocalTime; |
|
|
|
// moving and rotating, so rotate first, then move |
|
pBlocker = PhysicsPushRotate( movetime ); |
|
if ( !pBlocker ) |
|
{ |
|
float flRotateLocalTime = m_flLocalTime; |
|
|
|
// Reset the local time to what it was before we rotated |
|
m_flLocalTime = flInitialLocalTime; |
|
pBlocker = PhysicsPushMove( movetime ); |
|
if ( m_flLocalTime < flRotateLocalTime ) |
|
{ |
|
m_flLocalTime = flRotateLocalTime; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
// only rotating |
|
pBlocker = PhysicsPushRotate( movetime ); |
|
} |
|
} |
|
else |
|
{ |
|
// only moving |
|
pBlocker = PhysicsPushMove( movetime ); |
|
} |
|
|
|
m_pBlocker = pBlocker; |
|
if (m_pBlocker.ToInt() != hPrevBlocker) |
|
{ |
|
if (hPrevBlocker != INVALID_EHANDLE_INDEX) |
|
{ |
|
EndBlocked(); |
|
} |
|
if (m_pBlocker) |
|
{ |
|
StartBlocked( pBlocker ); |
|
} |
|
} |
|
if (m_pBlocker) |
|
{ |
|
Blocked( m_pBlocker ); |
|
} |
|
|
|
// NOTE NOTE: This is here for brutal reasons. |
|
// For MOVETYPE_PUSH objects with VPhysics shadow objects, the move done time |
|
// is handled by CBaseEntity::VPhyicsUpdatePusher, which only gets called if |
|
// the physics system thinks the entity is awake. That will happen if the |
|
// shadow gets updated, but the push code above doesn't update unless the |
|
// move is successful or non-zero. So we must make sure it's awake |
|
if ( VPhysicsGetObject() ) |
|
{ |
|
VPhysicsGetObject()->Wake(); |
|
} |
|
} |
|
|
|
// move done is handled by physics if it has any |
|
if ( VPhysicsGetObject() ) |
|
{ |
|
// store the list of moved entities for later |
|
// if you actually did an unblocked push that moved entities, and you're using physics (which may block later) |
|
if ( movetime > 0 && !m_pBlocker && GetSolid() == SOLID_VPHYSICS && g_pPushedEntities->CountMovedEntities() > 0 ) |
|
{ |
|
// UNDONE: Any reason to want to call this twice before physics runs? |
|
// If so, maybe just append to the list? |
|
Assert( !GetDataObject( PHYSICSPUSHLIST ) ); |
|
physicspushlist_t *pList = (physicspushlist_t *)CreateDataObject( PHYSICSPUSHLIST ); |
|
if ( pList ) |
|
{ |
|
g_pPushedEntities->StoreMovedEntities( *pList ); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
if ( m_flMoveDoneTime <= m_flLocalTime && m_flMoveDoneTime > 0 ) |
|
{ |
|
SetMoveDoneTime( -1 ); |
|
MoveDone(); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: UNDONE: This is only different from PhysicsParent because of the callback to PhysicsVelocity() |
|
// Can we support that callback in push objects as well? |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PhysicsPusher( void ) |
|
{ |
|
VPROF("CBaseEntity::PhysicsPusher"); |
|
|
|
// regular thinking |
|
if ( !PhysicsRunThink() ) |
|
return; |
|
|
|
m_flVPhysicsUpdateLocalTime = m_flLocalTime; |
|
|
|
float movetime = GetMoveDoneTime(); |
|
if (movetime > gpGlobals->frametime) |
|
{ |
|
movetime = gpGlobals->frametime; |
|
} |
|
|
|
PerformPush( movetime ); |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Non moving objects can only think |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PhysicsNone( void ) |
|
{ |
|
VPROF("CBaseEntity::PhysicsNone"); |
|
|
|
// regular thinking |
|
PhysicsRunThink(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: A moving object that doesn't obey physics |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PhysicsNoclip( void ) |
|
{ |
|
VPROF("CBaseEntity::PhysicsNoclip"); |
|
|
|
// regular thinking |
|
if ( !PhysicsRunThink() ) |
|
{ |
|
return; |
|
} |
|
|
|
// Apply angular velocity |
|
SimulateAngles( gpGlobals->frametime ); |
|
|
|
Vector origin; |
|
VectorMA( GetLocalOrigin(), gpGlobals->frametime, GetLocalVelocity(), origin ); |
|
SetLocalOrigin( origin ); |
|
} |
|
|
|
|
|
void CBaseEntity::PerformCustomPhysics( Vector *pNewPosition, Vector *pNewVelocity, QAngle *pNewAngles, QAngle *pNewAngVelocity ) |
|
{ |
|
// If you're going to use custom physics, you need to implement this! |
|
Assert(0); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Allows entities to describe their own physics |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PhysicsCustom() |
|
{ |
|
VPROF("CBaseEntity::PhysicsCustom"); |
|
PhysicsCheckWater(); |
|
|
|
// regular thinking |
|
if ( !PhysicsRunThink() ) |
|
return; |
|
|
|
// Moving upward, off the ground, or resting on a client/monster, remove FL_ONGROUND |
|
if ( m_vecVelocity[2] > 0 || !GetGroundEntity() || !GetGroundEntity()->IsStandable() ) |
|
{ |
|
SetGroundEntity( NULL ); |
|
} |
|
|
|
// NOTE: The entity must set the position, angles, velocity in its custom movement |
|
Vector vecNewPosition = GetAbsOrigin(); |
|
Vector vecNewVelocity = GetAbsVelocity(); |
|
QAngle angNewAngles = GetAbsAngles(); |
|
QAngle angNewAngVelocity = GetLocalAngularVelocity(); |
|
|
|
PerformCustomPhysics( &vecNewPosition, &vecNewVelocity, &angNewAngles, &angNewAngVelocity ); |
|
|
|
// Store off all of the new state information... |
|
SetAbsVelocity( vecNewVelocity ); |
|
SetAbsAngles( angNewAngles ); |
|
SetLocalAngularVelocity( angNewAngVelocity ); |
|
|
|
Vector move; |
|
VectorSubtract( vecNewPosition, GetAbsOrigin(), move ); |
|
|
|
if ( move.LengthSqr() > 1e-6f ) |
|
{ |
|
// move origin |
|
trace_t trace; |
|
PhysicsPushEntity( move, &trace ); |
|
|
|
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 (IsEdictFree()) |
|
return; |
|
|
|
// check for in water |
|
PhysicsCheckWaterTransition(); |
|
} |
|
|
|
bool g_bTestMoveTypeStepSimulation = true; |
|
ConVar sv_teststepsimulation( "sv_teststepsimulation", "1", 0 ); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Until we remove the above cvar, we need to have the entities able |
|
// to dynamically deal with changing their simulation stuff here. |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::CheckStepSimulationChanged() |
|
{ |
|
if ( g_bTestMoveTypeStepSimulation != IsSimulatedEveryTick() ) |
|
{ |
|
SetSimulatedEveryTick( g_bTestMoveTypeStepSimulation ); |
|
} |
|
|
|
bool hadobject = HasDataObjectType( STEPSIMULATION ); |
|
|
|
if ( g_bTestMoveTypeStepSimulation ) |
|
{ |
|
if ( !hadobject ) |
|
{ |
|
CreateDataObject( STEPSIMULATION ); |
|
} |
|
} |
|
else |
|
{ |
|
if ( hadobject ) |
|
{ |
|
DestroyDataObject( STEPSIMULATION ); |
|
} |
|
} |
|
} |
|
|
|
|
|
#define STEP_TELPORTATION_VEL_SQ ( 4096.0f * 4096.0f ) |
|
//----------------------------------------------------------------------------- |
|
// Purpose: Run regular think and latch off angle/origin changes so we can interpolate them on the server to fake simulation |
|
// Input : *step - |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::StepSimulationThink( float dt ) |
|
{ |
|
// See if we need to allocate, deallocate step simulation object |
|
CheckStepSimulationChanged(); |
|
|
|
StepSimulationData *step = ( StepSimulationData * )GetDataObject( STEPSIMULATION ); |
|
if ( !step ) |
|
{ |
|
PhysicsStepRunTimestep( dt ); |
|
|
|
// Just call the think function directly |
|
PhysicsRunThink( THINK_FIRE_BASE_ONLY ); |
|
} |
|
else |
|
{ |
|
// Assume that it's in use |
|
step->m_bOriginActive = true; |
|
step->m_bAnglesActive = true; |
|
|
|
// Reset networked versions of origin and angles |
|
step->m_nLastProcessTickCount = -1; |
|
step->m_vecNetworkOrigin.Init(); |
|
step->m_angNetworkAngles.Init(); |
|
|
|
// Remember old old values |
|
step->m_Previous2 = step->m_Previous; |
|
|
|
// Remember old values |
|
step->m_Previous.nTickCount = gpGlobals->tickcount; |
|
step->m_Previous.vecOrigin = GetStepOrigin(); |
|
QAngle stepAngles = GetStepAngles(); |
|
AngleQuaternion( stepAngles, step->m_Previous.qRotation ); |
|
|
|
// Run simulation |
|
PhysicsStepRunTimestep( dt ); |
|
|
|
// Call the actual think function... |
|
PhysicsRunThink( THINK_FIRE_BASE_ONLY ); |
|
|
|
// do any local processing that's needed |
|
if (GetBaseAnimating() != NULL) |
|
{ |
|
GetBaseAnimating()->UpdateStepOrigin(); |
|
} |
|
|
|
// Latch new values to see if external code modifies our position/orientation |
|
step->m_Next.vecOrigin = GetStepOrigin(); |
|
stepAngles = GetStepAngles(); |
|
AngleQuaternion( stepAngles, step->m_Next.qRotation ); |
|
// Also store of non-Quaternion version for simple comparisons |
|
step->m_angNextRotation = GetStepAngles(); |
|
step->m_Next.nTickCount = GetNextThinkTick(); |
|
|
|
// Hack: Add a tick if we are simulating every other tick |
|
if ( CBaseEntity::IsSimulatingOnAlternateTicks() ) |
|
{ |
|
++step->m_Next.nTickCount; |
|
} |
|
|
|
// Check for teleportation/snapping of the origin |
|
if ( dt > 0.0f ) |
|
{ |
|
Vector deltaorigin = step->m_Next.vecOrigin - step->m_Previous.vecOrigin; |
|
float velSq = deltaorigin.LengthSqr() / ( dt * dt ); |
|
if ( velSq >= STEP_TELPORTATION_VEL_SQ ) |
|
{ |
|
// Deactivate it due to large origin change |
|
step->m_bOriginActive = false; |
|
step->m_bAnglesActive = false; |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Monsters freefall when they don't have a ground entity, otherwise |
|
// all movement is done with discrete steps. |
|
// This is also used for objects that have become still on the ground, but |
|
// will fall if the floor is pulled out from under them. |
|
// JAY: Extended this to synchronize movement and thinking wherever possible. |
|
// This allows the client-side interpolation to interpolate animation and simulation |
|
// data at the same time. |
|
// UNDONE: Remove all other cases from this loop - only use MOVETYPE_STEP to simulate |
|
// entities that are currently animating/thinking. |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PhysicsStep() |
|
{ |
|
// EVIL HACK: Force these to appear as if they've changed!!! |
|
// The underlying values don't actually change, but we need the network sendproxy on origin/angles |
|
// to get triggered, and that only happens if NetworkStateChanged() appears to have occured. |
|
// Getting them for modify marks them as changed automagically. |
|
m_vecOrigin.GetForModify(); |
|
m_angRotation.GetForModify(); |
|
|
|
// HACK: Make sure that the client latches the networked origin/orientation changes with the current server tick count |
|
// so that we don't get jittery interpolation. All of this is necessary to mimic actual continuous simulation of the underlying |
|
// variables. |
|
SetSimulationTime( gpGlobals->curtime ); |
|
|
|
// Run all but the base think function |
|
PhysicsRunThink( THINK_FIRE_ALL_BUT_BASE ); |
|
|
|
int thinktick = GetNextThinkTick(); |
|
float thinktime = thinktick * TICK_INTERVAL; |
|
|
|
// Is the next think too far out, or non-existent? |
|
// BUGBUG: Interpolation is going to look bad in here. But it should only |
|
// be for dead things - and those should be ragdolls (client-side sim) anyway. |
|
// UNDONE: Remove this and assert? Force MOVETYPE_STEP objs to become MOVETYPE_TOSS when |
|
// they aren't thinking? |
|
// UNDONE: this happens as the first frame for a bunch of things like dynamically created ents. |
|
// can't remove until initial conditions are resolved |
|
float deltaThink = thinktime - gpGlobals->curtime; |
|
if ( thinktime <= 0 || deltaThink > 0.5 ) |
|
{ |
|
PhysicsStepRunTimestep( gpGlobals->frametime ); |
|
PhysicsCheckWaterTransition(); |
|
SetLastThink( -1, gpGlobals->curtime ); |
|
UpdatePhysicsShadowToCurrentPosition(gpGlobals->frametime); |
|
PhysicsRelinkChildren(gpGlobals->frametime); |
|
return; |
|
} |
|
|
|
Vector oldOrigin = GetAbsOrigin(); |
|
|
|
// Feed the position delta back from vphysics if enabled |
|
bool updateFromVPhysics = npc_vphysics.GetBool(); |
|
if ( HasDataObjectType(VPHYSICSUPDATEAI) ) |
|
{ |
|
vphysicsupdateai_t *pUpdate = static_cast<vphysicsupdateai_t *>(GetDataObject( VPHYSICSUPDATEAI )); |
|
if ( pUpdate->stopUpdateTime > gpGlobals->curtime ) |
|
{ |
|
updateFromVPhysics = true; |
|
} |
|
else |
|
{ |
|
float maxAngular; |
|
VPhysicsGetObject()->GetShadowController()->GetMaxSpeed( NULL, &maxAngular ); |
|
VPhysicsGetObject()->GetShadowController()->MaxSpeed( pUpdate->savedShadowControllerMaxSpeed, maxAngular ); |
|
DestroyDataObject(VPHYSICSUPDATEAI); |
|
} |
|
} |
|
|
|
if ( updateFromVPhysics && VPhysicsGetObject() && !GetParent() ) |
|
{ |
|
Vector position; |
|
VPhysicsGetObject()->GetShadowPosition( &position, NULL ); |
|
float delta = (GetAbsOrigin() - position).LengthSqr(); |
|
// for now, use a tolerance of 1 inch for these tests |
|
if ( delta < 1 ) |
|
{ |
|
// physics is really close, check to see if my current position is valid. |
|
// If so, ignore the physics result. |
|
trace_t tr; |
|
Physics_TraceEntity( this, GetAbsOrigin(), GetAbsOrigin(), PhysicsSolidMaskForEntity(), &tr ); |
|
updateFromVPhysics = tr.startsolid; |
|
} |
|
if ( updateFromVPhysics ) |
|
{ |
|
SetAbsOrigin( position ); |
|
PhysicsTouchTriggers(); |
|
} |
|
//NDebugOverlay::Box( position, WorldAlignMins(), WorldAlignMaxs(), 255, 255, 0, 0, 0.0 ); |
|
} |
|
|
|
// not going to think, don't run game physics either |
|
if ( thinktick > gpGlobals->tickcount ) |
|
return; |
|
|
|
// 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; |
|
} |
|
|
|
// simulate over the timestep |
|
float dt = thinktime - GetLastThink(); |
|
|
|
// Now run step simulator |
|
StepSimulationThink( dt ); |
|
|
|
PhysicsCheckWaterTransition(); |
|
|
|
if ( VPhysicsGetObject() ) |
|
{ |
|
if ( !VectorCompare( oldOrigin, GetAbsOrigin() ) ) |
|
{ |
|
VPhysicsGetObject()->UpdateShadow( GetAbsOrigin(), vec3_angle, (GetFlags() & FL_FLY) ? true : false, dt ); |
|
} |
|
} |
|
PhysicsRelinkChildren(dt); |
|
} |
|
|
|
|
|
void UTIL_TraceLineFilterEntity( CBaseEntity *pEntity, const Vector &vecAbsStart, const Vector &vecAbsEnd, |
|
unsigned int mask, const int nCollisionGroup, trace_t *ptr ); |
|
|
|
// Check to see what (if anything) this MOVETYPE_STEP entity is standing on |
|
void CBaseEntity::PhysicsStepRecheckGround() |
|
{ |
|
unsigned int mask = PhysicsSolidMaskForEntity(); |
|
// determine if it's on solid ground at all |
|
Vector mins, maxs, point; |
|
int x, y; |
|
trace_t trace; |
|
|
|
VectorAdd (GetAbsOrigin(), WorldAlignMins(), mins); |
|
VectorAdd (GetAbsOrigin(), WorldAlignMaxs(), maxs); |
|
point[2] = mins[2] - 1; |
|
for (x=0 ; x<=1 ; x++) |
|
{ |
|
for (y=0 ; y<=1 ; y++) |
|
{ |
|
point[0] = x ? maxs[0] : mins[0]; |
|
point[1] = y ? maxs[1] : mins[1]; |
|
|
|
ICollideable *pCollision = GetCollideable(); |
|
|
|
if ( pCollision && IsNPC() ) |
|
{ |
|
UTIL_TraceLineFilterEntity( this, point, point, mask, COLLISION_GROUP_NONE, &trace ); |
|
} |
|
else |
|
{ |
|
UTIL_TraceLine( point, point, mask, this, COLLISION_GROUP_NONE, &trace ); |
|
} |
|
|
|
if ( trace.startsolid ) |
|
{ |
|
SetGroundEntity( trace.m_pEnt ); |
|
return; |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : timestep - |
|
//----------------------------------------------------------------------------- |
|
void CBaseEntity::PhysicsStepRunTimestep( float timestep ) |
|
{ |
|
bool wasonground; |
|
bool inwater; |
|
bool hitsound = false; |
|
float speed, newspeed, control; |
|
float friction; |
|
|
|
PhysicsCheckVelocity(); |
|
|
|
wasonground = ( GetFlags() & FL_ONGROUND ) ? true : false; |
|
|
|
// add gravity except: |
|
// flying monsters |
|
// swimming monsters who are in the water |
|
inwater = PhysicsCheckWater(); |
|
|
|
bool isfalling = false; |
|
float fFallingSpeed = GetAbsVelocity()[2]; |
|
|
|
if ( !wasonground ) |
|
{ |
|
if ( !( GetFlags() & FL_FLY ) ) |
|
{ |
|
if ( !( ( GetFlags() & FL_SWIM ) && ( GetWaterLevel() > 0 ) ) ) |
|
{ |
|
if ( GetAbsVelocity()[2] < ( sv_gravity.GetFloat() * -0.1 ) ) |
|
{ |
|
hitsound = true; |
|
} |
|
|
|
if ( !inwater ) |
|
{ |
|
PhysicsAddHalfGravity( timestep ); |
|
isfalling = true; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if ( !(GetFlags() & FL_STEPMOVEMENT) && |
|
(!VectorCompare(GetAbsVelocity(), vec3_origin) || |
|
!VectorCompare(GetBaseVelocity(), vec3_origin))) |
|
{ |
|
Vector vecAbsVelocity = GetAbsVelocity(); |
|
|
|
SetGroundEntity( NULL ); |
|
// apply friction |
|
// let dead monsters who aren't completely onground slide |
|
if ( wasonground ) |
|
{ |
|
speed = VectorLength( vecAbsVelocity ); |
|
if (speed) |
|
{ |
|
friction = sv_friction.GetFloat() * GetFriction(); |
|
|
|
control = speed < sv_stopspeed.GetFloat() ? sv_stopspeed.GetFloat() : speed; |
|
newspeed = speed - timestep*control*friction; |
|
|
|
if (newspeed < 0) |
|
newspeed = 0; |
|
newspeed /= speed; |
|
|
|
vecAbsVelocity[0] *= newspeed; |
|
vecAbsVelocity[1] *= newspeed; |
|
} |
|
} |
|
|
|
vecAbsVelocity += GetBaseVelocity(); |
|
SetAbsVelocity( vecAbsVelocity ); |
|
|
|
// Apply angular velocity |
|
SimulateAngles( timestep ); |
|
|
|
PhysicsCheckVelocity(); |
|
|
|
PhysicsTryMove( timestep, NULL ); |
|
|
|
PhysicsCheckVelocity(); |
|
|
|
vecAbsVelocity = GetAbsVelocity(); |
|
vecAbsVelocity -= GetBaseVelocity(); |
|
SetAbsVelocity( vecAbsVelocity ); |
|
|
|
PhysicsCheckVelocity(); |
|
|
|
if ( !(GetFlags() & FL_ONGROUND) ) |
|
{ |
|
PhysicsStepRecheckGround(); |
|
} |
|
|
|
PhysicsTouchTriggers(); |
|
} |
|
|
|
if (!( GetFlags() & FL_ONGROUND ) && isfalling) |
|
{ |
|
PhysicsAddHalfGravity( timestep ); |
|
} |
|
|
|
// if we were falling before and now we're on the ground.. |
|
if ( isfalling && GetFlags() & FL_ONGROUND ) |
|
{ |
|
PhysicsLandedOnGround( fFallingSpeed ); |
|
} |
|
} |
|
|
|
// After this long, if a player isn't updating, then return it's projectiles to server control |
|
#define PLAYER_PACKETS_STOPPED_SO_RETURN_TO_PHYSICS_TIME 1.0f |
|
|
|
void Physics_SimulateEntity( CBaseEntity *pEntity ) |
|
{ |
|
VPROF( ( !vprof_scope_entity_gamephys.GetBool() ) ? |
|
"Physics_SimulateEntity" : |
|
EntityFactoryDictionary()->GetCannonicalName( pEntity->GetClassname() ) ); |
|
|
|
if ( pEntity->edict() ) |
|
{ |
|
#if !defined( NO_ENTITY_PREDICTION ) |
|
// Player drives simulation of this entity |
|
if ( pEntity->IsPlayerSimulated() ) |
|
{ |
|
// If the player is gone, dropped, crashed, then return |
|
// control to the game code. |
|
CBasePlayer *simulatingPlayer = pEntity->GetSimulatingPlayer(); |
|
if ( simulatingPlayer && |
|
( simulatingPlayer->GetTimeBase() > gpGlobals->curtime - PLAYER_PACKETS_STOPPED_SO_RETURN_TO_PHYSICS_TIME ) ) |
|
{ |
|
// Okay, the guy is still around |
|
return; |
|
} |
|
|
|
pEntity->UnsetPlayerSimulated(); |
|
} |
|
#endif |
|
|
|
#if !defined( NO_ENTITY_PREDICTION ) && defined( USE_PREDICTABLEID ) |
|
// If an object was at one point player simulated, but had that status revoked (as just |
|
// above when no packets have arrived in a while ), then we still will assume that the |
|
// owner/player will be predicting the entity locally (even if the game is playing like butt) |
|
// and so we won't spam that player with additional network data such as effects/sounds |
|
// that are theoretically being predicted by the player anyway. |
|
if ( pEntity->m_PredictableID->IsActive() ) |
|
{ |
|
CBasePlayer *playerowner = ToBasePlayer( pEntity->GetOwnerEntity() ); |
|
if ( playerowner ) |
|
{ |
|
CBasePlayer *pl = ToBasePlayer( UTIL_PlayerByIndex( pEntity->m_PredictableID->GetPlayer() + 1 ) ); |
|
// Is the player who created it still the owner? |
|
if ( pl == playerowner ) |
|
{ |
|
// Set up to suppress sending events to owner player |
|
if ( pl->IsPredictingWeapons() ) |
|
{ |
|
IPredictionSystem::SuppressHostEvents( playerowner ); |
|
} |
|
} |
|
} |
|
{ |
|
VPROF( "pEntity->PhysicsSimulate" ); |
|
|
|
// Run entity physics |
|
pEntity->PhysicsSimulate(); |
|
} |
|
|
|
// Restore suppression filter |
|
IPredictionSystem::SuppressHostEvents( NULL ); |
|
} |
|
else |
|
#endif |
|
{ |
|
// Run entity physics |
|
pEntity->PhysicsSimulate(); |
|
} |
|
} |
|
else |
|
{ |
|
pEntity->PhysicsRunThink(); |
|
} |
|
} |
|
//----------------------------------------------------------------------------- |
|
// Purpose: Runs the main physics simulation loop against all entities ( except players ) |
|
//----------------------------------------------------------------------------- |
|
void Physics_RunThinkFunctions( bool simulating ) |
|
{ |
|
MDLCACHE_CRITICAL_SECTION(); |
|
VPROF( "Physics_RunThinkFunctions"); |
|
|
|
g_bTestMoveTypeStepSimulation = sv_teststepsimulation.GetBool(); |
|
|
|
float starttime = gpGlobals->curtime; |
|
// clear all entites freed outside of this loop |
|
gEntList.CleanupDeleteList(); |
|
|
|
if ( !simulating ) |
|
{ |
|
// only simulate players |
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ ) |
|
{ |
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); |
|
if ( pPlayer ) |
|
{ |
|
// Always reset clock to real sv.time |
|
gpGlobals->curtime = starttime; |
|
// Force usercmd processing even though gpGlobals->tickcount isn't incrementing |
|
pPlayer->ForceSimulation(); |
|
Physics_SimulateEntity( pPlayer ); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
UTIL_DisableRemoveImmediate(); |
|
int listMax = SimThink_ListCount(); |
|
listMax = MAX(listMax,1); |
|
CBaseEntity **list = (CBaseEntity **)stackalloc( sizeof(CBaseEntity *) * listMax ); |
|
// iterate through all entities and have them think or simulate |
|
|
|
// UNDONE: This has problems with UTIL_RemoveImmediate() (now disabled during this loop). |
|
// Do we really need UTIL_RemoveImmediate()? |
|
int count = SimThink_ListCopy( list, listMax ); |
|
|
|
//DevMsg(1, "Count: %d\n", count ); |
|
for ( int i = 0; i < count; i++ ) |
|
{ |
|
if ( !list[i] ) |
|
continue; |
|
// Always reset clock to real sv.time |
|
gpGlobals->curtime = starttime; |
|
Physics_SimulateEntity( list[i] ); |
|
} |
|
|
|
stackfree( list ); |
|
UTIL_EnableRemoveImmediate(); |
|
} |
|
|
|
gpGlobals->curtime = starttime; |
|
} |
|
|
|
|