aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/shared/ragdoll_shared.cpp
diff options
context:
space:
mode:
authorJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
committerJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
commit39ed87570bdb2f86969d4be821c94b722dc71179 (patch)
treeabc53757f75f40c80278e87650ea92808274aa59 /mp/src/game/shared/ragdoll_shared.cpp
downloadsource-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz
source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/game/shared/ragdoll_shared.cpp')
-rw-r--r--mp/src/game/shared/ragdoll_shared.cpp1266
1 files changed, 1266 insertions, 0 deletions
diff --git a/mp/src/game/shared/ragdoll_shared.cpp b/mp/src/game/shared/ragdoll_shared.cpp
new file mode 100644
index 00000000..5ffd405d
--- /dev/null
+++ b/mp/src/game/shared/ragdoll_shared.cpp
@@ -0,0 +1,1266 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "ragdoll_shared.h"
+#include "bone_setup.h"
+#include "vphysics/constraints.h"
+#include "vphysics/collision_set.h"
+#include "vcollide_parse.h"
+#include "vphysics_interface.h"
+#include "tier0/vprof.h"
+#include "engine/ivdebugoverlay.h"
+#include "solidsetdefaults.h"
+//CLIENT
+#ifdef CLIENT_DLL
+#include "c_fire_smoke.h"
+#include "c_entitydissolve.h"
+#include "engine/IEngineSound.h"
+#endif
+
+//SERVER
+#if !defined( CLIENT_DLL )
+#include "util.h"
+#include "EntityFlame.h"
+#include "EntityDissolve.h"
+#endif
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+CRagdollLowViolenceManager g_RagdollLVManager;
+
+void CRagdollLowViolenceManager::SetLowViolence( const char *pMapName )
+{
+ // set the value using the engine's low violence settings
+ m_bLowViolence = UTIL_IsLowViolence();
+
+#if !defined( CLIENT_DLL )
+ // the server doesn't worry about low violence during multiplayer games
+ if ( g_pGameRules->IsMultiplayer() )
+ {
+ m_bLowViolence = false;
+ }
+#endif
+
+ // Turn the low violence ragdoll stuff off if we're in the HL2 Citadel maps because
+ // the player has the super gravity gun and fading ragdolls will break things.
+ if( hl2_episodic.GetBool() )
+ {
+ if ( Q_stricmp( pMapName, "ep1_citadel_02" ) == 0 ||
+ Q_stricmp( pMapName, "ep1_citadel_02b" ) == 0 ||
+ Q_stricmp( pMapName, "ep1_citadel_03" ) == 0 )
+ {
+ m_bLowViolence = false;
+ }
+ }
+ else
+ {
+ if ( Q_stricmp( pMapName, "d3_citadel_03" ) == 0 ||
+ Q_stricmp( pMapName, "d3_citadel_04" ) == 0 ||
+ Q_stricmp( pMapName, "d3_citadel_05" ) == 0 ||
+ Q_stricmp( pMapName, "d3_breen_01" ) == 0 )
+ {
+ m_bLowViolence = false;
+ }
+ }
+}
+
+class CRagdollCollisionRules : public IVPhysicsKeyHandler
+{
+public:
+ CRagdollCollisionRules( IPhysicsCollisionSet *pSet )
+ {
+ m_pSet = pSet;
+ m_bSelfCollisions = true;
+ }
+ virtual void ParseKeyValue( void *pData, const char *pKey, const char *pValue )
+ {
+ if ( !strcmpi( pKey, "selfcollisions" ) )
+ {
+ // keys disabled by default
+ Assert( atoi(pValue) == 0 );
+ m_bSelfCollisions = false;
+ }
+ else if ( !strcmpi( pKey, "collisionpair" ) )
+ {
+ if ( m_bSelfCollisions )
+ {
+ char szToken[256];
+ const char *pStr = nexttoken(szToken, pValue, ',');
+ int index0 = atoi(szToken);
+ nexttoken( szToken, pStr, ',' );
+ int index1 = atoi(szToken);
+
+ m_pSet->EnableCollisions( index0, index1 );
+ }
+ else
+ {
+ Assert(0);
+ }
+ }
+ }
+ virtual void SetDefaults( void *pData ) {}
+
+private:
+ IPhysicsCollisionSet *m_pSet;
+ bool m_bSelfCollisions;
+};
+
+class CRagdollAnimatedFriction : public IVPhysicsKeyHandler
+{
+public:
+ CRagdollAnimatedFriction( ragdoll_t *ragdoll )
+ {
+ m_ragdoll = ragdoll;
+ }
+ virtual void ParseKeyValue( void *pData, const char *pKey, const char *pValue )
+ {
+ if ( !strcmpi( pKey, "animfrictionmin" ) )
+ {
+ m_ragdoll->animfriction.iMinAnimatedFriction = atoi( pValue );
+ }
+ else if ( !strcmpi( pKey, "animfrictionmax" ) )
+ {
+ m_ragdoll->animfriction.iMaxAnimatedFriction = atoi( pValue );
+ }
+ else if ( !strcmpi( pKey, "animfrictiontimein" ) )
+ {
+ m_ragdoll->animfriction.flFrictionTimeIn = atof( pValue );
+ }
+ else if ( !strcmpi( pKey, "animfrictiontimeout" ) )
+ {
+ m_ragdoll->animfriction.flFrictionTimeOut = atof( pValue );
+ }
+ else if ( !strcmpi( pKey, "animfrictiontimehold" ) )
+ {
+ m_ragdoll->animfriction.flFrictionTimeHold = atof( pValue );
+ }
+ }
+
+ virtual void SetDefaults( void *pData ) {}
+
+private:
+ ragdoll_t *m_ragdoll;
+};
+
+void RagdollSetupAnimatedFriction( IPhysicsEnvironment *pPhysEnv, ragdoll_t *ragdoll, int iModelIndex )
+{
+ vcollide_t* pCollide = modelinfo->GetVCollide( iModelIndex );
+
+ if ( pCollide )
+ {
+ IVPhysicsKeyParser *pParse = physcollision->VPhysicsKeyParserCreate( pCollide->pKeyValues );
+
+ while ( !pParse->Finished() )
+ {
+ const char *pBlock = pParse->GetCurrentBlockName();
+
+ if ( !strcmpi( pBlock, "animatedfriction") )
+ {
+ CRagdollAnimatedFriction friction( ragdoll );
+ pParse->ParseCustom( (void*)&friction, &friction );
+ }
+ else
+ {
+ pParse->SkipBlock();
+ }
+ }
+
+ physcollision->VPhysicsKeyParserDestroy( pParse );
+ }
+}
+
+static void RagdollAddSolid( IPhysicsEnvironment *pPhysEnv, ragdoll_t &ragdoll, const ragdollparams_t &params, solid_t &solid )
+{
+ if ( solid.index >= 0 && solid.index < params.pCollide->solidCount)
+ {
+ Assert( ragdoll.listCount == solid.index );
+ int boneIndex = Studio_BoneIndexByName( params.pStudioHdr, solid.name );
+ ragdoll.boneIndex[ragdoll.listCount] = boneIndex;
+
+ if ( boneIndex >= 0 )
+ {
+ if ( params.fixedConstraints )
+ {
+ solid.params.mass = 1000.f;
+ }
+
+ solid.params.rotInertiaLimit = 0.1;
+ solid.params.pGameData = params.pGameData;
+ int surfaceData = physprops->GetSurfaceIndex( solid.surfaceprop );
+
+ if ( surfaceData < 0 )
+ surfaceData = physprops->GetSurfaceIndex( "default" );
+
+ solid.params.pName = params.pStudioHdr->pszName();
+ ragdoll.list[ragdoll.listCount].pObject = pPhysEnv->CreatePolyObject( params.pCollide->solids[solid.index], surfaceData, vec3_origin, vec3_angle, &solid.params );
+ ragdoll.list[ragdoll.listCount].pObject->SetPositionMatrix( params.pCurrentBones[boneIndex], true );
+ ragdoll.list[ragdoll.listCount].parentIndex = -1;
+ ragdoll.list[ragdoll.listCount].pObject->SetGameIndex( ragdoll.listCount );
+
+ ragdoll.listCount++;
+ }
+ else
+ {
+ Msg( "CRagdollProp::CreateObjects: Couldn't Lookup Bone %s\n", solid.name );
+ }
+ }
+}
+
+
+static void RagdollAddConstraint( IPhysicsEnvironment *pPhysEnv, ragdoll_t &ragdoll, const ragdollparams_t &params, constraint_ragdollparams_t &constraint )
+{
+ if( constraint.childIndex == constraint.parentIndex )
+ {
+ DevMsg( 1, "Bogus constraint on ragdoll %s\n", params.pStudioHdr->pszName() );
+ constraint.childIndex = -1;
+ constraint.parentIndex = -1;
+ }
+ if ( constraint.childIndex >= 0 && constraint.parentIndex >= 0 )
+ {
+ Assert(constraint.childIndex<ragdoll.listCount);
+
+
+ ragdollelement_t &childElement = ragdoll.list[constraint.childIndex];
+ // save parent index
+ childElement.parentIndex = constraint.parentIndex;
+
+ if ( params.jointFrictionScale > 0 )
+ {
+ for ( int k = 0; k < 3; k++ )
+ {
+ constraint.axes[k].torque *= params.jointFrictionScale;
+ }
+ }
+ // this parent/child pair is not usually a parent/child pair in the skeleton. There
+ // are often bones in between that are collapsed for simulation. So we need to compute
+ // the transform.
+ Studio_CalcBoneToBoneTransform( params.pStudioHdr, ragdoll.boneIndex[constraint.childIndex], ragdoll.boneIndex[constraint.parentIndex], constraint.constraintToAttached );
+ MatrixGetColumn( constraint.constraintToAttached, 3, childElement.originParentSpace );
+ // UNDONE: We could transform the constraint limit axes relative to the bone space
+ // using this data. Do we need that feature?
+ SetIdentityMatrix( constraint.constraintToReference );
+ if ( params.fixedConstraints )
+ {
+ // Makes the ragdoll a statue...
+ constraint_fixedparams_t fixed;
+ fixed.Defaults();
+ fixed.InitWithCurrentObjectState( childElement.pObject, ragdoll.list[constraint.parentIndex].pObject );
+ fixed.constraint.Defaults();
+ childElement.pConstraint = pPhysEnv->CreateFixedConstraint( childElement.pObject, ragdoll.list[constraint.parentIndex].pObject, ragdoll.pGroup, fixed );
+ }
+ else
+ {
+ childElement.pConstraint = pPhysEnv->CreateRagdollConstraint( childElement.pObject, ragdoll.list[constraint.parentIndex].pObject, ragdoll.pGroup, constraint );
+ }
+ }
+}
+
+
+static void RagdollCreateObjects( IPhysicsEnvironment *pPhysEnv, ragdoll_t &ragdoll, const ragdollparams_t &params )
+{
+ ragdoll.listCount = 0;
+ ragdoll.pGroup = NULL;
+ ragdoll.allowStretch = params.allowStretch;
+ memset( ragdoll.list, 0, sizeof(ragdoll.list) );
+ memset( &ragdoll.animfriction, 0, sizeof(ragdoll.animfriction) );
+
+ if ( !params.pCollide || params.pCollide->solidCount > RAGDOLL_MAX_ELEMENTS )
+ return;
+
+ constraint_groupparams_t group;
+ group.Defaults();
+ ragdoll.pGroup = pPhysEnv->CreateConstraintGroup( group );
+
+ IVPhysicsKeyParser *pParse = physcollision->VPhysicsKeyParserCreate( params.pCollide->pKeyValues );
+ while ( !pParse->Finished() )
+ {
+ const char *pBlock = pParse->GetCurrentBlockName();
+ if ( !strcmpi( pBlock, "solid" ) )
+ {
+ solid_t solid;
+
+ pParse->ParseSolid( &solid, &g_SolidSetup );
+ RagdollAddSolid( pPhysEnv, ragdoll, params, solid );
+ }
+ else if ( !strcmpi( pBlock, "ragdollconstraint" ) )
+ {
+ constraint_ragdollparams_t constraint;
+ pParse->ParseRagdollConstraint( &constraint, NULL );
+ RagdollAddConstraint( pPhysEnv, ragdoll, params, constraint );
+ }
+ else if ( !strcmpi( pBlock, "collisionrules" ) )
+ {
+ IPhysicsCollisionSet *pSet = physics->FindOrCreateCollisionSet( params.modelIndex, ragdoll.listCount );
+ CRagdollCollisionRules rules(pSet);
+ pParse->ParseCustom( (void *)&rules, &rules );
+ }
+ else if ( !strcmpi( pBlock, "animatedfriction") )
+ {
+ CRagdollAnimatedFriction friction( &ragdoll );
+ pParse->ParseCustom( (void*)&friction, &friction );
+ }
+ else
+ {
+ pParse->SkipBlock();
+ }
+ }
+ physcollision->VPhysicsKeyParserDestroy( pParse );
+}
+
+void RagdollSetupCollisions( ragdoll_t &ragdoll, vcollide_t *pCollide, int modelIndex )
+{
+ Assert(pCollide);
+ if (!pCollide)
+ return;
+
+ IPhysicsCollisionSet *pSet = physics->FindCollisionSet( modelIndex );
+ if ( !pSet )
+ {
+ pSet = physics->FindOrCreateCollisionSet( modelIndex, ragdoll.listCount );
+ if ( !pSet )
+ return;
+
+ bool bFoundRules = false;
+
+ IVPhysicsKeyParser *pParse = physcollision->VPhysicsKeyParserCreate( pCollide->pKeyValues );
+ while ( !pParse->Finished() )
+ {
+ const char *pBlock = pParse->GetCurrentBlockName();
+ if ( !strcmpi( pBlock, "collisionrules" ) )
+ {
+ IPhysicsCollisionSet *pSet = physics->FindOrCreateCollisionSet( modelIndex, ragdoll.listCount );
+ CRagdollCollisionRules rules(pSet);
+ pParse->ParseCustom( (void *)&rules, &rules );
+ bFoundRules = true;
+ }
+ else
+ {
+ pParse->SkipBlock();
+ }
+ }
+ physcollision->VPhysicsKeyParserDestroy( pParse );
+
+ if ( !bFoundRules )
+ {
+ // these are the default rules - each piece collides with everything
+ // except immediate parent/constrained object.
+ int i;
+ for ( i = 0; i < ragdoll.listCount; i++ )
+ {
+ for ( int j = i+1; j < ragdoll.listCount; j++ )
+ {
+ pSet->EnableCollisions( i, j );
+ }
+ }
+ for ( i = 0; i < ragdoll.listCount; i++ )
+ {
+ int parent = ragdoll.list[i].parentIndex;
+ if ( parent >= 0 )
+ {
+ Assert( ragdoll.list[i].pObject );
+ Assert( ragdoll.list[i].pConstraint );
+ pSet->DisableCollisions( i, parent );
+ }
+ }
+ }
+ }
+}
+
+void RagdollActivate( ragdoll_t &ragdoll, vcollide_t *pCollide, int modelIndex, bool bForceWake )
+{
+ RagdollSetupCollisions( ragdoll, pCollide, modelIndex );
+ for ( int i = 0; i < ragdoll.listCount; i++ )
+ {
+ ragdoll.list[i].pObject->SetGameIndex( i );
+ PhysSetGameFlags( ragdoll.list[i].pObject, FVPHYSICS_MULTIOBJECT_ENTITY );
+ // now that the relationships are set, activate the collision system
+ ragdoll.list[i].pObject->EnableCollisions( true );
+
+ if ( bForceWake == true )
+ {
+ ragdoll.list[i].pObject->Wake();
+ }
+ }
+ if ( ragdoll.pGroup )
+ {
+ // NOTE: This also wakes the objects
+ ragdoll.pGroup->Activate();
+ // so if we didn't want that, we'll need to put them back to sleep here
+ if ( !bForceWake )
+ {
+ for ( int i = 0; i < ragdoll.listCount; i++ )
+ {
+ ragdoll.list[i].pObject->Sleep();
+ }
+
+ }
+ }
+}
+
+
+bool RagdollCreate( ragdoll_t &ragdoll, const ragdollparams_t &params, IPhysicsEnvironment *pPhysEnv )
+{
+ RagdollCreateObjects( pPhysEnv, ragdoll, params );
+
+ if ( !ragdoll.listCount )
+ return false;
+
+ int forceBone = params.forceBoneIndex;
+
+ int i;
+ float totalMass = 0;
+ for ( i = 0; i < ragdoll.listCount; i++ )
+ {
+ totalMass += ragdoll.list[i].pObject->GetMass();
+ }
+ totalMass = MAX(totalMass,1);
+
+ // apply force to the model
+ Vector nudgeForce = params.forceVector;
+ Vector forcePosition = params.forcePosition;
+ // UNDONE: Test scaling the force by total mass on all bones
+
+ Assert( forceBone < ragdoll.listCount );
+
+ if ( forceBone >= 0 && forceBone < ragdoll.listCount )
+ {
+ ragdoll.list[forceBone].pObject->ApplyForceCenter( nudgeForce );
+ //nudgeForce *= 0.5;
+ ragdoll.list[forceBone].pObject->GetPosition( &forcePosition, NULL );
+ }
+
+ for ( i = 0; i < ragdoll.listCount; i++ )
+ {
+ PhysSetGameFlags( ragdoll.list[i].pObject, FVPHYSICS_PART_OF_RAGDOLL );
+ }
+
+ if ( forcePosition != vec3_origin )
+ {
+ for ( i = 0; i < ragdoll.listCount; i++ )
+ {
+ if ( forceBone != i )
+ {
+ float scale = ragdoll.list[i].pObject->GetMass() / totalMass;
+ ragdoll.list[i].pObject->ApplyForceOffset( scale * nudgeForce, forcePosition );
+ }
+ }
+ }
+
+ return true;
+}
+
+
+void RagdollApplyAnimationAsVelocity( ragdoll_t &ragdoll, const matrix3x4_t *pPrevBones, const matrix3x4_t *pCurrentBones, float dt )
+{
+ for ( int i = 0; i < ragdoll.listCount; i++ )
+ {
+ Vector velocity;
+ AngularImpulse angVel;
+ int boneIndex = ragdoll.boneIndex[i];
+ CalcBoneDerivatives( velocity, angVel, pPrevBones[boneIndex], pCurrentBones[boneIndex], dt );
+
+ AngularImpulse localAngVelocity;
+
+ // Angular velocity is always applied in local space in vphysics
+ ragdoll.list[i].pObject->WorldToLocalVector( &localAngVelocity, angVel );
+ ragdoll.list[i].pObject->AddVelocity( &velocity, &localAngVelocity );
+ }
+}
+
+void RagdollApplyAnimationAsVelocity( ragdoll_t &ragdoll, const matrix3x4_t *pBoneToWorld )
+{
+ for ( int i = 0; i < ragdoll.listCount; i++ )
+ {
+ matrix3x4_t inverse;
+ MatrixInvert( pBoneToWorld[i], inverse );
+ Quaternion q;
+ Vector pos;
+ MatrixAngles( inverse, q, pos );
+
+ Vector velocity;
+ AngularImpulse angVel;
+ float flSpin;
+
+ Vector localVelocity;
+ AngularImpulse localAngVelocity;
+
+ QuaternionAxisAngle( q, localAngVelocity, flSpin );
+ localAngVelocity *= flSpin;
+ localVelocity = pos;
+
+ // move those bone-local coords back to world space using the ragdoll transform
+ ragdoll.list[i].pObject->LocalToWorldVector( &velocity, localVelocity );
+
+ ragdoll.list[i].pObject->AddVelocity( &velocity, &localAngVelocity );
+ }
+}
+
+
+void RagdollDestroy( ragdoll_t &ragdoll )
+{
+ if ( !ragdoll.listCount )
+ return;
+
+ int i;
+ for ( i = 0; i < ragdoll.listCount; i++ )
+ {
+ physenv->DestroyConstraint( ragdoll.list[i].pConstraint );
+ ragdoll.list[i].pConstraint = NULL;
+ }
+ for ( i = 0; i < ragdoll.listCount; i++ )
+ {
+ // during level transitions these can get temporarily loaded without physics objects
+ // purely for the purpose of testing for PVS of transition. If they fail they get
+ // deleted before the physics objects are loaded. The list count will be nonzero
+ // since that is saved separately.
+ if ( ragdoll.list[i].pObject )
+ {
+ physenv->DestroyObject( ragdoll.list[i].pObject );
+ }
+ ragdoll.list[i].pObject = NULL;
+ }
+ physenv->DestroyConstraintGroup( ragdoll.pGroup );
+ ragdoll.pGroup = NULL;
+ ragdoll.listCount = 0;
+}
+
+// Parse the ragdoll and obtain the mapping from each physics element index to a bone index
+// returns num phys elements
+int RagdollExtractBoneIndices( int *boneIndexOut, CStudioHdr *pStudioHdr, vcollide_t *pCollide )
+{
+ int elementCount = 0;
+
+ IVPhysicsKeyParser *pParse = physcollision->VPhysicsKeyParserCreate( pCollide->pKeyValues );
+ while ( !pParse->Finished() )
+ {
+ const char *pBlock = pParse->GetCurrentBlockName();
+ if ( !strcmpi( pBlock, "solid" ) )
+ {
+ solid_t solid;
+ pParse->ParseSolid( &solid, NULL );
+ if ( elementCount < RAGDOLL_MAX_ELEMENTS )
+ {
+ boneIndexOut[elementCount] = Studio_BoneIndexByName( pStudioHdr, solid.name );
+ elementCount++;
+ }
+ }
+ else
+ {
+ pParse->SkipBlock();
+ }
+ }
+ physcollision->VPhysicsKeyParserDestroy( pParse );
+
+ return elementCount;
+}
+
+bool RagdollGetBoneMatrix( const ragdoll_t &ragdoll, CBoneAccessor &pBoneToWorld, int objectIndex )
+{
+ int boneIndex = ragdoll.boneIndex[objectIndex];
+ if ( boneIndex < 0 )
+ return false;
+
+ const ragdollelement_t &element = ragdoll.list[objectIndex];
+
+ // during restore if a model has changed since the file was saved, this could be NULL
+ if ( !element.pObject )
+ return false;
+ element.pObject->GetPositionMatrix( &pBoneToWorld.GetBoneForWrite( boneIndex ) );
+ if ( element.parentIndex >= 0 && !ragdoll.allowStretch )
+ {
+ // overwrite the position from physics to force rigid attachment
+ // UNDONE: If we support other types of constraints (or multiple constraints per object)
+ // make sure these don't fight !
+ int parentBoneIndex = ragdoll.boneIndex[element.parentIndex];
+ Vector out;
+ VectorTransform( element.originParentSpace, pBoneToWorld.GetBone( parentBoneIndex ), out );
+ MatrixSetColumn( out, 3, pBoneToWorld.GetBoneForWrite( boneIndex ) );
+ }
+ return true;
+}
+
+void RagdollComputeExactBbox( const ragdoll_t &ragdoll, const Vector &origin, Vector &outMins, Vector &outMaxs )
+{
+ outMins = origin;
+ outMaxs = origin;
+
+ for ( int i = 0; i < ragdoll.listCount; i++ )
+ {
+ Vector mins, maxs;
+ Vector objectOrg;
+ QAngle objectAng;
+ IPhysicsObject *pObject = ragdoll.list[i].pObject;
+ pObject->GetPosition( &objectOrg, &objectAng );
+ physcollision->CollideGetAABB( &mins, &maxs, pObject->GetCollide(), objectOrg, objectAng );
+ for ( int j = 0; j < 3; j++ )
+ {
+ if ( mins[j] < outMins[j] )
+ {
+ outMins[j] = mins[j];
+ }
+ if ( maxs[j] > outMaxs[j] )
+ {
+ outMaxs[j] = maxs[j];
+ }
+ }
+ }
+}
+
+bool RagdollIsAsleep( const ragdoll_t &ragdoll )
+{
+ for ( int i = 0; i < ragdoll.listCount; i++ )
+ {
+ if ( ragdoll.list[i].pObject && !ragdoll.list[i].pObject->IsAsleep() )
+ return false;
+ }
+
+ return true;
+}
+
+void RagdollSolveSeparation( ragdoll_t &ragdoll, CBaseEntity *pEntity )
+{
+ byte needsFix[256];
+ int fixCount = 0;
+ Assert(ragdoll.listCount<=ARRAYSIZE(needsFix));
+ for ( int i = 0; i < ragdoll.listCount; i++ )
+ {
+ needsFix[i] = 0;
+ const ragdollelement_t &element = ragdoll.list[i];
+ if ( element.pConstraint && element.parentIndex >= 0 )
+ {
+ Vector start, target;
+ element.pObject->GetPosition( &start, NULL );
+ ragdoll.list[element.parentIndex].pObject->LocalToWorld( &target, element.originParentSpace );
+ if ( needsFix[element.parentIndex] )
+ {
+ needsFix[i] = 1;
+ ++fixCount;
+ continue;
+ }
+ Vector dir = target-start;
+ if ( dir.LengthSqr() > 1.0f )
+ {
+ // this fixes a bug in ep2 with antlion grubs, but causes problems in TF2 - revisit, but disable for TF now
+#if !defined(TF_CLIENT_DLL)
+ // heuristic: guess that anything separated and small mass ratio is in some state that's
+ // keeping the solver from fixing it
+ float mass = element.pObject->GetMass();
+ float massParent = ragdoll.list[element.parentIndex].pObject->GetMass();
+
+ if ( mass*2.0f < massParent )
+ {
+ // if this is <0.5 mass of parent and still separated it's attached to something heavy or
+ // in a bad state
+ needsFix[i] = 1;
+ ++fixCount;
+ continue;
+ }
+#endif
+
+ if ( PhysHasContactWithOtherInDirection(element.pObject, dir) )
+ {
+ Ray_t ray;
+ trace_t tr;
+ ray.Init( target, start );
+ UTIL_TraceRay( ray, MASK_SOLID, pEntity, COLLISION_GROUP_NONE, &tr );
+ if ( tr.DidHit() )
+ {
+ needsFix[i] = 1;
+ ++fixCount;
+ }
+ }
+ }
+ }
+ }
+
+ if ( fixCount )
+ {
+ for ( int i = 0; i < ragdoll.listCount; i++ )
+ {
+ if ( !needsFix[i] )
+ continue;
+
+ const ragdollelement_t &element = ragdoll.list[i];
+ Vector target, velocity;
+ ragdoll.list[element.parentIndex].pObject->LocalToWorld( &target, element.originParentSpace );
+ ragdoll.list[element.parentIndex].pObject->GetVelocityAtPoint( target, &velocity );
+ matrix3x4_t xform;
+ element.pObject->GetPositionMatrix( &xform );
+ MatrixSetColumn( target, 3, xform );
+ element.pObject->SetPositionMatrix( xform, true );
+ element.pObject->SetVelocity( &velocity, &vec3_origin );
+ }
+ DevMsg(2, "TICK:%5d:Ragdoll separation count: %d\n", gpGlobals->tickcount, fixCount );
+ }
+ else
+ {
+ ragdoll.pGroup->ClearErrorState();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// LRU
+//-----------------------------------------------------------------------------
+#ifdef _XBOX
+// xbox defaults to 4 ragdolls max
+ConVar g_ragdoll_maxcount("g_ragdoll_maxcount", "4", FCVAR_REPLICATED );
+#else
+ConVar g_ragdoll_maxcount("g_ragdoll_maxcount", "8", FCVAR_REPLICATED );
+#endif
+ConVar g_debug_ragdoll_removal("g_debug_ragdoll_removal", "0", FCVAR_REPLICATED |FCVAR_CHEAT );
+
+CRagdollLRURetirement s_RagdollLRU( "CRagdollLRURetirement" );
+
+void CRagdollLRURetirement::LevelInitPreEntity( void )
+{
+ m_iMaxRagdolls = -1;
+ m_LRUImportantRagdolls.RemoveAll();
+ m_LRU.RemoveAll();
+}
+
+bool ShouldRemoveThisRagdoll( CBaseAnimating *pRagdoll )
+{
+ if ( g_RagdollLVManager.IsLowViolence() )
+ {
+ return true;
+ }
+
+#ifdef CLIENT_DLL
+
+ /* we no longer ignore enemies just because they are on fire -- a ragdoll in front of me
+ is always a higher priority for retention than a flaming zombie behind me. At the
+ time I put this in, the ragdolls do clean up their own effects if culled via SUB_Remove().
+ If you're encountering trouble with ragdolls leaving effects behind, try renabling the code below.
+ /////////////////////
+ //Just ignore it until we're done burning/dissolving.
+ if ( pRagdoll->GetEffectEntity() )
+ return false;
+ */
+
+ Vector vMins, vMaxs;
+
+ Vector origin = pRagdoll->m_pRagdoll->GetRagdollOrigin();
+ pRagdoll->m_pRagdoll->GetRagdollBounds( vMins, vMaxs );
+
+ if( engine->IsBoxInViewCluster( vMins + origin, vMaxs + origin) == false )
+ {
+ if ( g_debug_ragdoll_removal.GetBool() )
+ {
+ debugoverlay->AddBoxOverlay( origin, vMins, vMaxs, QAngle( 0, 0, 0 ), 0, 255, 0, 16, 5 );
+ debugoverlay->AddLineOverlay( origin, origin + Vector( 0, 0, 64 ), 0, 255, 0, true, 5 );
+ }
+
+ return true;
+ }
+ else if( engine->CullBox( vMins + origin, vMaxs + origin ) == true )
+ {
+ if ( g_debug_ragdoll_removal.GetBool() )
+ {
+ debugoverlay->AddBoxOverlay( origin, vMins, vMaxs, QAngle( 0, 0, 0 ), 0, 0, 255, 16, 5 );
+ debugoverlay->AddLineOverlay( origin, origin + Vector( 0, 0, 64 ), 0, 0, 255, true, 5 );
+ }
+
+ return true;
+ }
+
+#else
+ CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
+
+ if( !UTIL_FindClientInPVS( pRagdoll->edict() ) )
+ {
+ if ( g_debug_ragdoll_removal.GetBool() )
+ NDebugOverlay::Line( pRagdoll->GetAbsOrigin(), pRagdoll->GetAbsOrigin() + Vector( 0, 0, 64 ), 0, 255, 0, true, 5 );
+
+ return true;
+ }
+ else if( !pPlayer->FInViewCone( pRagdoll ) )
+ {
+ if ( g_debug_ragdoll_removal.GetBool() )
+ NDebugOverlay::Line( pRagdoll->GetAbsOrigin(), pRagdoll->GetAbsOrigin() + Vector( 0, 0, 64 ), 0, 0, 255, true, 5 );
+
+ return true;
+ }
+
+#endif
+
+ return false;
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Cull stale ragdolls. There is an ifdef here: one version for episodic,
+// one for everything else.
+//-----------------------------------------------------------------------------
+#if HL2_EPISODIC
+
+void CRagdollLRURetirement::Update( float frametime ) // EPISODIC VERSION
+{
+ VPROF( "CRagdollLRURetirement::Update" );
+ // Compress out dead items
+ int i, next;
+
+ int iMaxRagdollCount = m_iMaxRagdolls;
+
+ if ( iMaxRagdollCount == -1 )
+ {
+ iMaxRagdollCount = g_ragdoll_maxcount.GetInt();
+ }
+
+ // fade them all for the low violence version
+ if ( g_RagdollLVManager.IsLowViolence() )
+ {
+ iMaxRagdollCount = 0;
+ }
+ m_iRagdollCount = 0;
+ m_iSimulatedRagdollCount = 0;
+
+ // First, find ragdolls that are good candidates for deletion because they are not
+ // visible at all, or are in a culled visibility box
+ for ( i = m_LRU.Head(); i < m_LRU.InvalidIndex(); i = next )
+ {
+ next = m_LRU.Next(i);
+ CBaseAnimating *pRagdoll = m_LRU[i].Get();
+ if ( pRagdoll )
+ {
+ m_iRagdollCount++;
+ IPhysicsObject *pObject = pRagdoll->VPhysicsGetObject();
+ if (pObject && !pObject->IsAsleep())
+ {
+ m_iSimulatedRagdollCount++;
+ }
+ if ( m_LRU.Count() > iMaxRagdollCount )
+ {
+ //Found one, we're done.
+ if ( ShouldRemoveThisRagdoll( m_LRU[i] ) == true )
+ {
+#ifdef CLIENT_DLL
+ m_LRU[ i ]->SUB_Remove();
+#else
+ m_LRU[ i ]->SUB_StartFadeOut( 0 );
+#endif
+
+ m_LRU.Remove(i);
+ return;
+ }
+ }
+ }
+ else
+ {
+ m_LRU.Remove(i);
+ }
+ }
+
+ //////////////////////////////
+ /// EPISODIC ALGORITHM ///
+ //////////////////////////////
+ // If we get here, it means we couldn't find a suitable ragdoll to remove,
+ // so just remove the furthest one.
+ int furthestOne = m_LRU.Head();
+ float furthestDistSq = 0;
+#ifdef CLIENT_DLL
+ C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
+#else
+ CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
+#endif
+
+ if (pPlayer && m_LRU.Count() > iMaxRagdollCount) // find the furthest one algorithm
+ {
+ Vector PlayerOrigin = pPlayer->GetAbsOrigin();
+ // const CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
+
+ for ( i = m_LRU.Head(); i < m_LRU.InvalidIndex(); i = next )
+ {
+ CBaseAnimating *pRagdoll = m_LRU[i].Get();
+
+ next = m_LRU.Next(i);
+ IPhysicsObject *pObject = pRagdoll->VPhysicsGetObject();
+ if ( pRagdoll && (pRagdoll->GetEffectEntity() || ( pObject && !pObject->IsAsleep()) ) )
+ continue;
+
+ if ( pRagdoll )
+ {
+ // float distToPlayer = (pPlayer->GetAbsOrigin() - pRagdoll->GetAbsOrigin()).LengthSqr();
+ float distToPlayer = (PlayerOrigin - pRagdoll->GetAbsOrigin()).LengthSqr();
+
+ if (distToPlayer > furthestDistSq)
+ {
+ furthestOne = i;
+ furthestDistSq = distToPlayer;
+ }
+ }
+ else // delete bad rags first.
+ {
+ furthestOne = i;
+ break;
+ }
+ }
+
+#ifdef CLIENT_DLL
+ m_LRU[ furthestOne ]->SUB_Remove();
+#else
+ m_LRU[ furthestOne ]->SUB_StartFadeOut( 0 );
+#endif
+
+ }
+ else // fall back on old-style pick the oldest one algorithm
+ {
+ for ( i = m_LRU.Head(); i < m_LRU.InvalidIndex(); i = next )
+ {
+ if ( m_LRU.Count() <= iMaxRagdollCount )
+ break;
+
+ next = m_LRU.Next(i);
+
+ CBaseAnimating *pRagdoll = m_LRU[i].Get();
+
+ //Just ignore it until we're done burning/dissolving.
+ IPhysicsObject *pObject = pRagdoll->VPhysicsGetObject();
+ if ( pRagdoll && (pRagdoll->GetEffectEntity() || ( pObject && !pObject->IsAsleep()) ) )
+ continue;
+
+ #ifdef CLIENT_DLL
+ m_LRU[ i ]->SUB_Remove();
+ #else
+ m_LRU[ i ]->SUB_StartFadeOut( 0 );
+ #endif
+ m_LRU.Remove(i);
+ }
+ }
+}
+
+#else
+
+void CRagdollLRURetirement::Update( float frametime ) // Non-episodic version
+{
+ VPROF( "CRagdollLRURetirement::Update" );
+ // Compress out dead items
+ int i, next;
+
+ int iMaxRagdollCount = m_iMaxRagdolls;
+
+ if ( iMaxRagdollCount == -1 )
+ {
+ iMaxRagdollCount = g_ragdoll_maxcount.GetInt();
+ }
+
+ // fade them all for the low violence version
+ if ( g_RagdollLVManager.IsLowViolence() )
+ {
+ iMaxRagdollCount = 0;
+ }
+ m_iRagdollCount = 0;
+ m_iSimulatedRagdollCount = 0;
+
+ for ( i = m_LRU.Head(); i < m_LRU.InvalidIndex(); i = next )
+ {
+ next = m_LRU.Next(i);
+ CBaseAnimating *pRagdoll = m_LRU[i].Get();
+ if ( pRagdoll )
+ {
+ m_iRagdollCount++;
+ IPhysicsObject *pObject = pRagdoll->VPhysicsGetObject();
+ if (pObject && !pObject->IsAsleep())
+ {
+ m_iSimulatedRagdollCount++;
+ }
+ if ( m_LRU.Count() > iMaxRagdollCount )
+ {
+ //Found one, we're done.
+ if ( ShouldRemoveThisRagdoll( m_LRU[i] ) == true )
+ {
+#ifdef CLIENT_DLL
+ m_LRU[ i ]->SUB_Remove();
+#else
+ m_LRU[ i ]->SUB_StartFadeOut( 0 );
+#endif
+
+ m_LRU.Remove(i);
+ return;
+ }
+ }
+ }
+ else
+ {
+ m_LRU.Remove(i);
+ }
+ }
+
+
+ //////////////////////////////
+ /// ORIGINAL ALGORITHM ///
+ //////////////////////////////
+ // not episodic -- this is the original mechanism
+
+ for ( i = m_LRU.Head(); i < m_LRU.InvalidIndex(); i = next )
+ {
+ if ( m_LRU.Count() <= iMaxRagdollCount )
+ break;
+
+ next = m_LRU.Next(i);
+
+ CBaseAnimating *pRagdoll = m_LRU[i].Get();
+
+ //Just ignore it until we're done burning/dissolving.
+ if ( pRagdoll && pRagdoll->GetEffectEntity() )
+ continue;
+
+#ifdef CLIENT_DLL
+ m_LRU[ i ]->SUB_Remove();
+#else
+ m_LRU[ i ]->SUB_StartFadeOut( 0 );
+#endif
+ m_LRU.Remove(i);
+ }
+}
+
+#endif // HL2_EPISODIC
+
+//This is pretty hacky, it's only called on the server so it just calls the update method.
+void CRagdollLRURetirement::FrameUpdatePostEntityThink( void )
+{
+ Update( 0 );
+}
+
+ConVar g_ragdoll_important_maxcount( "g_ragdoll_important_maxcount", "2", FCVAR_REPLICATED );
+
+//-----------------------------------------------------------------------------
+// Move it to the top of the LRU
+//-----------------------------------------------------------------------------
+void CRagdollLRURetirement::MoveToTopOfLRU( CBaseAnimating *pRagdoll, bool bImportant )
+{
+ if ( bImportant )
+ {
+ m_LRUImportantRagdolls.AddToTail( pRagdoll );
+
+ if ( m_LRUImportantRagdolls.Count() > g_ragdoll_important_maxcount.GetInt() )
+ {
+ int iIndex = m_LRUImportantRagdolls.Head();
+
+ CBaseAnimating *pRagdoll = m_LRUImportantRagdolls[iIndex].Get();
+
+ if ( pRagdoll )
+ {
+#ifdef CLIENT_DLL
+ pRagdoll->SUB_Remove();
+#else
+ pRagdoll->SUB_StartFadeOut( 0 );
+#endif
+ m_LRUImportantRagdolls.Remove(iIndex);
+ }
+
+ }
+ return;
+ }
+ for ( int i = m_LRU.Head(); i < m_LRU.InvalidIndex(); i = m_LRU.Next(i) )
+ {
+ if ( m_LRU[i].Get() == pRagdoll )
+ {
+ m_LRU.Remove(i);
+ break;
+ }
+ }
+
+ m_LRU.AddToTail( pRagdoll );
+}
+
+
+//EFFECT/ENTITY TRANSFERS
+
+//CLIENT
+#ifdef CLIENT_DLL
+
+#define DEFAULT_FADE_START 2.0f
+#define DEFAULT_MODEL_FADE_START 1.9f
+#define DEFAULT_MODEL_FADE_LENGTH 0.1f
+#define DEFAULT_FADEIN_LENGTH 1.0f
+
+
+
+C_EntityDissolve *DissolveEffect( C_BaseEntity *pTarget, float flTime )
+{
+ C_EntityDissolve *pDissolve = new C_EntityDissolve;
+
+ if ( pDissolve->InitializeAsClientEntity( "sprites/blueglow1.vmt", RENDER_GROUP_TRANSLUCENT_ENTITY ) == false )
+ {
+ pDissolve->Release();
+ return NULL;
+ }
+
+ if ( pDissolve != NULL )
+ {
+ pTarget->AddFlag( FL_DISSOLVING );
+ pDissolve->SetParent( pTarget );
+ pDissolve->OnDataChanged( DATA_UPDATE_CREATED );
+ pDissolve->SetAbsOrigin( pTarget->GetAbsOrigin() );
+
+ pDissolve->m_flStartTime = flTime;
+ pDissolve->m_flFadeOutStart = DEFAULT_FADE_START;
+ pDissolve->m_flFadeOutModelStart = DEFAULT_MODEL_FADE_START;
+ pDissolve->m_flFadeOutModelLength = DEFAULT_MODEL_FADE_LENGTH;
+ pDissolve->m_flFadeInLength = DEFAULT_FADEIN_LENGTH;
+
+ pDissolve->m_nDissolveType = 0;
+ pDissolve->m_flNextSparkTime = 0.0f;
+ pDissolve->m_flFadeOutLength = 0.0f;
+ pDissolve->m_flFadeInStart = 0.0f;
+
+ // Let this entity know it needs to delete itself when it's done
+ pDissolve->SetServerLinkState( false );
+ pTarget->SetEffectEntity( pDissolve );
+ }
+
+ return pDissolve;
+
+}
+
+C_EntityFlame *FireEffect( C_BaseAnimating *pTarget, C_BaseEntity *pServerFire, float *flScaleEnd, float *flTimeStart, float *flTimeEnd )
+{
+ C_EntityFlame *pFire = new C_EntityFlame;
+
+ if ( pFire->InitializeAsClientEntity( NULL, RENDER_GROUP_TRANSLUCENT_ENTITY ) == false )
+ {
+ pFire->Release();
+ return NULL;
+ }
+
+ if ( pFire != NULL )
+ {
+ pFire->RemoveFromLeafSystem();
+
+ pTarget->AddFlag( FL_ONFIRE );
+ pFire->SetParent( pTarget );
+ pFire->m_hEntAttached = (C_BaseEntity *) pTarget;
+
+ pFire->OnDataChanged( DATA_UPDATE_CREATED );
+ pFire->SetAbsOrigin( pTarget->GetAbsOrigin() );
+
+#ifdef HL2_EPISODIC
+ if ( pServerFire )
+ {
+ if ( pServerFire->IsEffectActive(EF_DIMLIGHT) )
+ {
+ pFire->AddEffects( EF_DIMLIGHT );
+ }
+ if ( pServerFire->IsEffectActive(EF_BRIGHTLIGHT) )
+ {
+ pFire->AddEffects( EF_BRIGHTLIGHT );
+ }
+ }
+#endif
+
+ //Play a sound
+ CPASAttenuationFilter filter( pTarget );
+ pTarget->EmitSound( filter, pTarget->GetSoundSourceIndex(), "General.BurningFlesh" );
+
+ pFire->SetNextClientThink( gpGlobals->curtime + 7.0f );
+ }
+
+ return pFire;
+}
+
+void C_BaseAnimating::IgniteRagdoll( C_BaseAnimating *pSource )
+{
+ C_BaseEntity *pChild = pSource->GetEffectEntity();
+
+ if ( pChild )
+ {
+ C_EntityFlame *pFireChild = dynamic_cast<C_EntityFlame *>( pChild );
+ C_ClientRagdoll *pRagdoll = dynamic_cast< C_ClientRagdoll * > ( this );
+
+ if ( pFireChild )
+ {
+ pRagdoll->SetEffectEntity ( FireEffect( pRagdoll, pFireChild, NULL, NULL, NULL ) );
+ }
+ }
+}
+
+
+
+void C_BaseAnimating::TransferDissolveFrom( C_BaseAnimating *pSource )
+{
+ C_BaseEntity *pChild = pSource->GetEffectEntity();
+
+ if ( pChild )
+ {
+ C_EntityDissolve *pDissolveChild = dynamic_cast<C_EntityDissolve *>( pChild );
+
+ if ( pDissolveChild )
+ {
+ C_ClientRagdoll *pRagdoll = dynamic_cast< C_ClientRagdoll * > ( this );
+
+ if ( pRagdoll )
+ {
+ pRagdoll->m_flEffectTime = pDissolveChild->m_flStartTime;
+
+ C_EntityDissolve *pDissolve = DissolveEffect( pRagdoll, pRagdoll->m_flEffectTime );
+
+ if ( pDissolve )
+ {
+ pDissolve->SetRenderMode( pDissolveChild->GetRenderMode() );
+ pDissolve->m_nRenderFX = pDissolveChild->m_nRenderFX;
+ pDissolve->SetRenderColor( 255, 255, 255, 255 );
+ pDissolveChild->SetRenderColorA( 0 );
+
+ pDissolve->m_vDissolverOrigin = pDissolveChild->m_vDissolverOrigin;
+ pDissolve->m_nDissolveType = pDissolveChild->m_nDissolveType;
+
+ if ( pDissolve->m_nDissolveType == ENTITY_DISSOLVE_CORE )
+ {
+ pDissolve->m_nMagnitude = pDissolveChild->m_nMagnitude;
+ pDissolve->m_flFadeOutStart = CORE_DISSOLVE_FADE_START;
+ pDissolve->m_flFadeOutModelStart = CORE_DISSOLVE_MODEL_FADE_START;
+ pDissolve->m_flFadeOutModelLength = CORE_DISSOLVE_MODEL_FADE_LENGTH;
+ pDissolve->m_flFadeInLength = CORE_DISSOLVE_FADEIN_LENGTH;
+ }
+ }
+ }
+ }
+ }
+}
+
+#endif
+
+//SERVER
+#if !defined( CLIENT_DLL )
+
+//-----------------------------------------------------------------------------
+// Transfer dissolve
+//-----------------------------------------------------------------------------
+void CBaseAnimating::TransferDissolveFrom( CBaseAnimating *pAnim )
+{
+ if ( !pAnim || !pAnim->IsDissolving() )
+ return;
+
+ CEntityDissolve *pDissolve = CEntityDissolve::Create( this, pAnim );
+ if (pDissolve)
+ {
+ AddFlag( FL_DISSOLVING );
+ m_flDissolveStartTime = pAnim->m_flDissolveStartTime;
+
+ CEntityDissolve *pDissolveFrom = dynamic_cast < CEntityDissolve * > (pAnim->GetEffectEntity());
+
+ if ( pDissolveFrom )
+ {
+ pDissolve->SetDissolverOrigin( pDissolveFrom->GetDissolverOrigin() );
+ pDissolve->SetDissolveType( pDissolveFrom->GetDissolveType() );
+
+ if ( pDissolveFrom->GetDissolveType() == ENTITY_DISSOLVE_CORE )
+ {
+ pDissolve->SetMagnitude( pDissolveFrom->GetMagnitude() );
+ pDissolve->m_flFadeOutStart = CORE_DISSOLVE_FADE_START;
+ pDissolve->m_flFadeOutModelStart = CORE_DISSOLVE_MODEL_FADE_START;
+ pDissolve->m_flFadeOutModelLength = CORE_DISSOLVE_MODEL_FADE_LENGTH;
+ pDissolve->m_flFadeInLength = CORE_DISSOLVE_FADEIN_LENGTH;
+ }
+ }
+ }
+}
+
+#endif