summaryrefslogtreecommitdiff
path: root/game/server/portal/physicsshadowclone.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/portal/physicsshadowclone.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/server/portal/physicsshadowclone.cpp')
-rw-r--r--game/server/portal/physicsshadowclone.cpp1091
1 files changed, 1091 insertions, 0 deletions
diff --git a/game/server/portal/physicsshadowclone.cpp b/game/server/portal/physicsshadowclone.cpp
new file mode 100644
index 0000000..1f1f448
--- /dev/null
+++ b/game/server/portal/physicsshadowclone.cpp
@@ -0,0 +1,1091 @@
+//========= 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();
+}
+
+
+