From 39ed87570bdb2f86969d4be821c94b722dc71179 Mon Sep 17 00:00:00 2001 From: Joe Ludwig Date: Wed, 26 Jun 2013 15:22:04 -0700 Subject: First version of the SOurce SDK 2013 --- mp/src/game/shared/ragdoll_shared.cpp | 1266 +++++++++++++++++++++++++++++++++ 1 file changed, 1266 insertions(+) create mode 100644 mp/src/game/shared/ragdoll_shared.cpp (limited to 'mp/src/game/shared/ragdoll_shared.cpp') 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 ¶ms, 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 ¶ms, 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 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 ¶ms ) +{ + 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 ¶ms, 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( 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( 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 -- cgit v1.2.3