source-engine/game/server/portal/physicsshadowclone.cpp
2022-04-16 12:05:19 +03:00

1092 lines
30 KiB
C++

//========= Copyright 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?
static void DrawDebugOverlayForShadowClone( CPhysicsShadowClone *pClone );
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 );
}
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->GetAbsAngles();
vVelocity = pSource->GetAbsVelocity();
if( !m_bShadowTransformIsIdentity )
{
ptOrigin = (*pTransform) * ptOrigin;
qAngles = TransformAnglesToWorldSpace( qAngles, pTransform->As3x4() );
vVelocity = pTransform->ApplyRotation( vVelocity );
}
//else
//{
// pDest->SetGroundEntity( pSource->GetGroundEntity() );
//}
if( (ptOrigin != pDest->GetAbsOrigin()) || (qAngles != pDest->GetAbsAngles()) )
{
pDest->Teleport( &ptOrigin, &qAngles, NULL );
}
if( vVelocity != pDest->GetAbsVelocity() )
{
//pDest->IncrementInterpolationFrame();
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->EnableCollisions( pSource->IsCollisionEnabled() );
pDest->EnableGravity( pSource->IsGravityEnabled() );
pDest->EnableDrag( pSource->IsDragEnabled() );
pDest->EnableMotion( pSource->IsMotionEnabled() );
}
//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
pDest->SetMass( pSource->GetMass() );
Vector ptOrigin, vVelocity, vAngularVelocity, vInertia;
QAngle qAngles;
pSource->GetPosition( &ptOrigin, &qAngles );
pSource->GetVelocity( &vVelocity, &vAngularVelocity );
vInertia = pSource->GetInertia();
if( pTransform )
{
#if 0
pDest->SetPositionMatrix( pTransform->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 );
if( (ptOrigin != ptDestOrigin) || (qAngles != qDestAngles) )
pDest->SetPosition( ptOrigin, qAngles, bTeleport );
//pDest->SetVelocityInstantaneous( &vec3_origin, &vec3_origin );
//pDest->Sleep();
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();
}
}
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() );
}
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 );
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
pSource->SetGameFlags( iOldGameFlags );
cloneLink.pClone = m_pOwnerPhysEnvironment->UnserializeObjectFromBuffer( this, pBuffer, size, false ); //unserializer has to be in the target environment
assert( cloneLink.pClone ); //there should be absolutely no case where we can't clone a valid existing physics object
stackfree(pBuffer);
}
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 )
{
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();
SetMoveType( MOVETYPE_NONE );
SetSolid( SOLID_NONE );
SetSolidFlags( 0 );
SetCollisionGroup( COLLISION_GROUP_NONE );
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 = 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( pPhysics->IsStatic() )
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 = *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()];
}
static void DrawDebugOverlayForShadowClone( CPhysicsShadowClone *pClone )
{
unsigned char iColorIntensity = (pClone->IsInAssumedSyncState())?(127):(255);
int iRed = (pClone->IsUntransformedClone())?(0):(iColorIntensity);
int iGreen = iColorIntensity;
int iBlue = iColorIntensity;
NDebugOverlay::EntityBounds( pClone, iRed, iGreen, iBlue, (iColorIntensity>>2), 0.05f );
}
bool CTraceFilterTranslateClones::ShouldHitEntity( IHandleEntity *pEntity, int contentsMask )
{
CBaseEntity *pEnt = EntityFromEntityHandle( pEntity );
if( CPhysicsShadowClone::IsShadowClone( pEnt ) )
{
CBaseEntity *pClonedEntity = ((CPhysicsShadowClone *)pEnt)->GetClonedEntity();
CPortalSimulator *pSimulator = CPortalSimulator::GetSimulatorThatOwnsEntity( pClonedEntity );
if( pSimulator->m_DataAccess.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();
}