source-engine/game/shared/ragdoll_shared.cpp

1267 lines
34 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= 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 && g_pGameRules->IsMultiplayer() )
2020-04-22 16:56:21 +00:00
{
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" ) )
{
2022-03-01 20:00:42 +00:00
IPhysicsCollisionSet *pSet = physics->FindOrCreateCollisionSet( modelIndex, ragdoll.listCount );
CRagdollCollisionRules rules(pSet);
2020-04-22 16:56:21 +00:00
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;
2022-03-01 20:00:42 +00:00
2020-04-22 16:56:21 +00:00
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() )
{
2022-03-01 20:00:42 +00:00
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 );
2020-04-22 16:56:21 +00:00
}
return true;
}
else if( engine->CullBox( vMins + origin, vMaxs + origin ) == true )
{
if ( g_debug_ragdoll_removal.GetBool() )
{
2022-03-01 20:00:42 +00:00
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 );
2020-04-22 16:56:21 +00:00
}
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();
2022-03-01 20:00:42 +00:00
CBaseAnimating *pRagdoll = m_LRUImportantRagdolls[iIndex].Get();
2020-04-22 16:56:21 +00:00
2022-03-01 20:00:42 +00:00
if ( pRagdoll )
2020-04-22 16:56:21 +00:00
{
#ifdef CLIENT_DLL
2022-03-01 20:00:42 +00:00
pRagdoll->SUB_Remove();
2020-04-22 16:56:21 +00:00
#else
2022-03-01 20:00:42 +00:00
pRagdoll->SUB_StartFadeOut( 0 );
2020-04-22 16:56:21 +00:00
#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