diff options
Diffstat (limited to 'game/server/portal/physicsshadowclone.cpp')
| -rw-r--r-- | game/server/portal/physicsshadowclone.cpp | 1091 |
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(); +} + + + |