Permalink
| //========= 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<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 ¶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<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 |