Counter Strike : Global Offensive Source Code
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.
 
 
 
 
 
 

1220 lines
34 KiB

//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Clones a physics object (usually with a matrix transform applied)
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "physicsshadowclone.h"
#include "portal_util_shared.h"
#include "vphysics/object_hash.h"
#include "trains.h"
#include "props.h"
#include "model_types.h"
#include "portal/weapon_physcannon.h" //grab controllers
#include "PortalSimulation.h"
#define MAX_SHADOW_CLONE_COUNT 200
static int g_iShadowCloneCount = 0;
ConVar sv_debug_physicsshadowclones("sv_debug_physicsshadowclones", "0", FCVAR_REPLICATED );
ConVar sv_use_shadow_clones( "sv_use_shadow_clones", "1", FCVAR_REPLICATED | FCVAR_CHEAT ); //should we create shadow clones?
LINK_ENTITY_TO_CLASS( physicsshadowclone, CPhysicsShadowClone );
static CUtlVector<CPhysicsShadowClone *> s_ActiveShadowClones;
CUtlVector<CPhysicsShadowClone *> const &CPhysicsShadowClone::g_ShadowCloneList = s_ActiveShadowClones;
static bool s_IsShadowClone[MAX_EDICTS] = { false };
static CPhysicsShadowCloneLL *s_EntityClones[MAX_EDICTS] = { NULL };
struct ShadowCloneLLEntryManager
{
CPhysicsShadowCloneLL m_ShadowCloneLLEntries[MAX_SHADOW_CLONE_COUNT];
CPhysicsShadowCloneLL *m_pFreeShadowCloneLLEntries[MAX_SHADOW_CLONE_COUNT];
int m_iUsedEntryIndex;
ShadowCloneLLEntryManager( void )
{
m_iUsedEntryIndex = 0;
for( int i = 0; i != MAX_SHADOW_CLONE_COUNT; ++i )
{
m_pFreeShadowCloneLLEntries[i] = &m_ShadowCloneLLEntries[i];
}
}
inline CPhysicsShadowCloneLL *Alloc( void )
{
return m_pFreeShadowCloneLLEntries[m_iUsedEntryIndex++];
}
inline void Free( CPhysicsShadowCloneLL *pFree )
{
m_pFreeShadowCloneLLEntries[--m_iUsedEntryIndex] = pFree;
}
};
static ShadowCloneLLEntryManager s_SCLLManager;
CPhysicsShadowClone::CPhysicsShadowClone( void )
{
m_matrixShadowTransform.Identity();
m_matrixShadowTransform_Inverse.Identity();
m_bShadowTransformIsIdentity = true;
s_ActiveShadowClones.AddToTail( this );
}
CPhysicsShadowClone::~CPhysicsShadowClone( void )
{
VPhysicsDestroyObject();
VPhysicsSetObject( NULL );
m_hClonedEntity = NULL;
s_ActiveShadowClones.FindAndRemove( this ); //also removed in UpdateOnRemove()
Assert( s_IsShadowClone[entindex()] == true );
s_IsShadowClone[entindex()] = false;
}
void CPhysicsShadowClone::UpdateOnRemove( void )
{
CBaseEntity *pSource = m_hClonedEntity;
if( pSource )
{
CPhysicsShadowCloneLL *pCloneListHead = s_EntityClones[pSource->entindex()];
Assert( pCloneListHead != NULL );
CPhysicsShadowCloneLL *pFind = pCloneListHead;
CPhysicsShadowCloneLL *pLast = pFind;
while( pFind->pClone != this )
{
pLast = pFind;
Assert( pFind->pNext != NULL );
pFind = pFind->pNext;
}
if( pFind == pCloneListHead )
{
s_EntityClones[pSource->entindex()] = pFind->pNext;
}
else
{
pLast->pNext = pFind->pNext;
}
s_SCLLManager.Free( pFind );
}
#ifdef _DEBUG
else
{
//verify that it didn't weasel into a list somewhere and get left behind
for( int i = 0; i != MAX_SHADOW_CLONE_COUNT; ++i )
{
CPhysicsShadowCloneLL *pCloneSearch = s_EntityClones[i];
while( pCloneSearch )
{
Assert( pCloneSearch->pClone != this );
pCloneSearch = pCloneSearch->pNext;
}
}
}
#endif
VPhysicsDestroyObject();
VPhysicsSetObject( NULL );
m_hClonedEntity = NULL;
s_ActiveShadowClones.FindAndRemove( this ); //also removed in Destructor
BaseClass::UpdateOnRemove();
}
void CPhysicsShadowClone::Spawn( void )
{
AddFlag( FL_DONTTOUCH );
AddEffects( EF_NODRAW | EF_NOSHADOW | EF_NORECEIVESHADOW );
FullSync( false );
m_bInAssumedSyncState = false;
BaseClass::Spawn();
s_IsShadowClone[entindex()] = true;
}
void CPhysicsShadowClone::FullSync( bool bAllowAssumedSync )
{
Assert( IsMarkedForDeletion() == false );
CBaseEntity *pClonedEntity = m_hClonedEntity.Get();
if( pClonedEntity == NULL )
{
AssertMsg( VPhysicsGetObject() != NULL, "Been linkless for more than this update, something should have killed this clone." );
SetMoveType( MOVETYPE_NONE );
SetSolid( SOLID_NONE );
SetSolidFlags( 0 );
SetCollisionGroup( COLLISION_GROUP_NONE );
VPhysicsDestroyObject();
return;
}
SetGroundEntity( NULL );
bool bIsSynced = bAllowAssumedSync;
bool bBigChanges = true; //assume there are, and be proven wrong
if( bAllowAssumedSync )
{
IPhysicsObject *pSourceObjects[1024];
int iObjectCount = pClonedEntity->VPhysicsGetObjectList( pSourceObjects, 1024 );
//scan for really big differences that would definitely require a full sync
bBigChanges = ( iObjectCount != m_CloneLinks.Count() );
if( !bBigChanges )
{
for( int i = 0; i != iObjectCount; ++i )
{
IPhysicsObject *pSourcePhysics = pSourceObjects[i];
IPhysicsObject *pClonedPhysics = m_CloneLinks[i].pClone;
if( (pSourcePhysics != m_CloneLinks[i].pSource) ||
(pSourcePhysics->IsCollisionEnabled() != pClonedPhysics->IsCollisionEnabled()) )
{
bBigChanges = true;
bIsSynced = false;
break;
}
Vector ptSourcePosition, ptClonePosition;
pSourcePhysics->GetPosition( &ptSourcePosition, NULL );
if( !m_bShadowTransformIsIdentity )
ptSourcePosition = m_matrixShadowTransform * ptSourcePosition;
pClonedPhysics->GetPosition( &ptClonePosition, NULL );
if( (ptClonePosition - ptSourcePosition).LengthSqr() > 2500.0f )
{
bBigChanges = true;
bIsSynced = false;
break;
}
//Vector vSourceVelocity, vCloneVelocity;
if( !pSourcePhysics->IsAsleep() ) //only allow full syncrosity if the source entity is entirely asleep
bIsSynced = false;
if( m_bInAssumedSyncState && !pClonedPhysics->IsAsleep() )
bIsSynced = false;
}
}
else
{
bIsSynced = false;
}
bIsSynced = false;
if( bIsSynced )
{
//good enough to skip a full update
if( !m_bInAssumedSyncState )
{
//do one last sync
PartialSync( true );
//if we don't do this, objects just fall out of the world (it happens, I swear)
for( int i = m_CloneLinks.Count(); --i >= 0; )
{
if( (m_CloneLinks[i].pSource->GetShadowController() == NULL) && m_CloneLinks[i].pClone->IsMotionEnabled() )
{
//m_CloneLinks[i].pClone->SetVelocityInstantaneous( &vec3_origin, &vec3_origin );
//m_CloneLinks[i].pClone->SetVelocity( &vec3_origin, &vec3_origin );
m_CloneLinks[i].pClone->EnableGravity( false );
m_CloneLinks[i].pClone->EnableMotion( false );
m_CloneLinks[i].pClone->Sleep();
}
}
m_bInAssumedSyncState = true;
}
if( sv_debug_physicsshadowclones.GetBool() )
DrawDebugOverlayForShadowClone( this );
return;
}
}
m_bInAssumedSyncState = false;
//past this point, we're committed to a broad update
if( bBigChanges )
{
MoveType_t sourceMoveType = pClonedEntity->GetMoveType();
IPhysicsObject *pPhysObject = pClonedEntity->VPhysicsGetObject();
if( (sourceMoveType == MOVETYPE_CUSTOM) ||
(sourceMoveType == MOVETYPE_STEP) ||
(sourceMoveType == MOVETYPE_WALK) ||
(pPhysObject &&
(
(pPhysObject->GetGameFlags() & FVPHYSICS_PLAYER_HELD) ||
(pPhysObject->GetShadowController() != NULL)
)
)
)
{
//#ifdef _DEBUG
SetMoveType( MOVETYPE_NONE ); //to kill an assert
//#endif
//PUSH should be used sparingly, you can't stand on a MOVETYPE_PUSH object :/
SetMoveType( MOVETYPE_VPHYSICS, pClonedEntity->GetMoveCollide() ); //either an unclonable movetype, or a shadow/held object
}
/*else if(sourceMoveType == MOVETYPE_STEP)
{
//SetMoveType( MOVETYPE_NONE ); //to kill an assert
SetMoveType( MOVETYPE_VPHYSICS, pClonedEntity->GetMoveCollide() );
}*/
else
{
//if( m_bShadowTransformIsIdentity )
SetMoveType( sourceMoveType, pClonedEntity->GetMoveCollide() );
//else
//{
// SetMoveType( MOVETYPE_NONE ); //to kill an assert
// SetMoveType( MOVETYPE_PUSH, pClonedEntity->GetMoveCollide() );
//}
}
SolidType_t sourceSolidType = pClonedEntity->GetSolid();
if( sourceSolidType == SOLID_BBOX )
SetSolid( SOLID_VPHYSICS );
else
SetSolid( sourceSolidType );
//SetSolid( SOLID_VPHYSICS );
SetElasticity( pClonedEntity->GetElasticity() );
SetFriction( pClonedEntity->GetFriction() );
int iSolidFlags = pClonedEntity->GetSolidFlags() | FSOLID_CUSTOMRAYTEST;
if( m_bShadowTransformIsIdentity )
iSolidFlags |= FSOLID_CUSTOMBOXTEST; //need this at least for the player or they get stuck in themselves
else
iSolidFlags &= ~FSOLID_FORCE_WORLD_ALIGNED;
/*if( pClonedEntity->IsPlayer() )
{
iSolidFlags |= FSOLID_CUSTOMRAYTEST | FSOLID_CUSTOMBOXTEST;
}*/
SetSolidFlags( iSolidFlags );
SetEffects( pClonedEntity->GetEffects() | (EF_NODRAW | EF_NOSHADOW | EF_NORECEIVESHADOW) );
SetCollisionGroup( pClonedEntity->GetCollisionGroup() );
SetModelIndex( pClonedEntity->GetModelIndex() );
SetModelName( pClonedEntity->GetModelName() );
if( modelinfo->GetModelType( pClonedEntity->GetModel() ) == mod_studio )
SetModel( STRING( pClonedEntity->GetModelName() ) );
CCollisionProperty *pClonedCollisionProp = pClonedEntity->CollisionProp();
SetSize( pClonedCollisionProp->OBBMins(), pClonedCollisionProp->OBBMaxs() );
}
FullSyncClonedPhysicsObjects( bBigChanges );
SyncEntity( true );
if( bBigChanges )
CollisionRulesChanged();
if( sv_debug_physicsshadowclones.GetBool() )
DrawDebugOverlayForShadowClone( this );
}
// this enables a fast/cheap version of teleporting that is less accurate WRT physics objects' contacts after small teleportations
ConVar fast_teleport_enable("fast_teleport_enable", "1");
void CPhysicsShadowClone::SyncEntity( bool bPullChanges )
{
m_bShouldUpSync = false;
CBaseEntity *pSource, *pDest;
VMatrix *pTransform;
if( bPullChanges )
{
pSource = m_hClonedEntity.Get();
pDest = this;
pTransform = &m_matrixShadowTransform;
if( pSource == NULL )
return;
}
else
{
pSource = this;
pDest = m_hClonedEntity.Get();
pTransform = &m_matrixShadowTransform_Inverse;
if( pDest == NULL )
return;
}
Vector ptOrigin, vVelocity;
QAngle qAngles;
ptOrigin = pSource->GetAbsOrigin();
qAngles = pSource->IsPlayer() ? vec3_angle : pSource->GetAbsAngles();
vVelocity = pSource->GetAbsVelocity();
if( !m_bShadowTransformIsIdentity )
{
ptOrigin = (*pTransform) * ptOrigin;
qAngles = TransformAnglesToWorldSpace( qAngles, pTransform->As3x4() );
vVelocity = pTransform->ApplyRotation( vVelocity );
}
//else
//{
// pDest->SetGroundEntity( pSource->GetGroundEntity() );
//}
QAngle qDiff;
RotationDelta( pDest->GetAbsAngles(), qAngles, &qDiff );
if( (ptOrigin - pDest->GetAbsOrigin()).LengthSqr() > 0.0001f || qDiff.LengthSqr() > 0.0001f )
{
pDest->Teleport( &ptOrigin, &qAngles, NULL, !fast_teleport_enable.GetBool() );
}
if( vVelocity != pDest->GetAbsVelocity() )
{
//pDest->AddEffects( EF_NOINTERP );
pDest->SetAbsVelocity( vec3_origin ); //the two step process helps, I don't know why, but it does
pDest->ApplyAbsVelocityImpulse( vVelocity );
}
}
static void FullSyncPhysicsObject( IPhysicsObject *pSource, IPhysicsObject *pDest, const VMatrix *pTransform, bool bTeleport )
{
CGrabController *pGrabController = NULL;
if( !pSource->IsAsleep() )
pDest->Wake();
float fSavedMass = 0.0f, fSavedRotationalDamping; //setting mass to 0.0f purely to kill a warning that I can't seem to kill with pragmas
if( pSource->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
{
//CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
//Assert( pPlayer );
CBaseEntity *pLookingForEntity = (CBaseEntity *)pSource->GetGameData();
CBasePlayer *pHoldingPlayer = GetPlayerHoldingEntity( pLookingForEntity );
if( pHoldingPlayer )
{
pGrabController = GetGrabControllerForPlayer( pHoldingPlayer );
if ( !pGrabController )
pGrabController = GetGrabControllerForPhysCannon( pHoldingPlayer->GetActiveWeapon() );
}
AssertMsg( pGrabController, "Physics object is held, but we can't find the holding controller." );
GetSavedParamsForCarriedPhysObject( pGrabController, pSource, &fSavedMass, &fSavedRotationalDamping );
}
//Boiler plate
{
pDest->SetGameIndex( pSource->GetGameIndex() ); //what's it do?
pDest->SetCallbackFlags( pSource->GetCallbackFlags() ); //wise?
pDest->SetGameFlags( pSource->GetGameFlags() | FVPHYSICS_NO_SELF_COLLISIONS | FVPHYSICS_IS_SHADOWCLONE );
pDest->SetMaterialIndex( pSource->GetMaterialIndex() );
pDest->SetContents( pSource->GetContents() );
pDest->SyncWith( pSource );
}
//Damping
{
float fSpeedDamp, fRotDamp;
if( pGrabController )
{
pSource->GetDamping( &fSpeedDamp, NULL );
pDest->SetDamping( &fSpeedDamp, &fSavedRotationalDamping );
}
else
{
pSource->GetDamping( &fSpeedDamp, &fRotDamp );
pDest->SetDamping( &fSpeedDamp, &fRotDamp );
}
}
//stuff that we really care about
{
if( pGrabController )
{
pDest->SetMass( fSavedMass );
}
else
{
float flMass = pSource->GetMass();
if ( pDest->GetMass() != flMass )
{
pDest->SetMass( flMass );
}
}
Vector vInertia;
Vector vVelocity, vAngularVelocity;
pSource->GetVelocity( &vVelocity, &vAngularVelocity );
vInertia = pSource->GetInertia();
if( pTransform )
{
vVelocity = pTransform->ApplyRotation( vVelocity );
vAngularVelocity = pTransform->ApplyRotation( vAngularVelocity );
}
//avoid oversetting variables (I think that even setting them to the same value they already are disrupts the delicate physics balance)
if( vInertia != pDest->GetInertia() )
pDest->SetInertia( vInertia );
//pDest->SetVelocityInstantaneous( &vec3_origin, &vec3_origin );
//pDest->Sleep();
Vector vDestVelocity, vDestAngularVelocity;
pDest->GetVelocity( &vDestVelocity, &vDestAngularVelocity );
if( (vVelocity != vDestVelocity) || (vAngularVelocity != vDestAngularVelocity) )
pDest->SetVelocityInstantaneous( &vVelocity, &vAngularVelocity );
IPhysicsShadowController *pSourceController = pSource->GetShadowController();
if( pSourceController == NULL )
{
if( pDest->GetShadowController() != NULL )
{
//we don't need a shadow controller anymore
pDest->RemoveShadowController();
}
if ( bTeleport )
{
Vector ptOrigin;
QAngle qAngles;
pSource->GetPosition( &ptOrigin, &qAngles );
Vector ptDestOrigin;
QAngle qDestAngles;
pDest->GetPosition( &ptDestOrigin, &qDestAngles );
if( pTransform )
{
ptOrigin = (*pTransform) * ptOrigin;
qAngles = TransformAnglesToWorldSpace( qAngles, pTransform->As3x4() );
}
if( (ptOrigin != ptDestOrigin) || (qAngles != qDestAngles) )
{
pDest->SetPosition( ptOrigin, qAngles, bTeleport );
}
}
}
else
{
IPhysicsShadowController *pDestController = pDest->GetShadowController();
if( pDestController == NULL )
{
//we need a shadow controller
float fMaxSpeed, fMaxAngularSpeed;
pSourceController->GetMaxSpeed( &fMaxSpeed, &fMaxAngularSpeed );
pDest->SetShadow( fMaxSpeed, fMaxAngularSpeed, pSourceController->AllowsTranslation(), pSourceController->AllowsRotation() );
pDestController = pDest->GetShadowController();
pDestController->SetTeleportDistance( pSourceController->GetTeleportDistance() );
pDestController->SetPhysicallyControlled( pSourceController->IsPhysicallyControlled() );
}
//sync shadow controllers
float fTimeOffset;
Vector ptTargetPosition;
QAngle qTargetAngles;
fTimeOffset = pSourceController->GetTargetPosition( &ptTargetPosition, &qTargetAngles );
if( pTransform )
{
ptTargetPosition = (*pTransform) * ptTargetPosition;
qTargetAngles = TransformAnglesToWorldSpace( qTargetAngles, pTransform->As3x4() );
}
pDestController->Update( ptTargetPosition, qTargetAngles, fTimeOffset );
}
}
//pDest->RecheckContactPoints();
}
static void PartialSyncPhysicsObject( IPhysicsObject *pSource, IPhysicsObject *pDest, const VMatrix *pTransform )
{
Vector ptOrigin, vVelocity, vAngularVelocity, vInertia;
QAngle qAngles;
pSource->GetPosition( &ptOrigin, &qAngles );
pSource->GetVelocity( &vVelocity, &vAngularVelocity );
vInertia = pSource->GetInertia();
if( pTransform )
{
#if 0
//pDest->SetPositionMatrix( matTransform.As3x4(), true ); //works like we think?
#else
ptOrigin = (*pTransform) * ptOrigin;
qAngles = TransformAnglesToWorldSpace( qAngles, pTransform->As3x4() );
vVelocity = pTransform->ApplyRotation( vVelocity );
vAngularVelocity = pTransform->ApplyRotation( vAngularVelocity );
#endif
}
//avoid oversetting variables (I think that even setting them to the same value they already are disrupts the delicate physics balance)
if( vInertia != pDest->GetInertia() )
pDest->SetInertia( vInertia );
Vector ptDestOrigin, vDestVelocity, vDestAngularVelocity;
QAngle qDestAngles;
pDest->GetPosition( &ptDestOrigin, &qDestAngles );
pDest->GetVelocity( &vDestVelocity, &vDestAngularVelocity );
if( (ptOrigin != ptDestOrigin) || (qAngles != qDestAngles) )
pDest->SetPosition( ptOrigin, qAngles, false );
if( (vVelocity != vDestVelocity) || (vAngularVelocity != vDestAngularVelocity) )
pDest->SetVelocity( &vVelocity, &vAngularVelocity );
pDest->EnableCollisions( pSource->IsCollisionEnabled() );
}
IPhysicsObject *ClonePhysObject( IPhysicsObject *pSource, IPhysicsEnvironment *pDestEnvironment, void *pCloneGameData )
{
unsigned int size = physenv->GetObjectSerializeSize(pSource);
byte *pBuffer = (byte *)stackalloc(size);
memset( pBuffer, 0, size );
physenv->SerializeObjectToBuffer( pSource, pBuffer, size ); //this should work across physics environments because the serializer doesn't write anything about itself to the template
return pDestEnvironment->UnserializeObjectFromBuffer( pCloneGameData, pBuffer, size, false ); //unserializer has to be in the target environment
}
void CPhysicsShadowClone::FullSyncClonedPhysicsObjects( bool bTeleport )
{
CBaseEntity *pClonedEntity = m_hClonedEntity.Get();
if( pClonedEntity == NULL )
{
VPhysicsDestroyObject();
return;
}
VMatrix *pTransform;
if( m_bShadowTransformIsIdentity )
pTransform = NULL;
else
pTransform = &m_matrixShadowTransform;
IPhysicsObject *(pSourceObjects[1024]);
int iObjectCount = pClonedEntity->VPhysicsGetObjectList( pSourceObjects, 1024 );
//easy out if nothing has changed
if( iObjectCount == m_CloneLinks.Count() )
{
int i;
for( i = 0; i != iObjectCount; ++i )
{
if( pSourceObjects[i] == NULL )
break;
if( pSourceObjects[i] != m_CloneLinks[i].pSource )
break;
}
if( i == iObjectCount ) //no changes
{
for( i = 0; i != iObjectCount; ++i )
FullSyncPhysicsObject( m_CloneLinks[i].pSource, m_CloneLinks[i].pClone, pTransform, bTeleport );
return;
}
}
//copy the existing list of clone links to a temp array, we're going to be starting from scratch and copying links as we need them
PhysicsObjectCloneLink_t *pExistingLinks = NULL;
int iExistingLinkCount = m_CloneLinks.Count();
if( iExistingLinkCount != 0 )
{
pExistingLinks = (PhysicsObjectCloneLink_t *)stackalloc( sizeof(PhysicsObjectCloneLink_t) * m_CloneLinks.Count() );
memcpy( pExistingLinks, m_CloneLinks.Base(), sizeof(PhysicsObjectCloneLink_t) * m_CloneLinks.Count() );
}
m_CloneLinks.RemoveAll();
//now, go over the object list we just got from the source entity, and either copy or create links as necessary
int i;
for( i = 0; i != iObjectCount; ++i )
{
IPhysicsObject *pSource = pSourceObjects[i];
if( pSource == NULL ) //this really shouldn't happen, but it does >_<
continue;
PhysicsObjectCloneLink_t cloneLink;
int j;
for( j = 0; j != iExistingLinkCount; ++j )
{
if( pExistingLinks[j].pSource == pSource )
break;
}
if( j != iExistingLinkCount )
{
//copyable link found
cloneLink = pExistingLinks[j];
memset( &pExistingLinks[j], 0, sizeof( PhysicsObjectCloneLink_t ) ); //zero out this slot so we don't destroy it in cleanup
}
else
{
//no link found to copy, create a new one
cloneLink.pSource = pSource;
//apparently some collision code gets called on creation before we've set extra game flags, so we're going to cheat a bit and temporarily set our extra flags on the source
unsigned int iOldGameFlags = pSource->GetGameFlags();
pSource->SetGameFlags( iOldGameFlags | FVPHYSICS_IS_SHADOWCLONE );
cloneLink.pClone = ClonePhysObject( pSource, m_pOwnerPhysEnvironment, this );
assert( cloneLink.pClone ); //there should be absolutely no case where we can't clone a valid existing physics object
pSource->SetGameFlags( iOldGameFlags );
}
FullSyncPhysicsObject( cloneLink.pSource, cloneLink.pClone, pTransform, bTeleport );
//cloneLink.pClone->Wake();
m_CloneLinks.AddToTail( cloneLink );
}
//now go over the existing links, if any of them haven't been nullified, they need to be deleted
for( i = 0; i != iExistingLinkCount; ++i )
{
if( pExistingLinks[i].pClone )
m_pOwnerPhysEnvironment->DestroyObject( pExistingLinks[i].pClone ); //also destroys shadow controller
}
VPhysicsSetObject( NULL );
IPhysicsObject *pSource = m_hClonedEntity->VPhysicsGetObject();
for( i = m_CloneLinks.Count(); --i >= 0; )
{
if( m_CloneLinks[i].pSource == pSource )
{
//m_CloneLinks[i].pClone->Wake();
VPhysicsSetObject( m_CloneLinks[i].pClone );
break;
}
}
if( (i < 0) && (m_CloneLinks.Count() != 0) )
{
VPhysicsSetObject( m_CloneLinks[0].pClone );
}
stackfree( pExistingLinks );
//CollisionRulesChanged();
}
void CPhysicsShadowClone::PartialSync( bool bPullChanges )
{
VMatrix *pTransform;
if( bPullChanges )
{
if( m_bShadowTransformIsIdentity )
pTransform = NULL;
else
pTransform = &m_matrixShadowTransform;
for( int i = m_CloneLinks.Count(); --i >= 0; )
PartialSyncPhysicsObject( m_CloneLinks[i].pSource, m_CloneLinks[i].pClone, pTransform );
}
else
{
if( m_bShadowTransformIsIdentity )
pTransform = NULL;
else
pTransform = &m_matrixShadowTransform_Inverse;
for( int i = m_CloneLinks.Count(); --i >= 0; )
PartialSyncPhysicsObject( m_CloneLinks[i].pClone, m_CloneLinks[i].pSource, pTransform );
}
SyncEntity( bPullChanges );
}
int CPhysicsShadowClone::VPhysicsGetObjectList( IPhysicsObject **pList, int listMax )
{
int iCountStop = m_CloneLinks.Count();
if( iCountStop > listMax )
iCountStop = listMax;
for( int i = 0; i != iCountStop; ++i, ++pList )
*pList = m_CloneLinks[i].pClone;
return iCountStop;
}
void CPhysicsShadowClone::VPhysicsDestroyObject( void )
{
SetMoveType( MOVETYPE_NONE );
SetSolid( SOLID_NONE );
SetSolidFlags( 0 );
SetCollisionGroup( COLLISION_GROUP_NONE );
CollisionRulesChanged();
VPhysicsSetObject( NULL );
for( int i = m_CloneLinks.Count(); --i >= 0; )
{
Assert( m_CloneLinks[i].pClone != NULL );
m_pOwnerPhysEnvironment->DestroyObject( m_CloneLinks[i].pClone );
}
m_CloneLinks.RemoveAll();
BaseClass::VPhysicsDestroyObject();
}
bool CPhysicsShadowClone::ShouldCollide( int collisionGroup, int contentsMask ) const
{
CBaseEntity *pClonedEntity = m_hClonedEntity.Get();
if( pClonedEntity )
return pClonedEntity->ShouldCollide( collisionGroup, contentsMask );
else
return false;
}
bool CPhysicsShadowClone::TestCollision( const Ray_t &ray, unsigned int fContentsMask, trace_t& trace )
{
return false;
/*CBaseEntity *pSourceEntity = m_hClonedEntity.Get();
if( pSourceEntity == NULL )
return false;
enginetrace->ClipRayToEntity( ray, fContentsMask, pSourceEntity, &trace );
return trace.DidHit();*/
}
int CPhysicsShadowClone::ObjectCaps( void )
{
return ((BaseClass::ObjectCaps() | FCAP_DONT_SAVE) & ~(FCAP_FORCE_TRANSITION | FCAP_ACROSS_TRANSITION | FCAP_MUST_SPAWN | FCAP_SAVE_NON_NETWORKABLE));
}
void CPhysicsShadowClone::SetCloneTransformationMatrix( const matrix3x4_t &sourceMatrix )
{
m_matrixShadowTransform.Init( sourceMatrix );
m_bShadowTransformIsIdentity = m_matrixShadowTransform.IsIdentity();
if( m_matrixShadowTransform.InverseGeneral( m_matrixShadowTransform_Inverse ) == false )
{
m_matrixShadowTransform.InverseTR( m_matrixShadowTransform_Inverse ); //probably not the right matrix, but we're out of options
}
FullSync();
//PartialSync( true );
}
void CPhysicsShadowClone::SetClonedEntity( EHANDLE hEntToClone )
{
VPhysicsDestroyObject();
m_hClonedEntity = hEntToClone;
//FullSyncClonedPhysicsObjects();
}
EHANDLE CPhysicsShadowClone::GetClonedEntity( void )
{
return m_hClonedEntity;
}
//damage relays to source entity
bool CPhysicsShadowClone::PassesDamageFilter( const CTakeDamageInfo &info )
{
CBaseEntity *pClonedEntity = m_hClonedEntity.Get();
if( pClonedEntity )
return pClonedEntity->PassesDamageFilter( info );
else
return BaseClass::PassesDamageFilter( info );
}
bool CPhysicsShadowClone::CanBeHitByMeleeAttack( CBaseEntity *pAttacker )
{
CBaseEntity *pClonedEntity = m_hClonedEntity.Get();
if( pClonedEntity )
return pClonedEntity->CanBeHitByMeleeAttack( pAttacker );
else
return BaseClass::CanBeHitByMeleeAttack( pAttacker );
}
int CPhysicsShadowClone::OnTakeDamage( const CTakeDamageInfo &info )
{
CBaseEntity *pClonedEntity = m_hClonedEntity.Get();
if( pClonedEntity )
return pClonedEntity->OnTakeDamage( info );
else
return BaseClass::OnTakeDamage( info );
}
int CPhysicsShadowClone::TakeHealth( float flHealth, int bitsDamageType )
{
CBaseEntity *pClonedEntity = m_hClonedEntity.Get();
if( pClonedEntity )
return pClonedEntity->TakeHealth( flHealth, bitsDamageType );
else
return BaseClass::TakeHealth( flHealth, bitsDamageType );
}
void CPhysicsShadowClone::Event_Killed( const CTakeDamageInfo &info )
{
CBaseEntity *pClonedEntity = m_hClonedEntity.Get();
if( pClonedEntity )
pClonedEntity->Event_Killed( info );
else
BaseClass::Event_Killed( info );
}
CPhysicsShadowClone *CPhysicsShadowClone::CreateShadowClone( IPhysicsEnvironment *pInPhysicsEnvironment, EHANDLE hEntToClone, const char *szDebugMarker, const matrix3x4_t *pTransformationMatrix /*= NULL*/ )
{
AssertMsg( szDebugMarker != NULL, "All shadow clones must have a debug marker for where it came from in debug builds." );
if( !sv_use_shadow_clones.GetBool() )
return NULL;
CBaseEntity *pClonedEntity = hEntToClone.Get();
if( pClonedEntity == NULL )
return NULL;
AssertMsg( IsShadowClone( pClonedEntity ) == false, "Shouldn't attempt to clone clones" );
if( pClonedEntity->IsMarkedForDeletion() )
return NULL;
//if( pClonedEntity->IsPlayer() )
// return NULL;
IPhysicsObject *pPhysics = pClonedEntity->VPhysicsGetObject();
if( pPhysics == NULL )
return NULL;
if( pClonedEntity->GetSolid() == SOLID_BSP )
return NULL;
if( pClonedEntity->GetSolidFlags() & (FSOLID_NOT_SOLID | FSOLID_TRIGGER) )
return NULL;
if( pClonedEntity->GetFlags() & (FL_WORLDBRUSH | FL_STATICPROP) )
return NULL;
/*if( FClassnameIs( pClonedEntity, "func_door" ) )
{
//only clone func_door's that are in front of the portal
return NULL;
}*/
// Too many shadow clones breaks the game (too many entities)
if( g_iShadowCloneCount >= MAX_SHADOW_CLONE_COUNT )
{
AssertMsg( false, "Too many shadow clones, consider upping the limit or reducing the level's physics props" );
return NULL;
}
++g_iShadowCloneCount;
CPhysicsShadowClone *pClone = (CPhysicsShadowClone*)CreateEntityByName("physicsshadowclone");
s_IsShadowClone[pClone->entindex()] = true;
pClone->m_pOwnerPhysEnvironment = pInPhysicsEnvironment;
pClone->m_hClonedEntity = hEntToClone;
DBG_CODE_NOSCOPE( pClone->m_szDebugMarker = szDebugMarker; );
CPhysicsShadowCloneLL *pCloneLLEntry = s_SCLLManager.Alloc();
pCloneLLEntry->pClone = pClone;
pCloneLLEntry->pNext = s_EntityClones[pClonedEntity->entindex()];
s_EntityClones[pClonedEntity->entindex()] = pCloneLLEntry;
if( pTransformationMatrix )
{
pClone->m_matrixShadowTransform.Init( *pTransformationMatrix );
pClone->m_bShadowTransformIsIdentity = pClone->m_matrixShadowTransform.IsIdentity();
if( !pClone->m_bShadowTransformIsIdentity )
{
if( pClone->m_matrixShadowTransform.InverseGeneral( pClone->m_matrixShadowTransform_Inverse ) == false )
{
pClone->m_matrixShadowTransform.InverseTR( pClone->m_matrixShadowTransform_Inverse ); //probably not the right matrix, but we're out of options
}
}
}
DispatchSpawn( pClone );
return pClone;
}
void CPhysicsShadowClone::Free( void )
{
VPhysicsDestroyObject();
UTIL_Remove( this );
//Too many shadow clones breaks the game (too many entities)
--g_iShadowCloneCount;
}
void CPhysicsShadowClone::FullSyncAllClones( void )
{
for( int i = s_ActiveShadowClones.Count(); --i >= 0; )
{
s_ActiveShadowClones[i]->FullSync( true );
}
}
IPhysicsObject *CPhysicsShadowClone::TranslatePhysicsToClonedEnt( const IPhysicsObject *pPhysics )
{
if( m_hClonedEntity.Get() != NULL )
{
for( int i = m_CloneLinks.Count(); --i >= 0; )
{
if( m_CloneLinks[i].pClone == pPhysics )
return m_CloneLinks[i].pSource;
}
}
return NULL;
}
void CPhysicsShadowClone::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
{
//the baseclass just screenshakes, makes sounds, and outputs dust, we rely on the original entity to do this when applicable
}
bool CPhysicsShadowClone::IsShadowClone( const CBaseEntity *pEntity )
{
return s_IsShadowClone[pEntity->entindex()];
}
CPhysicsShadowCloneLL *CPhysicsShadowClone::GetClonesOfEntity( const CBaseEntity *pEntity )
{
return s_EntityClones[pEntity->entindex()];
}
void CPhysicsShadowClone::DestroyClonedPhys( IPhysicsObject *pPhys )
{
for( int i = m_CloneLinks.Count(); --i >= 0; )
{
if( pPhys == m_CloneLinks[i].pSource )
{
m_pOwnerPhysEnvironment->DestroyObject( m_CloneLinks[i].pClone ); //also destroys shadow controller
m_CloneLinks.FastRemove( i );
}
}
}
void CPhysicsShadowClone::DestroyClonedCollideable( CPhysCollide *pCollide )
{
for( int i = m_CloneLinks.Count(); --i >= 0; )
{
if( pCollide == m_CloneLinks[i].pSource->GetCollide() )
{
m_pOwnerPhysEnvironment->DestroyObject( m_CloneLinks[i].pClone ); //also destroys shadow controller
m_CloneLinks.FastRemove( i );
}
}
}
void CPhysicsShadowClone::NotifyDestroy( IPhysicsObject *pDestroyingPhys, CBaseEntity *pOwningEntity )
{
if( pOwningEntity )
{
CPhysicsShadowCloneLL *pCloneLL = GetClonesOfEntity( pOwningEntity );
while( pCloneLL )
{
pCloneLL->pClone->DestroyClonedPhys( pDestroyingPhys );
pCloneLL = pCloneLL->pNext;
}
}
else
{
for( int i = 0; i != s_ActiveShadowClones.Count(); ++i )
{
s_ActiveShadowClones[i]->DestroyClonedPhys( pDestroyingPhys );
}
}
}
void CPhysicsShadowClone::NotifyDestroy( CPhysCollide *pDestroyingCollide, CBaseEntity *pOwningEntity )
{
if( pOwningEntity )
{
CPhysicsShadowCloneLL *pCloneLL = GetClonesOfEntity( pOwningEntity );
while( pCloneLL )
{
pCloneLL->pClone->DestroyClonedCollideable( pDestroyingCollide );
pCloneLL = pCloneLL->pNext;
}
}
else
{
for( int i = 0; i != s_ActiveShadowClones.Count(); ++i )
{
s_ActiveShadowClones[i]->DestroyClonedCollideable( pDestroyingCollide );
}
}
}
void DrawDebugOverlayForShadowClone( CPhysicsShadowClone *pClone )
{
unsigned char iColorIntensity = (pClone->IsInAssumedSyncState())?(127):(255);
int iRed = (pClone->IsUntransformedClone())?(0):(iColorIntensity);
int iGreen = iColorIntensity;
int iBlue = iColorIntensity;
CPortalSimulator *pSimulator = CPortalSimulator::GetSimulatorThatOwnsEntity( pClone );
if( pSimulator )
{
Color debugColor = pSimulator->GetInternalData().Debugging.overlayColor;
iRed = debugColor.r();
iGreen = debugColor.g();
iBlue = debugColor.b();
}
for( int i = 0; i != pClone->m_CloneLinks.Count(); ++i )
{
IPhysicsObject *pPhys = pClone->m_CloneLinks[i].pClone;
if( pPhys )
{
const CPhysCollide *pCollide = pPhys->GetCollide();
if( pCollide )
{
Vector origin;
QAngle angles;
pPhys->GetPosition( &origin, &angles );
Vector *outVerts;
int vertCount = physcollision->CreateDebugMesh( pCollide, &outVerts );
int triCount = vertCount / 3;
int vert = 0;
VMatrix tmp = SetupMatrixOrgAngles( origin, angles );
int i;
for ( i = 0; i < vertCount; i++ )
{
outVerts[i] = tmp.VMul4x3( outVerts[i] );
}
for ( i = 0; i < triCount; i++ )
{
NDebugOverlay::Line( outVerts[vert], outVerts[vert + 1], iRed, iGreen, iBlue, true, 0.0f );
NDebugOverlay::Line( outVerts[vert + 1], outVerts[vert + 2], iRed, iGreen, iBlue, true, 0.0f );
NDebugOverlay::Line( outVerts[vert + 2], outVerts[vert], iRed, iGreen, iBlue, true, 0.0f );
vert += 3;
}
physcollision->DestroyDebugMesh( vertCount, outVerts );
}
}
}
NDebugOverlay::EntityBounds( pClone, iRed, iGreen, iBlue, (iColorIntensity>>2), 0.0f );
}
bool CTraceFilterTranslateClones::ShouldHitEntity( IHandleEntity *pEntity, int contentsMask )
{
CBaseEntity *pEnt = EntityFromEntityHandle( pEntity );
if( CPhysicsShadowClone::IsShadowClone( pEnt ) )
{
CBaseEntity *pClonedEntity = ((CPhysicsShadowClone *)pEnt)->GetClonedEntity();
CPortalSimulator *pSimulator = CPortalSimulator::GetSimulatorThatOwnsEntity( pEnt )->GetLinkedPortalSimulator();
if( pSimulator->GetInternalData().Simulation.Dynamic.EntFlags[pClonedEntity->entindex()] & PSEF_IS_IN_PORTAL_HOLE )
return m_pActualFilter->ShouldHitEntity( pClonedEntity, contentsMask );
else
return false;
}
else
{
return m_pActualFilter->ShouldHitEntity( pEntity, contentsMask );
}
}
TraceType_t CTraceFilterTranslateClones::GetTraceType() const
{
return m_pActualFilter->GetTraceType();
}