diff options
| author | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:31:46 -0800 |
|---|---|---|
| committer | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:46:31 -0800 |
| commit | f56bb35301836e56582a575a75864392a0177875 (patch) | |
| tree | de61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/physics_prop_ragdoll.cpp | |
| parent | Mark some more files as text. (diff) | |
| download | source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip | |
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/server/physics_prop_ragdoll.cpp')
| -rw-r--r-- | mp/src/game/server/physics_prop_ragdoll.cpp | 3426 |
1 files changed, 1713 insertions, 1713 deletions
diff --git a/mp/src/game/server/physics_prop_ragdoll.cpp b/mp/src/game/server/physics_prop_ragdoll.cpp index f9d8c8f8..72635ea8 100644 --- a/mp/src/game/server/physics_prop_ragdoll.cpp +++ b/mp/src/game/server/physics_prop_ragdoll.cpp @@ -1,1713 +1,1713 @@ -//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-//=============================================================================//
-
-#include "cbase.h"
-#include "baseanimating.h"
-#include "studio.h"
-#include "physics.h"
-#include "physics_saverestore.h"
-#include "ai_basenpc.h"
-#include "vphysics/constraints.h"
-#include "datacache/imdlcache.h"
-#include "bone_setup.h"
-#include "physics_prop_ragdoll.h"
-#include "KeyValues.h"
-#include "props.h"
-#include "RagdollBoogie.h"
-#include "AI_Criteria.h"
-#include "ragdoll_shared.h"
-#include "hierarchy.h"
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-//-----------------------------------------------------------------------------
-// Forward declarations
-//-----------------------------------------------------------------------------
-const char *GetMassEquivalent(float flMass);
-
-#define RAGDOLL_VISUALIZE 0
-
-//-----------------------------------------------------------------------------
-// ThinkContext
-//-----------------------------------------------------------------------------
-const char *s_pFadeOutContext = "RagdollFadeOutContext";
-const char *s_pDebrisContext = "DebrisContext";
-
-const float ATTACHED_DAMPING_SCALE = 50.0f;
-
-//-----------------------------------------------------------------------------
-// Spawnflags
-//-----------------------------------------------------------------------------
-#define SF_RAGDOLLPROP_DEBRIS 0x0004
-#define SF_RAGDOLLPROP_USE_LRU_RETIREMENT 0x1000
-#define SF_RAGDOLLPROP_ALLOW_DISSOLVE 0x2000 // Allow this prop to be dissolved
-#define SF_RAGDOLLPROP_MOTIONDISABLED 0x4000
-#define SF_RAGDOLLPROP_ALLOW_STRETCH 0x8000
-#define SF_RAGDOLLPROP_STARTASLEEP 0x10000
-
-//-----------------------------------------------------------------------------
-// Networking
-//-----------------------------------------------------------------------------
-LINK_ENTITY_TO_CLASS( physics_prop_ragdoll, CRagdollProp );
-LINK_ENTITY_TO_CLASS( prop_ragdoll, CRagdollProp );
-EXTERN_SEND_TABLE(DT_Ragdoll)
-
-IMPLEMENT_SERVERCLASS_ST(CRagdollProp, DT_Ragdoll)
- SendPropArray (SendPropQAngles(SENDINFO_ARRAY(m_ragAngles), 13, 0 ), m_ragAngles),
- SendPropArray (SendPropVector(SENDINFO_ARRAY(m_ragPos), -1, SPROP_COORD ), m_ragPos),
- SendPropEHandle(SENDINFO( m_hUnragdoll ) ),
- SendPropFloat(SENDINFO(m_flBlendWeight), 8, SPROP_ROUNDDOWN, 0.0f, 1.0f ),
- SendPropInt(SENDINFO(m_nOverlaySequence), 11),
-END_SEND_TABLE()
-
-#define DEFINE_RAGDOLL_ELEMENT( i ) \
- DEFINE_FIELD( m_ragdoll.list[i].originParentSpace, FIELD_VECTOR ), \
- DEFINE_PHYSPTR( m_ragdoll.list[i].pObject ), \
- DEFINE_PHYSPTR( m_ragdoll.list[i].pConstraint ), \
- DEFINE_FIELD( m_ragdoll.list[i].parentIndex, FIELD_INTEGER )
-
-BEGIN_DATADESC(CRagdollProp)
-// m_ragdoll (custom handling)
- DEFINE_AUTO_ARRAY ( m_ragdoll.boneIndex, FIELD_INTEGER ),
- DEFINE_AUTO_ARRAY ( m_ragPos, FIELD_POSITION_VECTOR ),
- DEFINE_AUTO_ARRAY ( m_ragAngles, FIELD_VECTOR ),
- DEFINE_KEYFIELD(m_anglesOverrideString, FIELD_STRING, "angleOverride" ),
- DEFINE_FIELD( m_lastUpdateTickCount, FIELD_INTEGER ),
- DEFINE_FIELD( m_allAsleep, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_hDamageEntity, FIELD_EHANDLE ),
- DEFINE_FIELD( m_hKiller, FIELD_EHANDLE ),
-
- DEFINE_KEYFIELD( m_bStartDisabled, FIELD_BOOLEAN, "StartDisabled" ),
-
- DEFINE_INPUTFUNC( FIELD_VOID, "StartRagdollBoogie", InputStartRadgollBoogie ),
- DEFINE_INPUTFUNC( FIELD_VOID, "EnableMotion", InputEnableMotion ),
- DEFINE_INPUTFUNC( FIELD_VOID, "DisableMotion", InputDisableMotion ),
- DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputTurnOn ),
- DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputTurnOff ),
- DEFINE_INPUTFUNC( FIELD_FLOAT, "FadeAndRemove", InputFadeAndRemove ),
-
- DEFINE_FIELD( m_hUnragdoll, FIELD_EHANDLE ),
- DEFINE_FIELD( m_bFirstCollisionAfterLaunch, FIELD_BOOLEAN ),
-
- DEFINE_FIELD( m_flBlendWeight, FIELD_FLOAT ),
- DEFINE_FIELD( m_nOverlaySequence, FIELD_INTEGER ),
- DEFINE_AUTO_ARRAY( m_ragdollMins, FIELD_VECTOR ),
- DEFINE_AUTO_ARRAY( m_ragdollMaxs, FIELD_VECTOR ),
-
- // Physics Influence
- DEFINE_FIELD( m_hPhysicsAttacker, FIELD_EHANDLE ),
- DEFINE_FIELD( m_flLastPhysicsInfluenceTime, FIELD_TIME ),
- DEFINE_FIELD( m_flFadeOutStartTime, FIELD_TIME ),
- DEFINE_FIELD( m_flFadeTime, FIELD_FLOAT),
- DEFINE_FIELD( m_strSourceClassName, FIELD_STRING ),
- DEFINE_FIELD( m_bHasBeenPhysgunned, FIELD_BOOLEAN ),
-
- // think functions
- DEFINE_THINKFUNC( SetDebrisThink ),
- DEFINE_THINKFUNC( ClearFlagsThink ),
- DEFINE_THINKFUNC( FadeOutThink ),
-
- DEFINE_FIELD( m_ragdoll.listCount, FIELD_INTEGER ),
- DEFINE_FIELD( m_ragdoll.allowStretch, FIELD_BOOLEAN ),
- DEFINE_PHYSPTR( m_ragdoll.pGroup ),
- DEFINE_FIELD( m_flDefaultFadeScale, FIELD_FLOAT ),
-
- //DEFINE_RAGDOLL_ELEMENT( 0 ),
- DEFINE_RAGDOLL_ELEMENT( 1 ),
- DEFINE_RAGDOLL_ELEMENT( 2 ),
- DEFINE_RAGDOLL_ELEMENT( 3 ),
- DEFINE_RAGDOLL_ELEMENT( 4 ),
- DEFINE_RAGDOLL_ELEMENT( 5 ),
- DEFINE_RAGDOLL_ELEMENT( 6 ),
- DEFINE_RAGDOLL_ELEMENT( 7 ),
- DEFINE_RAGDOLL_ELEMENT( 8 ),
- DEFINE_RAGDOLL_ELEMENT( 9 ),
- DEFINE_RAGDOLL_ELEMENT( 10 ),
- DEFINE_RAGDOLL_ELEMENT( 11 ),
- DEFINE_RAGDOLL_ELEMENT( 12 ),
- DEFINE_RAGDOLL_ELEMENT( 13 ),
- DEFINE_RAGDOLL_ELEMENT( 14 ),
- DEFINE_RAGDOLL_ELEMENT( 15 ),
- DEFINE_RAGDOLL_ELEMENT( 16 ),
- DEFINE_RAGDOLL_ELEMENT( 17 ),
- DEFINE_RAGDOLL_ELEMENT( 18 ),
- DEFINE_RAGDOLL_ELEMENT( 19 ),
- DEFINE_RAGDOLL_ELEMENT( 20 ),
- DEFINE_RAGDOLL_ELEMENT( 21 ),
- DEFINE_RAGDOLL_ELEMENT( 22 ),
- DEFINE_RAGDOLL_ELEMENT( 23 ),
-
-END_DATADESC()
-
-//-----------------------------------------------------------------------------
-// Disable auto fading under dx7 or when level fades are specified
-//-----------------------------------------------------------------------------
-void CRagdollProp::DisableAutoFade()
-{
- m_flFadeScale = 0;
- m_flDefaultFadeScale = 0;
-}
-
-
-void CRagdollProp::Spawn( void )
-{
- // Starts out as the default fade scale value
- m_flDefaultFadeScale = m_flFadeScale;
-
- // NOTE: If this fires, then the assert or the datadesc is wrong! (see DEFINE_RAGDOLL_ELEMENT above)
- Assert( RAGDOLL_MAX_ELEMENTS == 24 );
- Precache();
- SetModel( STRING( GetModelName() ) );
-
- CStudioHdr *pStudioHdr = GetModelPtr( );
- if ( pStudioHdr->flags() & STUDIOHDR_FLAGS_NO_FORCED_FADE )
- {
- DisableAutoFade();
- }
- else
- {
- m_flFadeScale = m_flDefaultFadeScale;
- }
-
- matrix3x4_t pBoneToWorld[MAXSTUDIOBONES];
- BaseClass::SetupBones( pBoneToWorld, BONE_USED_BY_ANYTHING ); // FIXME: shouldn't this be a subset of the bones
- // this is useless info after the initial conditions are set
- SetAbsAngles( vec3_angle );
- int collisionGroup = (m_spawnflags & SF_RAGDOLLPROP_DEBRIS) ? COLLISION_GROUP_DEBRIS : COLLISION_GROUP_NONE;
- bool bWake = (m_spawnflags & SF_RAGDOLLPROP_STARTASLEEP) ? false : true;
- InitRagdoll( vec3_origin, 0, vec3_origin, pBoneToWorld, pBoneToWorld, 0, collisionGroup, true, bWake );
- m_lastUpdateTickCount = 0;
- m_flBlendWeight = 0.0f;
- m_nOverlaySequence = -1;
-
- // Unless specified, do not allow this to be dissolved
- if ( HasSpawnFlags( SF_RAGDOLLPROP_ALLOW_DISSOLVE ) == false )
- {
- AddEFlags( EFL_NO_DISSOLVE );
- }
-
- if ( HasSpawnFlags(SF_RAGDOLLPROP_MOTIONDISABLED) )
- {
- DisableMotion();
- }
-
- if( m_bStartDisabled )
- {
- AddEffects( EF_NODRAW );
- }
-}
-
-void CRagdollProp::SetSourceClassName( const char *pClassname )
-{
- m_strSourceClassName = MAKE_STRING( pClassname );
-}
-
-
-void CRagdollProp::OnSave( IEntitySaveUtils *pUtils )
-{
- if ( !m_ragdoll.listCount )
- return;
-
- // Don't save ragdoll element 0, base class saves the pointer in
- // m_pPhysicsObject
- Assert( m_ragdoll.list[0].parentIndex == -1 );
- Assert( m_ragdoll.list[0].pConstraint == NULL );
- Assert( m_ragdoll.list[0].originParentSpace == vec3_origin );
- Assert( m_ragdoll.list[0].pObject != NULL );
- VPhysicsSetObject( NULL ); // squelch a warning message
- VPhysicsSetObject( m_ragdoll.list[0].pObject ); // make sure object zero is saved by CBaseEntity
- BaseClass::OnSave( pUtils );
-}
-
-void CRagdollProp::OnRestore()
-{
- // rebuild element 0 since it isn't saved
- // NOTE: This breaks the rules - the pointer needs to get fixed in Restore()
- m_ragdoll.list[0].pObject = VPhysicsGetObject();
- m_ragdoll.list[0].parentIndex = -1;
- m_ragdoll.list[0].originParentSpace.Init();
-
- BaseClass::OnRestore();
- if ( !m_ragdoll.listCount )
- return;
-
- // JAY: Reset collision relationships
- RagdollSetupCollisions( m_ragdoll, modelinfo->GetVCollide( GetModelIndex() ), GetModelIndex() );
- VPhysicsUpdate( VPhysicsGetObject() );
-}
-
-void CRagdollProp::CalcRagdollSize( void )
-{
- CollisionProp()->SetSurroundingBoundsType( USE_HITBOXES );
- CollisionProp()->RemoveSolidFlags( FSOLID_FORCE_WORLD_ALIGNED );
-}
-
-void CRagdollProp::UpdateOnRemove( void )
-{
- for ( int i = 0; i < m_ragdoll.listCount; i++ )
- {
- if ( m_ragdoll.list[i].pObject )
- {
- g_pPhysSaveRestoreManager->ForgetModel( m_ragdoll.list[i].pObject );
- }
- }
-
- // Set to null so that the destructor's call to DestroyObject won't destroy
- // m_pObjects[ 0 ] twice since that's the physics object for the prop
- VPhysicsSetObject( NULL );
-
- RagdollDestroy( m_ragdoll );
- // Chain to base after doing our own cleanup to mimic
- // destructor unwind order
- BaseClass::UpdateOnRemove();
-}
-
-CRagdollProp::CRagdollProp( void )
-{
- m_strSourceClassName = NULL_STRING;
- m_anglesOverrideString = NULL_STRING;
- m_ragdoll.listCount = 0;
- Assert( (1<<RAGDOLL_INDEX_BITS) >=RAGDOLL_MAX_ELEMENTS );
- m_allAsleep = false;
- m_flFadeScale = 1;
- m_flDefaultFadeScale = 1;
-}
-
-CRagdollProp::~CRagdollProp( void )
-{
-}
-
-void CRagdollProp::Precache( void )
-{
- PrecacheModel( STRING( GetModelName() ) );
- BaseClass::Precache();
-}
-
-int CRagdollProp::ObjectCaps()
-{
- return BaseClass::ObjectCaps() | FCAP_WCEDIT_POSITION;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CRagdollProp::InitRagdollAnimation()
-{
- m_flAnimTime = gpGlobals->curtime;
- m_flPlaybackRate = 0.0;
- SetCycle( 0 );
-
- // put into ACT_DIERAGDOLL if it exists, otherwise use sequence 0
- int nSequence = SelectWeightedSequence( ACT_DIERAGDOLL );
- if ( nSequence < 0 )
- {
- ResetSequence( 0 );
- }
- else
- {
- ResetSequence( nSequence );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Response system stuff
-//-----------------------------------------------------------------------------
-IResponseSystem *CRagdollProp::GetResponseSystem()
-{
- extern IResponseSystem *g_pResponseSystem;
-
- // Just use the general NPC response system; we often come from NPCs after all
- return g_pResponseSystem;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CRagdollProp::ModifyOrAppendCriteria( AI_CriteriaSet& set )
-{
- BaseClass::ModifyOrAppendCriteria( set );
-
- if ( m_strSourceClassName != NULL_STRING )
- {
- set.RemoveCriteria( "classname" );
- set.AppendCriteria( "classname", STRING(m_strSourceClassName) );
- set.AppendCriteria( "ragdoll", "1" );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CRagdollProp::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
-{
- m_hPhysicsAttacker = pPhysGunUser;
- m_flLastPhysicsInfluenceTime = gpGlobals->curtime;
-
- // Clear out the classname if we've been physgunned before
- // so that the screams, etc. don't happen. Simulate that the first
- // major punt or throw has been enough to kill him.
- if ( m_bHasBeenPhysgunned )
- {
- m_strSourceClassName = NULL_STRING;
- }
- m_bHasBeenPhysgunned = true;
-
- if( HasPhysgunInteraction( "onpickup", "boogie" ) )
- {
- if ( reason == PUNTED_BY_CANNON )
- {
- CRagdollBoogie::Create( this, 150, gpGlobals->curtime, 3.0f, SF_RAGDOLL_BOOGIE_ELECTRICAL );
- }
- else
- {
- CRagdollBoogie::Create( this, 150, gpGlobals->curtime, 2.0f, 0.0f );
- }
- }
-
- if ( HasSpawnFlags( SF_RAGDOLLPROP_USE_LRU_RETIREMENT ) )
- {
- s_RagdollLRU.MoveToTopOfLRU( this );
- }
-
- if ( !HasSpawnFlags( SF_PHYSPROP_ENABLE_ON_PHYSCANNON ) )
- return;
-
- ragdoll_t *pRagdollPhys = GetRagdoll( );
- for ( int j = 0; j < pRagdollPhys->listCount; ++j )
- {
- pRagdollPhys->list[j].pObject->Wake();
- pRagdollPhys->list[j].pObject->EnableMotion( true );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CRagdollProp::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason )
-{
- CDefaultPlayerPickupVPhysics::OnPhysGunDrop( pPhysGunUser, Reason );
- m_hPhysicsAttacker = pPhysGunUser;
- m_flLastPhysicsInfluenceTime = gpGlobals->curtime;
-
- if( HasPhysgunInteraction( "onpickup", "boogie" ) )
- {
- CRagdollBoogie::Create( this, 150, gpGlobals->curtime, 3.0f, SF_RAGDOLL_BOOGIE_ELECTRICAL );
- }
-
- if ( HasSpawnFlags( SF_RAGDOLLPROP_USE_LRU_RETIREMENT ) )
- {
- s_RagdollLRU.MoveToTopOfLRU( this );
- }
-
- // Make sure it's interactive debris for at most 5 seconds
- if ( GetCollisionGroup() == COLLISION_GROUP_INTERACTIVE_DEBRIS )
- {
- SetContextThink( &CRagdollProp::SetDebrisThink, gpGlobals->curtime + 5, s_pDebrisContext );
- }
-
- if ( Reason != LAUNCHED_BY_CANNON )
- return;
-
- if( HasPhysgunInteraction( "onlaunch", "spin_zaxis" ) )
- {
- Vector vecAverageCenter( 0, 0, 0 );
-
- // Get the average position, apply forces to produce a spin
- int j;
- ragdoll_t *pRagdollPhys = GetRagdoll( );
- for ( j = 0; j < pRagdollPhys->listCount; ++j )
- {
- Vector vecCenter;
- pRagdollPhys->list[j].pObject->GetPosition( &vecCenter, NULL );
- vecAverageCenter += vecCenter;
- }
-
- vecAverageCenter /= pRagdollPhys->listCount;
-
- Vector vecZAxis( 0, 0, 1 );
- for ( j = 0; j < pRagdollPhys->listCount; ++j )
- {
- Vector vecDelta;
- pRagdollPhys->list[j].pObject->GetPosition( &vecDelta, NULL );
- vecDelta -= vecAverageCenter;
-
- Vector vecDir;
- CrossProduct( vecZAxis, vecDelta, vecDir );
- vecDir *= 100;
- pRagdollPhys->list[j].pObject->AddVelocity( &vecDir, NULL );
- }
- }
-
- PhysSetGameFlags( VPhysicsGetObject(), FVPHYSICS_WAS_THROWN );
- m_bFirstCollisionAfterLaunch = true;
-}
-
-
-//-----------------------------------------------------------------------------
-// Physics attacker
-//-----------------------------------------------------------------------------
-CBasePlayer *CRagdollProp::HasPhysicsAttacker( float dt )
-{
- if (gpGlobals->curtime - dt <= m_flLastPhysicsInfluenceTime)
- {
- return m_hPhysicsAttacker;
- }
- return NULL;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CRagdollProp::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
-{
- BaseClass::VPhysicsCollision( index, pEvent );
-
- CBaseEntity *pHitEntity = pEvent->pEntities[!index];
- if ( pHitEntity == this )
- return;
-
- // Don't take physics damage from whoever's holding him with the physcannon.
- if ( VPhysicsGetObject() && (VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD) )
- {
- if ( pHitEntity && (pHitEntity == HasPhysicsAttacker( FLT_MAX )) )
- return;
- }
-
- // Don't bother taking damage from the physics attacker
- if ( pHitEntity && HasPhysicsAttacker( 0.5f ) == pHitEntity )
- return;
-
- if( m_bFirstCollisionAfterLaunch )
- {
- HandleFirstCollisionInteractions( index, pEvent );
- }
-
- if ( m_takedamage != DAMAGE_NO )
- {
- int damageType = 0;
- float damage = CalculateDefaultPhysicsDamage( index, pEvent, 1.0f, true, damageType );
- if ( damage > 0 )
- {
- // Take extra damage after we're punted by the physcannon
- if ( m_bFirstCollisionAfterLaunch )
- {
- damage *= 10;
- }
-
- CBaseEntity *pHitEntity = pEvent->pEntities[!index];
- if ( !pHitEntity )
- {
- // hit world
- pHitEntity = GetContainingEntity( INDEXENT(0) );
- }
- Vector damagePos;
- pEvent->pInternalData->GetContactPoint( damagePos );
- Vector damageForce = pEvent->postVelocity[index] * pEvent->pObjects[index]->GetMass();
- if ( damageForce == vec3_origin )
- {
- // This can happen if this entity is motion disabled, and can't move.
- // Use the velocity of the entity that hit us instead.
- damageForce = pEvent->postVelocity[!index] * pEvent->pObjects[!index]->GetMass();
- }
-
- // FIXME: this doesn't pass in who is responsible if some other entity "caused" this collision
- PhysCallbackDamage( this, CTakeDamageInfo( pHitEntity, pHitEntity, damageForce, damagePos, damage, damageType ), *pEvent, index );
- }
- }
-
- if ( m_bFirstCollisionAfterLaunch )
- {
- // Setup the think function to remove the flags
- SetThink( &CRagdollProp::ClearFlagsThink );
- SetNextThink( gpGlobals->curtime );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CRagdollProp::HasPhysgunInteraction( const char *pszKeyName, const char *pszValue )
-{
- KeyValues *modelKeyValues = new KeyValues("");
- if ( modelKeyValues->LoadFromBuffer( modelinfo->GetModelName( GetModel() ), modelinfo->GetModelKeyValueText( GetModel() ) ) )
- {
- KeyValues *pkvPropData = modelKeyValues->FindKey("physgun_interactions");
- if ( pkvPropData )
- {
- char const *pszBase = pkvPropData->GetString( pszKeyName );
-
- if ( pszBase && pszBase[0] && !stricmp( pszBase, pszValue ) )
- {
- modelKeyValues->deleteThis();
- return true;
- }
- }
- }
-
- modelKeyValues->deleteThis();
- return false;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CRagdollProp::HandleFirstCollisionInteractions( int index, gamevcollisionevent_t *pEvent )
-{
- IPhysicsObject *pObj = VPhysicsGetObject();
- if ( !pObj)
- return;
-
- if( HasPhysgunInteraction( "onfirstimpact", "break" ) )
- {
- // Looks like it's best to break by having the object damage itself.
- CTakeDamageInfo info;
-
- info.SetDamage( m_iHealth );
- info.SetAttacker( this );
- info.SetInflictor( this );
- info.SetDamageType( DMG_GENERIC );
-
- Vector vecPosition;
- Vector vecVelocity;
-
- VPhysicsGetObject()->GetVelocity( &vecVelocity, NULL );
- VPhysicsGetObject()->GetPosition( &vecPosition, NULL );
-
- info.SetDamageForce( vecVelocity );
- info.SetDamagePosition( vecPosition );
-
- TakeDamage( info );
- return;
- }
-
- if( HasPhysgunInteraction( "onfirstimpact", "paintsplat" ) )
- {
- IPhysicsObject *pObj = VPhysicsGetObject();
-
- Vector vecPos;
- pObj->GetPosition( &vecPos, NULL );
-
- trace_t tr;
- UTIL_TraceLine( vecPos, vecPos + pEvent->preVelocity[0] * 1.5, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
-
- switch( random->RandomInt( 1, 3 ) )
- {
- case 1:
- UTIL_DecalTrace( &tr, "PaintSplatBlue" );
- break;
-
- case 2:
- UTIL_DecalTrace( &tr, "PaintSplatGreen" );
- break;
-
- case 3:
- UTIL_DecalTrace( &tr, "PaintSplatPink" );
- break;
- }
- }
-
- bool bAlienBloodSplat = HasPhysgunInteraction( "onfirstimpact", "alienbloodsplat" );
- if( bAlienBloodSplat || HasPhysgunInteraction( "onfirstimpact", "bloodsplat" ) )
- {
- IPhysicsObject *pObj = VPhysicsGetObject();
-
- Vector vecPos;
- pObj->GetPosition( &vecPos, NULL );
-
- trace_t tr;
- UTIL_TraceLine( vecPos, vecPos + pEvent->preVelocity[0] * 1.5, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
-
- UTIL_BloodDecalTrace( &tr, bAlienBloodSplat ? BLOOD_COLOR_GREEN : BLOOD_COLOR_RED );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CRagdollProp::ClearFlagsThink( void )
-{
- PhysClearGameFlags( VPhysicsGetObject(), FVPHYSICS_WAS_THROWN );
- m_bFirstCollisionAfterLaunch = false;
- SetThink( NULL );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-AngularImpulse CRagdollProp::PhysGunLaunchAngularImpulse()
-{
- if( HasPhysgunInteraction( "onlaunch", "spin_zaxis" ) )
- {
- // Don't add in random angular impulse if this object is supposed to spin in a specific way.
- AngularImpulse ang( 0, 0, 0 );
- return ang;
- }
-
- return CDefaultPlayerPickupVPhysics::PhysGunLaunchAngularImpulse();
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : activity -
-//-----------------------------------------------------------------------------
-void CRagdollProp::SetOverlaySequence( Activity activity )
-{
- int seq = SelectWeightedSequence( activity );
- if ( seq < 0 )
- {
- m_nOverlaySequence = -1;
- }
- else
- {
- m_nOverlaySequence = seq;
- }
-}
-
-void CRagdollProp::InitRagdoll( const Vector &forceVector, int forceBone, const Vector &forcePos, matrix3x4_t *pPrevBones, matrix3x4_t *pBoneToWorld, float dt, int collisionGroup, bool activateRagdoll, bool bWakeRagdoll )
-{
- SetCollisionGroup( collisionGroup );
-
- // Make sure it's interactive debris for at most 5 seconds
- if ( collisionGroup == COLLISION_GROUP_INTERACTIVE_DEBRIS )
- {
- SetContextThink( &CRagdollProp::SetDebrisThink, gpGlobals->curtime + 5, s_pDebrisContext );
- }
-
- SetMoveType( MOVETYPE_VPHYSICS );
- SetSolid( SOLID_VPHYSICS );
- AddSolidFlags( FSOLID_CUSTOMRAYTEST | FSOLID_CUSTOMBOXTEST );
- m_takedamage = DAMAGE_EVENTS_ONLY;
-
- ragdollparams_t params;
- params.pGameData = static_cast<void *>( static_cast<CBaseEntity *>(this) );
- params.modelIndex = GetModelIndex();
- params.pCollide = modelinfo->GetVCollide( params.modelIndex );
- params.pStudioHdr = GetModelPtr();
- params.forceVector = forceVector;
- params.forceBoneIndex = forceBone;
- params.forcePosition = forcePos;
- params.pCurrentBones = pBoneToWorld;
- params.jointFrictionScale = 1.0;
- params.allowStretch = HasSpawnFlags(SF_RAGDOLLPROP_ALLOW_STRETCH);
- params.fixedConstraints = false;
- RagdollCreate( m_ragdoll, params, physenv );
- RagdollApplyAnimationAsVelocity( m_ragdoll, pPrevBones, pBoneToWorld, dt );
- if ( m_anglesOverrideString != NULL_STRING && Q_strlen(m_anglesOverrideString.ToCStr()) > 0 )
- {
- char szToken[2048];
- const char *pStr = nexttoken(szToken, STRING(m_anglesOverrideString), ',');
- // anglesOverride is index,angles,index,angles (e.g. "1, 22.5 123.0 0.0, 2, 0 0 0, 3, 0 0 180.0")
- while ( szToken[0] != 0 )
- {
- int objectIndex = atoi(szToken);
- // sanity check to make sure this token is an integer
- Assert( atof(szToken) == ((float)objectIndex) );
- pStr = nexttoken(szToken, pStr, ',');
- Assert( szToken[0] );
- if ( objectIndex >= m_ragdoll.listCount )
- {
- Warning("Bad ragdoll pose in entity %s, model (%s) at %s, model changed?\n", GetDebugName(), GetModelName().ToCStr(), VecToString(GetAbsOrigin()) );
- }
- else if ( szToken[0] != 0 )
- {
- QAngle angles;
- Assert( objectIndex >= 0 && objectIndex < RAGDOLL_MAX_ELEMENTS );
- UTIL_StringToVector( angles.Base(), szToken );
- int boneIndex = m_ragdoll.boneIndex[objectIndex];
- AngleMatrix( angles, pBoneToWorld[boneIndex] );
- const ragdollelement_t &element = m_ragdoll.list[objectIndex];
- Vector out;
- if ( element.parentIndex >= 0 )
- {
- int parentBoneIndex = m_ragdoll.boneIndex[element.parentIndex];
- VectorTransform( element.originParentSpace, pBoneToWorld[parentBoneIndex], out );
- }
- else
- {
- out = GetAbsOrigin();
- }
- MatrixSetColumn( out, 3, pBoneToWorld[boneIndex] );
- element.pObject->SetPositionMatrix( pBoneToWorld[boneIndex], true );
- }
- pStr = nexttoken(szToken, pStr, ',');
- }
- }
-
- if ( activateRagdoll )
- {
- MEM_ALLOC_CREDIT();
- RagdollActivate( m_ragdoll, params.pCollide, GetModelIndex(), bWakeRagdoll );
- }
-
- for ( int i = 0; i < m_ragdoll.listCount; i++ )
- {
- UpdateNetworkDataFromVPhysics( m_ragdoll.list[i].pObject, i );
- g_pPhysSaveRestoreManager->AssociateModel( m_ragdoll.list[i].pObject, GetModelIndex() );
- physcollision->CollideGetAABB( &m_ragdollMins[i], &m_ragdollMaxs[i], m_ragdoll.list[i].pObject->GetCollide(), vec3_origin, vec3_angle );
- }
- VPhysicsSetObject( m_ragdoll.list[0].pObject );
-
- CalcRagdollSize();
-}
-
-void CRagdollProp::SetDebrisThink()
-{
- SetCollisionGroup( COLLISION_GROUP_DEBRIS );
- RecheckCollisionFilter();
-}
-
-void CRagdollProp::SetDamageEntity( CBaseEntity *pEntity )
-{
- // Damage passing
- m_hDamageEntity = pEntity;
-
- // Set our takedamage to match it
- if ( pEntity )
- {
- m_takedamage = pEntity->m_takedamage;
- }
- else
- {
- m_takedamage = DAMAGE_EVENTS_ONLY;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-int CRagdollProp::OnTakeDamage( const CTakeDamageInfo &info )
-{
- // If we have a damage entity, we want to pass damage to it. Add the
- // Never Ragdoll flag, on the assumption that if the entity dies, we'll
- // actually be taking the role of its ragdoll.
- if ( m_hDamageEntity.Get() )
- {
- CTakeDamageInfo subInfo = info;
- subInfo.AddDamageType( DMG_REMOVENORAGDOLL );
- return m_hDamageEntity->OnTakeDamage( subInfo );
- }
-
- return BaseClass::OnTakeDamage( info );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Force all the ragdoll's bone's physics objects to recheck their collision filters
-//-----------------------------------------------------------------------------
-void CRagdollProp::RecheckCollisionFilter( void )
-{
- for ( int i = 0; i < m_ragdoll.listCount; i++ )
- {
- m_ragdoll.list[i].pObject->RecheckCollisionFilter();
- }
-}
-
-
-void CRagdollProp::TraceAttack( const CTakeDamageInfo &info, const Vector &dir, trace_t *ptr, CDmgAccumulator *pAccumulator )
-{
- if ( ptr->physicsbone >= 0 && ptr->physicsbone < m_ragdoll.listCount )
- {
- VPhysicsSwapObject( m_ragdoll.list[ptr->physicsbone].pObject );
- }
- BaseClass::TraceAttack( info, dir, ptr, pAccumulator );
-}
-
-void CRagdollProp::SetupBones( matrix3x4_t *pBoneToWorld, int boneMask )
-{
- // no ragdoll, fall through to base class
- if ( !m_ragdoll.listCount )
- {
- BaseClass::SetupBones( pBoneToWorld, boneMask );
- return;
- }
-
- // Not really ideal, but it'll work for now
- UpdateModelScale();
-
- MDLCACHE_CRITICAL_SECTION();
- CStudioHdr *pStudioHdr = GetModelPtr( );
- bool sim[MAXSTUDIOBONES];
- memset( sim, 0, pStudioHdr->numbones() );
-
- int i;
-
- CBoneAccessor boneaccessor( pBoneToWorld );
- for ( i = 0; i < m_ragdoll.listCount; i++ )
- {
- // during restore this may be NULL
- if ( !m_ragdoll.list[i].pObject )
- continue;
-
- if ( RagdollGetBoneMatrix( m_ragdoll, boneaccessor, i ) )
- {
- sim[m_ragdoll.boneIndex[i]] = true;
- }
- }
-
- mstudiobone_t *pbones = pStudioHdr->pBone( 0 );
- for ( i = 0; i < pStudioHdr->numbones(); i++ )
- {
- if ( sim[i] )
- continue;
-
- if ( !(pStudioHdr->boneFlags(i) & boneMask) )
- continue;
-
- matrix3x4_t matBoneLocal;
- AngleMatrix( pbones[i].rot, pbones[i].pos, matBoneLocal );
- ConcatTransforms( pBoneToWorld[pbones[i].parent], matBoneLocal, pBoneToWorld[i]);
- }
-}
-
-bool CRagdollProp::TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace )
-{
-#if 0
- // PERFORMANCE: Use hitboxes for rays instead of vcollides if this is a performance problem
- if ( ray.m_IsRay )
- {
- return BaseClass::TestCollision( ray, mask, trace );
- }
-#endif
-
- CStudioHdr *pStudioHdr = GetModelPtr( );
- if (!pStudioHdr)
- return false;
-
- // Just iterate all of the elements and trace the box against each one.
- // NOTE: This is pretty expensive for small/dense characters
- trace_t tr;
- for ( int i = 0; i < m_ragdoll.listCount; i++ )
- {
- Vector position;
- QAngle angles;
-
- if( m_ragdoll.list[i].pObject )
- {
- m_ragdoll.list[i].pObject->GetPosition( &position, &angles );
- physcollision->TraceBox( ray, m_ragdoll.list[i].pObject->GetCollide(), position, angles, &tr );
-
- if ( tr.fraction < trace.fraction )
- {
- tr.physicsbone = i;
- tr.surface.surfaceProps = m_ragdoll.list[i].pObject->GetMaterialIndex();
- trace = tr;
- }
- }
- else
- {
- DevWarning("Bogus object in Ragdoll Prop's ragdoll list!\n");
- }
- }
-
- if ( trace.fraction >= 1 )
- {
- return false;
- }
-
- return true;
-}
-
-
-void CRagdollProp::Teleport( const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity )
-{
- // newAngles is a relative transform for the entity
- // But a ragdoll entity has identity orientation by design
- // so we compute a relative transform here based on the previous transform
- matrix3x4_t startMatrixInv;
- MatrixInvert( EntityToWorldTransform(), startMatrixInv );
- matrix3x4_t endMatrix;
- MatrixCopy( EntityToWorldTransform(), endMatrix );
- if ( newAngles )
- {
- AngleMatrix( *newAngles, endMatrix );
- }
- if ( newPosition )
- {
- PositionMatrix( *newPosition, endMatrix );
- }
- // now endMatrix is the refernce matrix for the entity at the target position
- matrix3x4_t xform;
- ConcatTransforms( endMatrix, startMatrixInv, xform );
- // now xform is the relative transform the entity must undergo
-
- // we need to call the base class and it will teleport our vphysics object,
- // so set object 0 up and compute the origin/angles for its new position (base implementation has side effects)
- VPhysicsSwapObject( m_ragdoll.list[0].pObject );
- matrix3x4_t obj0source, obj0Target;
- m_ragdoll.list[0].pObject->GetPositionMatrix( &obj0source );
- ConcatTransforms( xform, obj0source, obj0Target );
- Vector obj0Pos;
- QAngle obj0Angles;
- MatrixAngles( obj0Target, obj0Angles, obj0Pos );
- BaseClass::Teleport( &obj0Pos, &obj0Angles, newVelocity );
-
- for ( int i = 1; i < m_ragdoll.listCount; i++ )
- {
- matrix3x4_t matrix, newMatrix;
- m_ragdoll.list[i].pObject->GetPositionMatrix( &matrix );
- ConcatTransforms( xform, matrix, newMatrix );
- m_ragdoll.list[i].pObject->SetPositionMatrix( newMatrix, true );
- UpdateNetworkDataFromVPhysics( m_ragdoll.list[i].pObject, i );
- }
- // fixup/relink object 0
- UpdateNetworkDataFromVPhysics( m_ragdoll.list[0].pObject, 0 );
-}
-
-void CRagdollProp::VPhysicsUpdate( IPhysicsObject *pPhysics )
-{
- if ( m_lastUpdateTickCount == (unsigned int)gpGlobals->tickcount )
- return;
-
- m_lastUpdateTickCount = gpGlobals->tickcount;
- //NetworkStateChanged();
-
- matrix3x4_t boneToWorld[MAXSTUDIOBONES];
- QAngle angles;
- Vector surroundingMins, surroundingMaxs;
-
- int i;
- for ( i = 0; i < m_ragdoll.listCount; i++ )
- {
- CBoneAccessor boneaccessor( boneToWorld );
- if ( RagdollGetBoneMatrix( m_ragdoll, boneaccessor, i ) )
- {
- Vector vNewPos;
- MatrixAngles( boneToWorld[m_ragdoll.boneIndex[i]], angles, vNewPos );
- m_ragPos.Set( i, vNewPos );
- m_ragAngles.Set( i, angles );
- }
- else
- {
- m_ragPos.GetForModify(i).Init();
- m_ragAngles.GetForModify(i).Init();
- }
- }
-
- // BUGBUG: Use the ragdollmins/maxs to do this instead of the collides
- m_allAsleep = RagdollIsAsleep( m_ragdoll );
-
- // Don't scream after you've come to rest
- if ( m_allAsleep )
- {
- m_strSourceClassName = NULL_STRING;
- }
- else
- {
- if ( m_ragdoll.pGroup->IsInErrorState() )
- {
- RagdollSolveSeparation( m_ragdoll, this );
- }
- }
-
- // Interactive debris converts back to debris when it comes to rest
- if ( m_allAsleep && GetCollisionGroup() == COLLISION_GROUP_INTERACTIVE_DEBRIS )
- {
- SetCollisionGroup( COLLISION_GROUP_DEBRIS );
- RecheckCollisionFilter();
- SetContextThink( NULL, gpGlobals->curtime, s_pDebrisContext );
- }
-
- Vector vecFullMins, vecFullMaxs;
- vecFullMins = m_ragPos[0];
- vecFullMaxs = m_ragPos[0];
- for ( i = 0; i < m_ragdoll.listCount; i++ )
- {
- Vector mins, maxs;
- matrix3x4_t update;
- if ( !m_ragdoll.list[i].pObject )
- {
- m_ragdollMins[i].Init();
- m_ragdollMaxs[i].Init();
- continue;
- }
- m_ragdoll.list[i].pObject->GetPositionMatrix( &update );
- TransformAABB( update, m_ragdollMins[i], m_ragdollMaxs[i], mins, maxs );
- for ( int j = 0; j < 3; j++ )
- {
- if ( mins[j] < vecFullMins[j] )
- {
- vecFullMins[j] = mins[j];
- }
- if ( maxs[j] > vecFullMaxs[j] )
- {
- vecFullMaxs[j] = maxs[j];
- }
- }
- }
-
- SetAbsOrigin( m_ragPos[0] );
- SetAbsAngles( vec3_angle );
- const Vector &vecOrigin = CollisionProp()->GetCollisionOrigin();
- CollisionProp()->AddSolidFlags( FSOLID_FORCE_WORLD_ALIGNED );
- CollisionProp()->SetSurroundingBoundsType( USE_COLLISION_BOUNDS_NEVER_VPHYSICS );
- SetCollisionBounds( vecFullMins - vecOrigin, vecFullMaxs - vecOrigin );
- CollisionProp()->MarkSurroundingBoundsDirty();
-
- PhysicsTouchTriggers();
-}
-
-int CRagdollProp::VPhysicsGetObjectList( IPhysicsObject **pList, int listMax )
-{
- for ( int i = 0; i < m_ragdoll.listCount; i++ )
- {
- if ( i < listMax )
- {
- pList[i] = m_ragdoll.list[i].pObject;
- }
- }
-
- return m_ragdoll.listCount;
-}
-
-void CRagdollProp::UpdateNetworkDataFromVPhysics( IPhysicsObject *pPhysics, int index )
-{
- Assert(index < m_ragdoll.listCount);
-
- QAngle angles;
- Vector vPos;
- m_ragdoll.list[index].pObject->GetPosition( &vPos, &angles );
- m_ragPos.Set( index, vPos );
- m_ragAngles.Set( index, angles );
-
- // move/relink if root moved
- if ( index == 0 )
- {
- SetAbsOrigin( m_ragPos[0] );
- PhysicsTouchTriggers();
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Fade out due to the LRU telling it do
-//-----------------------------------------------------------------------------
-#define FADE_OUT_LENGTH 0.5f
-
-void CRagdollProp::FadeOut( float flDelay, float fadeTime )
-{
- if ( IsFading() )
- return;
-
- m_flFadeTime = ( fadeTime == -1 ) ? FADE_OUT_LENGTH : fadeTime;
-
- m_flFadeOutStartTime = gpGlobals->curtime + flDelay;
- m_flFadeScale = 0;
- SetContextThink( &CRagdollProp::FadeOutThink, gpGlobals->curtime + flDelay + 0.01f, s_pFadeOutContext );
-}
-
-bool CRagdollProp::IsFading()
-{
- return ( GetNextThink( s_pFadeOutContext ) >= gpGlobals->curtime );
-}
-
-void CRagdollProp::FadeOutThink(void)
-{
- float dt = gpGlobals->curtime - m_flFadeOutStartTime;
- if ( dt < 0 )
- {
- SetContextThink( &CRagdollProp::FadeOutThink, gpGlobals->curtime + 0.1, s_pFadeOutContext );
- }
- else if ( dt < m_flFadeTime )
- {
- float alpha = 1.0f - dt / m_flFadeTime;
- int nFade = (int)(alpha * 255.0f);
- m_nRenderMode = kRenderTransTexture;
- SetRenderColorA( nFade );
- NetworkStateChanged();
- SetContextThink( &CRagdollProp::FadeOutThink, gpGlobals->curtime + TICK_INTERVAL, s_pFadeOutContext );
- }
- else
- {
- // Necessary to cause it to do the appropriate death cleanup
- // Yeah, the player may have nothing to do with it, but
- // passing NULL to TakeDamage causes bad things to happen
- CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
- CTakeDamageInfo info( pPlayer, pPlayer, 10000.0, DMG_GENERIC );
- TakeDamage( info );
- UTIL_Remove( this );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Draw any debug text overlays
-// Output : Current text offset from the top
-//-----------------------------------------------------------------------------
-int CRagdollProp::DrawDebugTextOverlays(void)
-{
- int text_offset = BaseClass::DrawDebugTextOverlays();
-
- if (m_debugOverlays & OVERLAY_TEXT_BIT)
- {
- if (m_ragdoll.listCount)
- {
- float mass = 0;
- for ( int i = 0; i < m_ragdoll.listCount; i++ )
- {
- if ( m_ragdoll.list[i].pObject != NULL )
- {
- mass += m_ragdoll.list[i].pObject->GetMass();
- }
- }
-
- char tempstr[512];
- Q_snprintf(tempstr, sizeof(tempstr),"Mass: %.2f kg / %.2f lb (%s)", mass, kg2lbs(mass), GetMassEquivalent(mass) );
- EntityText( text_offset, tempstr, 0);
- text_offset++;
- }
- }
-
- return text_offset;
-}
-
-void CRagdollProp::DrawDebugGeometryOverlays()
-{
- if (m_debugOverlays & OVERLAY_BBOX_BIT)
- {
- DrawServerHitboxes();
- }
- if (m_debugOverlays & OVERLAY_PIVOT_BIT)
- {
- for ( int i = 0; i < m_ragdoll.listCount; i++ )
- {
- if ( m_ragdoll.list[i].pObject )
- {
- float mass = m_ragdoll.list[i].pObject->GetMass();
- Vector pos;
- m_ragdoll.list[i].pObject->GetPosition( &pos, NULL );
- CFmtStr str("mass %.1f", mass );
- NDebugOverlay::EntityTextAtPosition( pos, 0, str.Access(), 0, 0, 255, 0, 255 );
- }
- }
- }
- BaseClass::DrawDebugGeometryOverlays();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *pOther -
-//-----------------------------------------------------------------------------
-void CRagdollProp::SetUnragdoll( CBaseAnimating *pOther )
-{
- m_hUnragdoll = pOther;
-}
-
-//===============================================================================================================
-// RagdollPropAttached
-//===============================================================================================================
-class CRagdollPropAttached : public CRagdollProp
-{
- DECLARE_CLASS( CRagdollPropAttached, CRagdollProp );
-public:
-
- CRagdollPropAttached()
- {
- m_bShouldDetach = false;
- }
-
- ~CRagdollPropAttached()
- {
- physenv->DestroyConstraint( m_pAttachConstraint );
- m_pAttachConstraint = NULL;
- }
-
- void InitRagdollAttached( IPhysicsObject *pAttached, const Vector &forceVector, int forceBone, matrix3x4_t *pPrevBones, matrix3x4_t *pBoneToWorld, float dt, int collisionGroup, CBaseAnimating *pFollow, int boneIndexRoot, const Vector &boneLocalOrigin, int parentBoneAttach, const Vector &worldAttachOrigin );
- void DetachOnNextUpdate();
- void VPhysicsUpdate( IPhysicsObject *pPhysics );
-
- DECLARE_SERVERCLASS();
- DECLARE_DATADESC();
-
-private:
- void Detach();
- CNetworkVar( int, m_boneIndexAttached );
- CNetworkVar( int, m_ragdollAttachedObjectIndex );
- CNetworkVector( m_attachmentPointBoneSpace );
- CNetworkVector( m_attachmentPointRagdollSpace );
- bool m_bShouldDetach;
- IPhysicsConstraint *m_pAttachConstraint;
-};
-
-LINK_ENTITY_TO_CLASS( prop_ragdoll_attached, CRagdollPropAttached );
-EXTERN_SEND_TABLE(DT_Ragdoll_Attached)
-
-IMPLEMENT_SERVERCLASS_ST(CRagdollPropAttached, DT_Ragdoll_Attached)
- SendPropInt( SENDINFO( m_boneIndexAttached ), MAXSTUDIOBONEBITS, SPROP_UNSIGNED ),
- SendPropInt( SENDINFO( m_ragdollAttachedObjectIndex ), RAGDOLL_INDEX_BITS, SPROP_UNSIGNED ),
- SendPropVector(SENDINFO(m_attachmentPointBoneSpace), -1, SPROP_COORD ),
- SendPropVector(SENDINFO(m_attachmentPointRagdollSpace), -1, SPROP_COORD ),
-END_SEND_TABLE()
-
-BEGIN_DATADESC(CRagdollPropAttached)
- DEFINE_FIELD( m_boneIndexAttached, FIELD_INTEGER ),
- DEFINE_FIELD( m_ragdollAttachedObjectIndex, FIELD_INTEGER ),
- DEFINE_FIELD( m_attachmentPointBoneSpace, FIELD_VECTOR ),
- DEFINE_FIELD( m_attachmentPointRagdollSpace, FIELD_VECTOR ),
- DEFINE_FIELD( m_bShouldDetach, FIELD_BOOLEAN ),
- DEFINE_PHYSPTR( m_pAttachConstraint ),
-END_DATADESC()
-
-
-static void SyncAnimatingWithPhysics( CBaseAnimating *pAnimating )
-{
- IPhysicsObject *pPhysics = pAnimating->VPhysicsGetObject();
- if ( pPhysics )
- {
- Vector pos;
- pPhysics->GetShadowPosition( &pos, NULL );
- pAnimating->SetAbsOrigin( pos );
- }
-}
-
-
-CBaseAnimating *CreateServerRagdollSubmodel( CBaseAnimating *pOwner, const char *pModelName, const Vector &position, const QAngle &angles, int collisionGroup )
-{
- CRagdollProp *pRagdoll = (CRagdollProp *)CBaseEntity::CreateNoSpawn( "prop_ragdoll", position, angles, pOwner );
- pRagdoll->SetModelName( AllocPooledString( pModelName ) );
- pRagdoll->SetModel( STRING(pRagdoll->GetModelName()) );
- matrix3x4_t pBoneToWorld[MAXSTUDIOBONES], pBoneToWorldNext[MAXSTUDIOBONES];
- pRagdoll->ResetSequence( 0 );
-
- // let bone merging do the work of copying everything over for us
- pRagdoll->SetParent( pOwner );
- pRagdoll->SetupBones( pBoneToWorld, BONE_USED_BY_ANYTHING );
- // HACKHACK: don't want this parent anymore
- pRagdoll->SetParent( NULL );
-
- memcpy( pBoneToWorldNext, pBoneToWorld, sizeof(pBoneToWorld) );
-
- pRagdoll->InitRagdoll( vec3_origin, -1, vec3_origin, pBoneToWorld, pBoneToWorldNext, 0.1, collisionGroup, true );
- return pRagdoll;
-}
-
-
-CBaseEntity *CreateServerRagdoll( CBaseAnimating *pAnimating, int forceBone, const CTakeDamageInfo &info, int collisionGroup, bool bUseLRURetirement )
-{
- if ( info.GetDamageType() & (DMG_VEHICLE|DMG_CRUSH) )
- {
- // if the entity was killed by physics or a vehicle, move to the vphysics shadow position before creating the ragdoll.
- SyncAnimatingWithPhysics( pAnimating );
- }
- CRagdollProp *pRagdoll = (CRagdollProp *)CBaseEntity::CreateNoSpawn( "prop_ragdoll", pAnimating->GetAbsOrigin(), vec3_angle, NULL );
- pRagdoll->CopyAnimationDataFrom( pAnimating );
- pRagdoll->SetOwnerEntity( pAnimating );
-
- pRagdoll->InitRagdollAnimation();
- matrix3x4_t pBoneToWorld[MAXSTUDIOBONES], pBoneToWorldNext[MAXSTUDIOBONES];
-
- float dt = 0.1f;
-
- // Copy over dissolve state...
- if ( pAnimating->IsEFlagSet( EFL_NO_DISSOLVE ) )
- {
- pRagdoll->AddEFlags( EFL_NO_DISSOLVE );
- }
-
- // NOTE: This currently is only necessary to prevent manhacks from
- // colliding with server ragdolls they kill
- pRagdoll->SetKiller( info.GetInflictor() );
- pRagdoll->SetSourceClassName( pAnimating->GetClassname() );
-
- // NPC_STATE_DEAD npc's will have their COND_IN_PVS cleared, so this needs to force SetupBones to happen
- unsigned short fPrevFlags = pAnimating->GetBoneCacheFlags();
- pAnimating->SetBoneCacheFlags( BCF_NO_ANIMATION_SKIP );
-
- // UNDONE: Extract velocity from bones via animation (like we do on the client)
- // UNDONE: For now, just move each bone by the total entity velocity if set.
- // Get Bones positions before
- // Store current cycle
- float fSequenceDuration = pAnimating->SequenceDuration( pAnimating->GetSequence() );
- float fSequenceTime = pAnimating->GetCycle() * fSequenceDuration;
-
- if( fSequenceTime <= dt && fSequenceTime > 0.0f )
- {
- // Avoid having negative cycle
- dt = fSequenceTime;
- }
-
- float fPreviousCycle = clamp(pAnimating->GetCycle()-( dt * ( 1 / fSequenceDuration ) ),0.f,1.f);
- float fCurCycle = pAnimating->GetCycle();
- // Get current bones positions
- pAnimating->SetupBones( pBoneToWorldNext, BONE_USED_BY_ANYTHING );
- // Get previous bones positions
- pAnimating->SetCycle( fPreviousCycle );
- pAnimating->SetupBones( pBoneToWorld, BONE_USED_BY_ANYTHING );
- // Restore current cycle
- pAnimating->SetCycle( fCurCycle );
-
- // Reset previous bone flags
- pAnimating->ClearBoneCacheFlags( BCF_NO_ANIMATION_SKIP );
- pAnimating->SetBoneCacheFlags( fPrevFlags );
-
- Vector vel = pAnimating->GetAbsVelocity();
- if( ( vel.Length() == 0 ) && ( dt > 0 ) )
- {
- // Compute animation velocity
- CStudioHdr *pstudiohdr = pAnimating->GetModelPtr();
- if ( pstudiohdr )
- {
- Vector deltaPos;
- QAngle deltaAngles;
- if (Studio_SeqMovement( pstudiohdr,
- pAnimating->GetSequence(),
- fPreviousCycle,
- pAnimating->GetCycle(),
- pAnimating->GetPoseParameterArray(),
- deltaPos,
- deltaAngles ))
- {
- VectorRotate( deltaPos, pAnimating->EntityToWorldTransform(), vel );
- vel /= dt;
- }
- }
- }
-
- if ( vel.LengthSqr() > 0 )
- {
- int numbones = pAnimating->GetModelPtr()->numbones();
- vel *= dt;
- for ( int i = 0; i < numbones; i++ )
- {
- Vector pos;
- MatrixGetColumn( pBoneToWorld[i], 3, pos );
- pos -= vel;
- MatrixSetColumn( pos, 3, pBoneToWorld[i] );
- }
- }
-
-#if RAGDOLL_VISUALIZE
- pAnimating->DrawRawSkeleton( pBoneToWorld, BONE_USED_BY_ANYTHING, true, 20, false );
- pAnimating->DrawRawSkeleton( pBoneToWorldNext, BONE_USED_BY_ANYTHING, true, 20, true );
-#endif
- // Is this a vehicle / NPC collision?
- if ( (info.GetDamageType() & DMG_VEHICLE) && pAnimating->MyNPCPointer() )
- {
- // init the ragdoll with no forces
- pRagdoll->InitRagdoll( vec3_origin, -1, vec3_origin, pBoneToWorld, pBoneToWorldNext, dt, collisionGroup, true );
-
- // apply vehicle forces
- // Get a list of bones with hitboxes below the plane of impact
- int boxList[128];
- Vector normal(0,0,-1);
- int count = pAnimating->GetHitboxesFrontside( boxList, ARRAYSIZE(boxList), normal, DotProduct( normal, info.GetDamagePosition() ) );
-
- // distribute force over mass of entire character
- float massScale = Studio_GetMass(pAnimating->GetModelPtr());
- massScale = clamp( massScale, 1.f, 1.e4f );
- massScale = 1.f / massScale;
-
- // distribute the force
- // BUGBUG: This will hit the same bone twice if it has two hitboxes!!!!
- ragdoll_t *pRagInfo = pRagdoll->GetRagdoll();
- for ( int i = 0; i < count; i++ )
- {
- int physBone = pAnimating->GetPhysicsBone( pAnimating->GetHitboxBone( boxList[i] ) );
- IPhysicsObject *pPhysics = pRagInfo->list[physBone].pObject;
- pPhysics->ApplyForceCenter( info.GetDamageForce() * pPhysics->GetMass() * massScale );
- }
- }
- else
- {
- pRagdoll->InitRagdoll( info.GetDamageForce(), forceBone, info.GetDamagePosition(), pBoneToWorld, pBoneToWorldNext, dt, collisionGroup, true );
- }
-
- // Are we dissolving?
- if ( pAnimating->IsDissolving() )
- {
- pRagdoll->TransferDissolveFrom( pAnimating );
- }
- else if ( bUseLRURetirement )
- {
- pRagdoll->AddSpawnFlags( SF_RAGDOLLPROP_USE_LRU_RETIREMENT );
- s_RagdollLRU.MoveToTopOfLRU( pRagdoll );
- }
-
- // Tracker 22598: If we don't set the OBB mins/maxs to something valid here, then the client will have a zero sized hull
- // for the ragdoll for one frame until Vphysics updates the real obb bounds after the first simulation frame. Having
- // a zero sized hull makes the ragdoll think it should be faded/alpha'd to zero for a frame, so you get a blink where
- // the ragdoll doesn't draw initially.
- Vector mins, maxs;
- mins = pAnimating->CollisionProp()->OBBMins();
- maxs = pAnimating->CollisionProp()->OBBMaxs();
- pRagdoll->CollisionProp()->SetCollisionBounds( mins, maxs );
-
- return pRagdoll;
-}
-
-void CRagdollPropAttached::DetachOnNextUpdate()
-{
- m_bShouldDetach = true;
-}
-
-void CRagdollPropAttached::VPhysicsUpdate( IPhysicsObject *pPhysics )
-{
- if ( m_bShouldDetach )
- {
- Detach();
- m_bShouldDetach = false;
- }
- BaseClass::VPhysicsUpdate( pPhysics );
-}
-
-void CRagdollPropAttached::Detach()
-{
- SetParent(NULL);
- SetOwnerEntity( NULL );
- SetAbsAngles( vec3_angle );
- SetMoveType( MOVETYPE_VPHYSICS );
- RemoveSolidFlags( FSOLID_NOT_SOLID );
- physenv->DestroyConstraint( m_pAttachConstraint );
- m_pAttachConstraint = NULL;
- const float dampingScale = 1.0f / ATTACHED_DAMPING_SCALE;
- for ( int i = 0; i < m_ragdoll.listCount; i++ )
- {
- float damping, rotdamping;
- m_ragdoll.list[i].pObject->GetDamping( &damping, &rotdamping );
- damping *= dampingScale;
- rotdamping *= dampingScale;
- m_ragdoll.list[i].pObject->SetDamping( &damping, &damping );
- }
-
- // Go non-solid
- SetCollisionGroup( COLLISION_GROUP_DEBRIS );
- RecheckCollisionFilter();
-}
-
-void CRagdollPropAttached::InitRagdollAttached(
- IPhysicsObject *pAttached,
- const Vector &forceVector,
- int forceBone,
- matrix3x4_t *pPrevBones,
- matrix3x4_t *pBoneToWorld,
- float dt,
- int collisionGroup,
- CBaseAnimating *pFollow,
- int boneIndexRoot,
- const Vector &boneLocalOrigin,
- int parentBoneAttach,
- const Vector &worldAttachOrigin )
-{
- int ragdollAttachedIndex = 0;
- if ( parentBoneAttach > 0 )
- {
- CStudioHdr *pStudioHdr = GetModelPtr();
- mstudiobone_t *pBone = pStudioHdr->pBone( parentBoneAttach );
- ragdollAttachedIndex = pBone->physicsbone;
- }
-
- InitRagdoll( forceVector, forceBone, vec3_origin, pPrevBones, pBoneToWorld, dt, collisionGroup, false );
-
- IPhysicsObject *pRefObject = m_ragdoll.list[ragdollAttachedIndex].pObject;
-
- Vector attachmentPointRagdollSpace;
- pRefObject->WorldToLocal( &attachmentPointRagdollSpace, worldAttachOrigin );
-
- constraint_ragdollparams_t constraint;
- constraint.Defaults();
- matrix3x4_t tmp, worldToAttached, worldToReference, constraintToWorld;
-
- Vector offsetWS;
- pAttached->LocalToWorld( &offsetWS, boneLocalOrigin );
-
- QAngle followAng = QAngle(0, pFollow->GetAbsAngles().y, 0 );
- AngleMatrix( followAng, offsetWS, constraintToWorld );
-
- constraint.axes[0].SetAxisFriction( -2, 2, 20 );
- constraint.axes[1].SetAxisFriction( 0, 0, 0 );
- constraint.axes[2].SetAxisFriction( -15, 15, 20 );
-
- // Exaggerate the bone's ability to pull the mass of the ragdoll around
- constraint.constraint.bodyMassScale[1] = 50.0f;
-
- pAttached->GetPositionMatrix( &tmp );
- MatrixInvert( tmp, worldToAttached );
-
- pRefObject->GetPositionMatrix( &tmp );
- MatrixInvert( tmp, worldToReference );
-
- ConcatTransforms( worldToReference, constraintToWorld, constraint.constraintToReference );
- ConcatTransforms( worldToAttached, constraintToWorld, constraint.constraintToAttached );
-
- // for now, just slam this to be the passed in value
- MatrixSetColumn( attachmentPointRagdollSpace, 3, constraint.constraintToReference );
-
- PhysDisableEntityCollisions( pAttached, m_ragdoll.list[0].pObject );
- m_pAttachConstraint = physenv->CreateRagdollConstraint( pRefObject, pAttached, m_ragdoll.pGroup, constraint );
-
- SetParent( pFollow );
- SetOwnerEntity( pFollow );
-
- RagdollActivate( m_ragdoll, modelinfo->GetVCollide( GetModelIndex() ), GetModelIndex() );
-
- // add a bunch of dampening to the ragdoll
- for ( int i = 0; i < m_ragdoll.listCount; i++ )
- {
- float damping, rotdamping;
- m_ragdoll.list[i].pObject->GetDamping( &damping, &rotdamping );
- damping *= ATTACHED_DAMPING_SCALE;
- rotdamping *= ATTACHED_DAMPING_SCALE;
- m_ragdoll.list[i].pObject->SetDamping( &damping, &rotdamping );
- }
-
- m_boneIndexAttached = boneIndexRoot;
- m_ragdollAttachedObjectIndex = ragdollAttachedIndex;
- m_attachmentPointBoneSpace = boneLocalOrigin;
-
- Vector vTemp;
- MatrixGetColumn( constraint.constraintToReference, 3, vTemp );
- m_attachmentPointRagdollSpace = vTemp;
-}
-
-CRagdollProp *CreateServerRagdollAttached( CBaseAnimating *pAnimating, const Vector &vecForce, int forceBone, int collisionGroup, IPhysicsObject *pAttached, CBaseAnimating *pParentEntity, int boneAttach, const Vector &originAttached, int parentBoneAttach, const Vector &boneOrigin )
-{
- // Return immediately if the model doesn't have a vcollide
- if ( modelinfo->GetVCollide( pAnimating->GetModelIndex() ) == NULL )
- return NULL;
-
- CRagdollPropAttached *pRagdoll = (CRagdollPropAttached *)CBaseEntity::CreateNoSpawn( "prop_ragdoll_attached", pAnimating->GetAbsOrigin(), vec3_angle, NULL );
- pRagdoll->CopyAnimationDataFrom( pAnimating );
-
- pRagdoll->InitRagdollAnimation();
- matrix3x4_t pBoneToWorld[MAXSTUDIOBONES];
- pAnimating->SetupBones( pBoneToWorld, BONE_USED_BY_ANYTHING );
- pRagdoll->InitRagdollAttached( pAttached, vecForce, forceBone, pBoneToWorld, pBoneToWorld, 0.1, collisionGroup, pParentEntity, boneAttach, boneOrigin, parentBoneAttach, originAttached );
-
- return pRagdoll;
-}
-
-void DetachAttachedRagdoll( CBaseEntity *pRagdollIn )
-{
- CRagdollPropAttached *pRagdoll = dynamic_cast<CRagdollPropAttached *>(pRagdollIn);
-
- if ( pRagdoll )
- {
- pRagdoll->DetachOnNextUpdate();
- }
-}
-
-void DetachAttachedRagdollsForEntity( CBaseEntity *pRagdollParent )
-{
- CUtlVector<CBaseEntity *> list;
- GetAllChildren( pRagdollParent, list );
- for ( int i = list.Count()-1; i >= 0; --i )
- {
- DetachAttachedRagdoll( list[i] );
- }
-}
-
-bool Ragdoll_IsPropRagdoll( CBaseEntity *pEntity )
-{
- if ( dynamic_cast<CRagdollProp *>(pEntity) != NULL )
- return true;
- return false;
-}
-
-ragdoll_t *Ragdoll_GetRagdoll( CBaseEntity *pEntity )
-{
- CRagdollProp *pProp = dynamic_cast<CRagdollProp *>(pEntity);
- if ( pProp )
- return pProp->GetRagdoll();
- return NULL;
-}
-
-void CRagdollProp::GetAngleOverrideFromCurrentState( char *pOut, int size )
-{
- pOut[0] = 0;
- for ( int i = 0; i < m_ragdoll.listCount; i++ )
- {
- if ( i != 0 )
- {
- Q_strncat( pOut, ",", size, COPY_ALL_CHARACTERS );
-
- }
- CFmtStr str("%d,%.2f %.2f %.2f", i, m_ragAngles[i].x, m_ragAngles[i].y, m_ragAngles[i].z );
- Q_strncat( pOut, str, size, COPY_ALL_CHARACTERS );
- }
-}
-
-void CRagdollProp::DisableMotion( void )
-{
- for ( int iRagdoll = 0; iRagdoll < m_ragdoll.listCount; ++iRagdoll )
- {
- IPhysicsObject *pPhysicsObject = m_ragdoll.list[ iRagdoll ].pObject;
- if ( pPhysicsObject != NULL )
- {
- pPhysicsObject->EnableMotion( false );
- }
- }
-}
-
-void CRagdollProp::InputStartRadgollBoogie( inputdata_t &inputdata )
-{
- float duration = inputdata.value.Float();
-
- if( duration <= 0.0f )
- {
- duration = 5.0f;
- }
-
- CRagdollBoogie::Create( this, 100, gpGlobals->curtime, duration, 0 );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Enable physics motion and collision response (on by default)
-//-----------------------------------------------------------------------------
-void CRagdollProp::InputEnableMotion( inputdata_t &inputdata )
-{
- for ( int iRagdoll = 0; iRagdoll < m_ragdoll.listCount; ++iRagdoll )
- {
- IPhysicsObject *pPhysicsObject = m_ragdoll.list[ iRagdoll ].pObject;
- if ( pPhysicsObject != NULL )
- {
- pPhysicsObject->EnableMotion( true );
- pPhysicsObject->Wake();
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Disable any physics motion or collision response
-//-----------------------------------------------------------------------------
-void CRagdollProp::InputDisableMotion( inputdata_t &inputdata )
-{
- DisableMotion();
-}
-
-void CRagdollProp::InputTurnOn( inputdata_t &inputdata )
-{
- RemoveEffects( EF_NODRAW );
-}
-
-void CRagdollProp::InputTurnOff( inputdata_t &inputdata )
-{
- AddEffects( EF_NODRAW );
-}
-
-void CRagdollProp::InputFadeAndRemove( inputdata_t &inputdata )
-{
- float flFadeDuration = inputdata.value.Float();
-
- if( flFadeDuration == 0.0f )
- flFadeDuration = 1.0f;
-
- FadeOut( 0.0f, flFadeDuration );
-}
-
-void Ragdoll_GetAngleOverrideString( char *pOut, int size, CBaseEntity *pEntity )
-{
- CRagdollProp *pRagdoll = dynamic_cast<CRagdollProp *>(pEntity);
- if ( pRagdoll )
- {
- pRagdoll->GetAngleOverrideFromCurrentState( pOut, size );
- }
-}
+//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "baseanimating.h" +#include "studio.h" +#include "physics.h" +#include "physics_saverestore.h" +#include "ai_basenpc.h" +#include "vphysics/constraints.h" +#include "datacache/imdlcache.h" +#include "bone_setup.h" +#include "physics_prop_ragdoll.h" +#include "KeyValues.h" +#include "props.h" +#include "RagdollBoogie.h" +#include "AI_Criteria.h" +#include "ragdoll_shared.h" +#include "hierarchy.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Forward declarations +//----------------------------------------------------------------------------- +const char *GetMassEquivalent(float flMass); + +#define RAGDOLL_VISUALIZE 0 + +//----------------------------------------------------------------------------- +// ThinkContext +//----------------------------------------------------------------------------- +const char *s_pFadeOutContext = "RagdollFadeOutContext"; +const char *s_pDebrisContext = "DebrisContext"; + +const float ATTACHED_DAMPING_SCALE = 50.0f; + +//----------------------------------------------------------------------------- +// Spawnflags +//----------------------------------------------------------------------------- +#define SF_RAGDOLLPROP_DEBRIS 0x0004 +#define SF_RAGDOLLPROP_USE_LRU_RETIREMENT 0x1000 +#define SF_RAGDOLLPROP_ALLOW_DISSOLVE 0x2000 // Allow this prop to be dissolved +#define SF_RAGDOLLPROP_MOTIONDISABLED 0x4000 +#define SF_RAGDOLLPROP_ALLOW_STRETCH 0x8000 +#define SF_RAGDOLLPROP_STARTASLEEP 0x10000 + +//----------------------------------------------------------------------------- +// Networking +//----------------------------------------------------------------------------- +LINK_ENTITY_TO_CLASS( physics_prop_ragdoll, CRagdollProp ); +LINK_ENTITY_TO_CLASS( prop_ragdoll, CRagdollProp ); +EXTERN_SEND_TABLE(DT_Ragdoll) + +IMPLEMENT_SERVERCLASS_ST(CRagdollProp, DT_Ragdoll) + SendPropArray (SendPropQAngles(SENDINFO_ARRAY(m_ragAngles), 13, 0 ), m_ragAngles), + SendPropArray (SendPropVector(SENDINFO_ARRAY(m_ragPos), -1, SPROP_COORD ), m_ragPos), + SendPropEHandle(SENDINFO( m_hUnragdoll ) ), + SendPropFloat(SENDINFO(m_flBlendWeight), 8, SPROP_ROUNDDOWN, 0.0f, 1.0f ), + SendPropInt(SENDINFO(m_nOverlaySequence), 11), +END_SEND_TABLE() + +#define DEFINE_RAGDOLL_ELEMENT( i ) \ + DEFINE_FIELD( m_ragdoll.list[i].originParentSpace, FIELD_VECTOR ), \ + DEFINE_PHYSPTR( m_ragdoll.list[i].pObject ), \ + DEFINE_PHYSPTR( m_ragdoll.list[i].pConstraint ), \ + DEFINE_FIELD( m_ragdoll.list[i].parentIndex, FIELD_INTEGER ) + +BEGIN_DATADESC(CRagdollProp) +// m_ragdoll (custom handling) + DEFINE_AUTO_ARRAY ( m_ragdoll.boneIndex, FIELD_INTEGER ), + DEFINE_AUTO_ARRAY ( m_ragPos, FIELD_POSITION_VECTOR ), + DEFINE_AUTO_ARRAY ( m_ragAngles, FIELD_VECTOR ), + DEFINE_KEYFIELD(m_anglesOverrideString, FIELD_STRING, "angleOverride" ), + DEFINE_FIELD( m_lastUpdateTickCount, FIELD_INTEGER ), + DEFINE_FIELD( m_allAsleep, FIELD_BOOLEAN ), + DEFINE_FIELD( m_hDamageEntity, FIELD_EHANDLE ), + DEFINE_FIELD( m_hKiller, FIELD_EHANDLE ), + + DEFINE_KEYFIELD( m_bStartDisabled, FIELD_BOOLEAN, "StartDisabled" ), + + DEFINE_INPUTFUNC( FIELD_VOID, "StartRagdollBoogie", InputStartRadgollBoogie ), + DEFINE_INPUTFUNC( FIELD_VOID, "EnableMotion", InputEnableMotion ), + DEFINE_INPUTFUNC( FIELD_VOID, "DisableMotion", InputDisableMotion ), + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputTurnOn ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputTurnOff ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "FadeAndRemove", InputFadeAndRemove ), + + DEFINE_FIELD( m_hUnragdoll, FIELD_EHANDLE ), + DEFINE_FIELD( m_bFirstCollisionAfterLaunch, FIELD_BOOLEAN ), + + DEFINE_FIELD( m_flBlendWeight, FIELD_FLOAT ), + DEFINE_FIELD( m_nOverlaySequence, FIELD_INTEGER ), + DEFINE_AUTO_ARRAY( m_ragdollMins, FIELD_VECTOR ), + DEFINE_AUTO_ARRAY( m_ragdollMaxs, FIELD_VECTOR ), + + // Physics Influence + DEFINE_FIELD( m_hPhysicsAttacker, FIELD_EHANDLE ), + DEFINE_FIELD( m_flLastPhysicsInfluenceTime, FIELD_TIME ), + DEFINE_FIELD( m_flFadeOutStartTime, FIELD_TIME ), + DEFINE_FIELD( m_flFadeTime, FIELD_FLOAT), + DEFINE_FIELD( m_strSourceClassName, FIELD_STRING ), + DEFINE_FIELD( m_bHasBeenPhysgunned, FIELD_BOOLEAN ), + + // think functions + DEFINE_THINKFUNC( SetDebrisThink ), + DEFINE_THINKFUNC( ClearFlagsThink ), + DEFINE_THINKFUNC( FadeOutThink ), + + DEFINE_FIELD( m_ragdoll.listCount, FIELD_INTEGER ), + DEFINE_FIELD( m_ragdoll.allowStretch, FIELD_BOOLEAN ), + DEFINE_PHYSPTR( m_ragdoll.pGroup ), + DEFINE_FIELD( m_flDefaultFadeScale, FIELD_FLOAT ), + + //DEFINE_RAGDOLL_ELEMENT( 0 ), + DEFINE_RAGDOLL_ELEMENT( 1 ), + DEFINE_RAGDOLL_ELEMENT( 2 ), + DEFINE_RAGDOLL_ELEMENT( 3 ), + DEFINE_RAGDOLL_ELEMENT( 4 ), + DEFINE_RAGDOLL_ELEMENT( 5 ), + DEFINE_RAGDOLL_ELEMENT( 6 ), + DEFINE_RAGDOLL_ELEMENT( 7 ), + DEFINE_RAGDOLL_ELEMENT( 8 ), + DEFINE_RAGDOLL_ELEMENT( 9 ), + DEFINE_RAGDOLL_ELEMENT( 10 ), + DEFINE_RAGDOLL_ELEMENT( 11 ), + DEFINE_RAGDOLL_ELEMENT( 12 ), + DEFINE_RAGDOLL_ELEMENT( 13 ), + DEFINE_RAGDOLL_ELEMENT( 14 ), + DEFINE_RAGDOLL_ELEMENT( 15 ), + DEFINE_RAGDOLL_ELEMENT( 16 ), + DEFINE_RAGDOLL_ELEMENT( 17 ), + DEFINE_RAGDOLL_ELEMENT( 18 ), + DEFINE_RAGDOLL_ELEMENT( 19 ), + DEFINE_RAGDOLL_ELEMENT( 20 ), + DEFINE_RAGDOLL_ELEMENT( 21 ), + DEFINE_RAGDOLL_ELEMENT( 22 ), + DEFINE_RAGDOLL_ELEMENT( 23 ), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Disable auto fading under dx7 or when level fades are specified +//----------------------------------------------------------------------------- +void CRagdollProp::DisableAutoFade() +{ + m_flFadeScale = 0; + m_flDefaultFadeScale = 0; +} + + +void CRagdollProp::Spawn( void ) +{ + // Starts out as the default fade scale value + m_flDefaultFadeScale = m_flFadeScale; + + // NOTE: If this fires, then the assert or the datadesc is wrong! (see DEFINE_RAGDOLL_ELEMENT above) + Assert( RAGDOLL_MAX_ELEMENTS == 24 ); + Precache(); + SetModel( STRING( GetModelName() ) ); + + CStudioHdr *pStudioHdr = GetModelPtr( ); + if ( pStudioHdr->flags() & STUDIOHDR_FLAGS_NO_FORCED_FADE ) + { + DisableAutoFade(); + } + else + { + m_flFadeScale = m_flDefaultFadeScale; + } + + matrix3x4_t pBoneToWorld[MAXSTUDIOBONES]; + BaseClass::SetupBones( pBoneToWorld, BONE_USED_BY_ANYTHING ); // FIXME: shouldn't this be a subset of the bones + // this is useless info after the initial conditions are set + SetAbsAngles( vec3_angle ); + int collisionGroup = (m_spawnflags & SF_RAGDOLLPROP_DEBRIS) ? COLLISION_GROUP_DEBRIS : COLLISION_GROUP_NONE; + bool bWake = (m_spawnflags & SF_RAGDOLLPROP_STARTASLEEP) ? false : true; + InitRagdoll( vec3_origin, 0, vec3_origin, pBoneToWorld, pBoneToWorld, 0, collisionGroup, true, bWake ); + m_lastUpdateTickCount = 0; + m_flBlendWeight = 0.0f; + m_nOverlaySequence = -1; + + // Unless specified, do not allow this to be dissolved + if ( HasSpawnFlags( SF_RAGDOLLPROP_ALLOW_DISSOLVE ) == false ) + { + AddEFlags( EFL_NO_DISSOLVE ); + } + + if ( HasSpawnFlags(SF_RAGDOLLPROP_MOTIONDISABLED) ) + { + DisableMotion(); + } + + if( m_bStartDisabled ) + { + AddEffects( EF_NODRAW ); + } +} + +void CRagdollProp::SetSourceClassName( const char *pClassname ) +{ + m_strSourceClassName = MAKE_STRING( pClassname ); +} + + +void CRagdollProp::OnSave( IEntitySaveUtils *pUtils ) +{ + if ( !m_ragdoll.listCount ) + return; + + // Don't save ragdoll element 0, base class saves the pointer in + // m_pPhysicsObject + Assert( m_ragdoll.list[0].parentIndex == -1 ); + Assert( m_ragdoll.list[0].pConstraint == NULL ); + Assert( m_ragdoll.list[0].originParentSpace == vec3_origin ); + Assert( m_ragdoll.list[0].pObject != NULL ); + VPhysicsSetObject( NULL ); // squelch a warning message + VPhysicsSetObject( m_ragdoll.list[0].pObject ); // make sure object zero is saved by CBaseEntity + BaseClass::OnSave( pUtils ); +} + +void CRagdollProp::OnRestore() +{ + // rebuild element 0 since it isn't saved + // NOTE: This breaks the rules - the pointer needs to get fixed in Restore() + m_ragdoll.list[0].pObject = VPhysicsGetObject(); + m_ragdoll.list[0].parentIndex = -1; + m_ragdoll.list[0].originParentSpace.Init(); + + BaseClass::OnRestore(); + if ( !m_ragdoll.listCount ) + return; + + // JAY: Reset collision relationships + RagdollSetupCollisions( m_ragdoll, modelinfo->GetVCollide( GetModelIndex() ), GetModelIndex() ); + VPhysicsUpdate( VPhysicsGetObject() ); +} + +void CRagdollProp::CalcRagdollSize( void ) +{ + CollisionProp()->SetSurroundingBoundsType( USE_HITBOXES ); + CollisionProp()->RemoveSolidFlags( FSOLID_FORCE_WORLD_ALIGNED ); +} + +void CRagdollProp::UpdateOnRemove( void ) +{ + for ( int i = 0; i < m_ragdoll.listCount; i++ ) + { + if ( m_ragdoll.list[i].pObject ) + { + g_pPhysSaveRestoreManager->ForgetModel( m_ragdoll.list[i].pObject ); + } + } + + // Set to null so that the destructor's call to DestroyObject won't destroy + // m_pObjects[ 0 ] twice since that's the physics object for the prop + VPhysicsSetObject( NULL ); + + RagdollDestroy( m_ragdoll ); + // Chain to base after doing our own cleanup to mimic + // destructor unwind order + BaseClass::UpdateOnRemove(); +} + +CRagdollProp::CRagdollProp( void ) +{ + m_strSourceClassName = NULL_STRING; + m_anglesOverrideString = NULL_STRING; + m_ragdoll.listCount = 0; + Assert( (1<<RAGDOLL_INDEX_BITS) >=RAGDOLL_MAX_ELEMENTS ); + m_allAsleep = false; + m_flFadeScale = 1; + m_flDefaultFadeScale = 1; +} + +CRagdollProp::~CRagdollProp( void ) +{ +} + +void CRagdollProp::Precache( void ) +{ + PrecacheModel( STRING( GetModelName() ) ); + BaseClass::Precache(); +} + +int CRagdollProp::ObjectCaps() +{ + return BaseClass::ObjectCaps() | FCAP_WCEDIT_POSITION; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CRagdollProp::InitRagdollAnimation() +{ + m_flAnimTime = gpGlobals->curtime; + m_flPlaybackRate = 0.0; + SetCycle( 0 ); + + // put into ACT_DIERAGDOLL if it exists, otherwise use sequence 0 + int nSequence = SelectWeightedSequence( ACT_DIERAGDOLL ); + if ( nSequence < 0 ) + { + ResetSequence( 0 ); + } + else + { + ResetSequence( nSequence ); + } +} + + +//----------------------------------------------------------------------------- +// Response system stuff +//----------------------------------------------------------------------------- +IResponseSystem *CRagdollProp::GetResponseSystem() +{ + extern IResponseSystem *g_pResponseSystem; + + // Just use the general NPC response system; we often come from NPCs after all + return g_pResponseSystem; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CRagdollProp::ModifyOrAppendCriteria( AI_CriteriaSet& set ) +{ + BaseClass::ModifyOrAppendCriteria( set ); + + if ( m_strSourceClassName != NULL_STRING ) + { + set.RemoveCriteria( "classname" ); + set.AppendCriteria( "classname", STRING(m_strSourceClassName) ); + set.AppendCriteria( "ragdoll", "1" ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CRagdollProp::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason ) +{ + m_hPhysicsAttacker = pPhysGunUser; + m_flLastPhysicsInfluenceTime = gpGlobals->curtime; + + // Clear out the classname if we've been physgunned before + // so that the screams, etc. don't happen. Simulate that the first + // major punt or throw has been enough to kill him. + if ( m_bHasBeenPhysgunned ) + { + m_strSourceClassName = NULL_STRING; + } + m_bHasBeenPhysgunned = true; + + if( HasPhysgunInteraction( "onpickup", "boogie" ) ) + { + if ( reason == PUNTED_BY_CANNON ) + { + CRagdollBoogie::Create( this, 150, gpGlobals->curtime, 3.0f, SF_RAGDOLL_BOOGIE_ELECTRICAL ); + } + else + { + CRagdollBoogie::Create( this, 150, gpGlobals->curtime, 2.0f, 0.0f ); + } + } + + if ( HasSpawnFlags( SF_RAGDOLLPROP_USE_LRU_RETIREMENT ) ) + { + s_RagdollLRU.MoveToTopOfLRU( this ); + } + + if ( !HasSpawnFlags( SF_PHYSPROP_ENABLE_ON_PHYSCANNON ) ) + return; + + ragdoll_t *pRagdollPhys = GetRagdoll( ); + for ( int j = 0; j < pRagdollPhys->listCount; ++j ) + { + pRagdollPhys->list[j].pObject->Wake(); + pRagdollPhys->list[j].pObject->EnableMotion( true ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CRagdollProp::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason ) +{ + CDefaultPlayerPickupVPhysics::OnPhysGunDrop( pPhysGunUser, Reason ); + m_hPhysicsAttacker = pPhysGunUser; + m_flLastPhysicsInfluenceTime = gpGlobals->curtime; + + if( HasPhysgunInteraction( "onpickup", "boogie" ) ) + { + CRagdollBoogie::Create( this, 150, gpGlobals->curtime, 3.0f, SF_RAGDOLL_BOOGIE_ELECTRICAL ); + } + + if ( HasSpawnFlags( SF_RAGDOLLPROP_USE_LRU_RETIREMENT ) ) + { + s_RagdollLRU.MoveToTopOfLRU( this ); + } + + // Make sure it's interactive debris for at most 5 seconds + if ( GetCollisionGroup() == COLLISION_GROUP_INTERACTIVE_DEBRIS ) + { + SetContextThink( &CRagdollProp::SetDebrisThink, gpGlobals->curtime + 5, s_pDebrisContext ); + } + + if ( Reason != LAUNCHED_BY_CANNON ) + return; + + if( HasPhysgunInteraction( "onlaunch", "spin_zaxis" ) ) + { + Vector vecAverageCenter( 0, 0, 0 ); + + // Get the average position, apply forces to produce a spin + int j; + ragdoll_t *pRagdollPhys = GetRagdoll( ); + for ( j = 0; j < pRagdollPhys->listCount; ++j ) + { + Vector vecCenter; + pRagdollPhys->list[j].pObject->GetPosition( &vecCenter, NULL ); + vecAverageCenter += vecCenter; + } + + vecAverageCenter /= pRagdollPhys->listCount; + + Vector vecZAxis( 0, 0, 1 ); + for ( j = 0; j < pRagdollPhys->listCount; ++j ) + { + Vector vecDelta; + pRagdollPhys->list[j].pObject->GetPosition( &vecDelta, NULL ); + vecDelta -= vecAverageCenter; + + Vector vecDir; + CrossProduct( vecZAxis, vecDelta, vecDir ); + vecDir *= 100; + pRagdollPhys->list[j].pObject->AddVelocity( &vecDir, NULL ); + } + } + + PhysSetGameFlags( VPhysicsGetObject(), FVPHYSICS_WAS_THROWN ); + m_bFirstCollisionAfterLaunch = true; +} + + +//----------------------------------------------------------------------------- +// Physics attacker +//----------------------------------------------------------------------------- +CBasePlayer *CRagdollProp::HasPhysicsAttacker( float dt ) +{ + if (gpGlobals->curtime - dt <= m_flLastPhysicsInfluenceTime) + { + return m_hPhysicsAttacker; + } + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CRagdollProp::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) +{ + BaseClass::VPhysicsCollision( index, pEvent ); + + CBaseEntity *pHitEntity = pEvent->pEntities[!index]; + if ( pHitEntity == this ) + return; + + // Don't take physics damage from whoever's holding him with the physcannon. + if ( VPhysicsGetObject() && (VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD) ) + { + if ( pHitEntity && (pHitEntity == HasPhysicsAttacker( FLT_MAX )) ) + return; + } + + // Don't bother taking damage from the physics attacker + if ( pHitEntity && HasPhysicsAttacker( 0.5f ) == pHitEntity ) + return; + + if( m_bFirstCollisionAfterLaunch ) + { + HandleFirstCollisionInteractions( index, pEvent ); + } + + if ( m_takedamage != DAMAGE_NO ) + { + int damageType = 0; + float damage = CalculateDefaultPhysicsDamage( index, pEvent, 1.0f, true, damageType ); + if ( damage > 0 ) + { + // Take extra damage after we're punted by the physcannon + if ( m_bFirstCollisionAfterLaunch ) + { + damage *= 10; + } + + CBaseEntity *pHitEntity = pEvent->pEntities[!index]; + if ( !pHitEntity ) + { + // hit world + pHitEntity = GetContainingEntity( INDEXENT(0) ); + } + Vector damagePos; + pEvent->pInternalData->GetContactPoint( damagePos ); + Vector damageForce = pEvent->postVelocity[index] * pEvent->pObjects[index]->GetMass(); + if ( damageForce == vec3_origin ) + { + // This can happen if this entity is motion disabled, and can't move. + // Use the velocity of the entity that hit us instead. + damageForce = pEvent->postVelocity[!index] * pEvent->pObjects[!index]->GetMass(); + } + + // FIXME: this doesn't pass in who is responsible if some other entity "caused" this collision + PhysCallbackDamage( this, CTakeDamageInfo( pHitEntity, pHitEntity, damageForce, damagePos, damage, damageType ), *pEvent, index ); + } + } + + if ( m_bFirstCollisionAfterLaunch ) + { + // Setup the think function to remove the flags + SetThink( &CRagdollProp::ClearFlagsThink ); + SetNextThink( gpGlobals->curtime ); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CRagdollProp::HasPhysgunInteraction( const char *pszKeyName, const char *pszValue ) +{ + KeyValues *modelKeyValues = new KeyValues(""); + if ( modelKeyValues->LoadFromBuffer( modelinfo->GetModelName( GetModel() ), modelinfo->GetModelKeyValueText( GetModel() ) ) ) + { + KeyValues *pkvPropData = modelKeyValues->FindKey("physgun_interactions"); + if ( pkvPropData ) + { + char const *pszBase = pkvPropData->GetString( pszKeyName ); + + if ( pszBase && pszBase[0] && !stricmp( pszBase, pszValue ) ) + { + modelKeyValues->deleteThis(); + return true; + } + } + } + + modelKeyValues->deleteThis(); + return false; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CRagdollProp::HandleFirstCollisionInteractions( int index, gamevcollisionevent_t *pEvent ) +{ + IPhysicsObject *pObj = VPhysicsGetObject(); + if ( !pObj) + return; + + if( HasPhysgunInteraction( "onfirstimpact", "break" ) ) + { + // Looks like it's best to break by having the object damage itself. + CTakeDamageInfo info; + + info.SetDamage( m_iHealth ); + info.SetAttacker( this ); + info.SetInflictor( this ); + info.SetDamageType( DMG_GENERIC ); + + Vector vecPosition; + Vector vecVelocity; + + VPhysicsGetObject()->GetVelocity( &vecVelocity, NULL ); + VPhysicsGetObject()->GetPosition( &vecPosition, NULL ); + + info.SetDamageForce( vecVelocity ); + info.SetDamagePosition( vecPosition ); + + TakeDamage( info ); + return; + } + + if( HasPhysgunInteraction( "onfirstimpact", "paintsplat" ) ) + { + IPhysicsObject *pObj = VPhysicsGetObject(); + + Vector vecPos; + pObj->GetPosition( &vecPos, NULL ); + + trace_t tr; + UTIL_TraceLine( vecPos, vecPos + pEvent->preVelocity[0] * 1.5, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); + + switch( random->RandomInt( 1, 3 ) ) + { + case 1: + UTIL_DecalTrace( &tr, "PaintSplatBlue" ); + break; + + case 2: + UTIL_DecalTrace( &tr, "PaintSplatGreen" ); + break; + + case 3: + UTIL_DecalTrace( &tr, "PaintSplatPink" ); + break; + } + } + + bool bAlienBloodSplat = HasPhysgunInteraction( "onfirstimpact", "alienbloodsplat" ); + if( bAlienBloodSplat || HasPhysgunInteraction( "onfirstimpact", "bloodsplat" ) ) + { + IPhysicsObject *pObj = VPhysicsGetObject(); + + Vector vecPos; + pObj->GetPosition( &vecPos, NULL ); + + trace_t tr; + UTIL_TraceLine( vecPos, vecPos + pEvent->preVelocity[0] * 1.5, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); + + UTIL_BloodDecalTrace( &tr, bAlienBloodSplat ? BLOOD_COLOR_GREEN : BLOOD_COLOR_RED ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CRagdollProp::ClearFlagsThink( void ) +{ + PhysClearGameFlags( VPhysicsGetObject(), FVPHYSICS_WAS_THROWN ); + m_bFirstCollisionAfterLaunch = false; + SetThink( NULL ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +AngularImpulse CRagdollProp::PhysGunLaunchAngularImpulse() +{ + if( HasPhysgunInteraction( "onlaunch", "spin_zaxis" ) ) + { + // Don't add in random angular impulse if this object is supposed to spin in a specific way. + AngularImpulse ang( 0, 0, 0 ); + return ang; + } + + return CDefaultPlayerPickupVPhysics::PhysGunLaunchAngularImpulse(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : activity - +//----------------------------------------------------------------------------- +void CRagdollProp::SetOverlaySequence( Activity activity ) +{ + int seq = SelectWeightedSequence( activity ); + if ( seq < 0 ) + { + m_nOverlaySequence = -1; + } + else + { + m_nOverlaySequence = seq; + } +} + +void CRagdollProp::InitRagdoll( const Vector &forceVector, int forceBone, const Vector &forcePos, matrix3x4_t *pPrevBones, matrix3x4_t *pBoneToWorld, float dt, int collisionGroup, bool activateRagdoll, bool bWakeRagdoll ) +{ + SetCollisionGroup( collisionGroup ); + + // Make sure it's interactive debris for at most 5 seconds + if ( collisionGroup == COLLISION_GROUP_INTERACTIVE_DEBRIS ) + { + SetContextThink( &CRagdollProp::SetDebrisThink, gpGlobals->curtime + 5, s_pDebrisContext ); + } + + SetMoveType( MOVETYPE_VPHYSICS ); + SetSolid( SOLID_VPHYSICS ); + AddSolidFlags( FSOLID_CUSTOMRAYTEST | FSOLID_CUSTOMBOXTEST ); + m_takedamage = DAMAGE_EVENTS_ONLY; + + ragdollparams_t params; + params.pGameData = static_cast<void *>( static_cast<CBaseEntity *>(this) ); + params.modelIndex = GetModelIndex(); + params.pCollide = modelinfo->GetVCollide( params.modelIndex ); + params.pStudioHdr = GetModelPtr(); + params.forceVector = forceVector; + params.forceBoneIndex = forceBone; + params.forcePosition = forcePos; + params.pCurrentBones = pBoneToWorld; + params.jointFrictionScale = 1.0; + params.allowStretch = HasSpawnFlags(SF_RAGDOLLPROP_ALLOW_STRETCH); + params.fixedConstraints = false; + RagdollCreate( m_ragdoll, params, physenv ); + RagdollApplyAnimationAsVelocity( m_ragdoll, pPrevBones, pBoneToWorld, dt ); + if ( m_anglesOverrideString != NULL_STRING && Q_strlen(m_anglesOverrideString.ToCStr()) > 0 ) + { + char szToken[2048]; + const char *pStr = nexttoken(szToken, STRING(m_anglesOverrideString), ','); + // anglesOverride is index,angles,index,angles (e.g. "1, 22.5 123.0 0.0, 2, 0 0 0, 3, 0 0 180.0") + while ( szToken[0] != 0 ) + { + int objectIndex = atoi(szToken); + // sanity check to make sure this token is an integer + Assert( atof(szToken) == ((float)objectIndex) ); + pStr = nexttoken(szToken, pStr, ','); + Assert( szToken[0] ); + if ( objectIndex >= m_ragdoll.listCount ) + { + Warning("Bad ragdoll pose in entity %s, model (%s) at %s, model changed?\n", GetDebugName(), GetModelName().ToCStr(), VecToString(GetAbsOrigin()) ); + } + else if ( szToken[0] != 0 ) + { + QAngle angles; + Assert( objectIndex >= 0 && objectIndex < RAGDOLL_MAX_ELEMENTS ); + UTIL_StringToVector( angles.Base(), szToken ); + int boneIndex = m_ragdoll.boneIndex[objectIndex]; + AngleMatrix( angles, pBoneToWorld[boneIndex] ); + const ragdollelement_t &element = m_ragdoll.list[objectIndex]; + Vector out; + if ( element.parentIndex >= 0 ) + { + int parentBoneIndex = m_ragdoll.boneIndex[element.parentIndex]; + VectorTransform( element.originParentSpace, pBoneToWorld[parentBoneIndex], out ); + } + else + { + out = GetAbsOrigin(); + } + MatrixSetColumn( out, 3, pBoneToWorld[boneIndex] ); + element.pObject->SetPositionMatrix( pBoneToWorld[boneIndex], true ); + } + pStr = nexttoken(szToken, pStr, ','); + } + } + + if ( activateRagdoll ) + { + MEM_ALLOC_CREDIT(); + RagdollActivate( m_ragdoll, params.pCollide, GetModelIndex(), bWakeRagdoll ); + } + + for ( int i = 0; i < m_ragdoll.listCount; i++ ) + { + UpdateNetworkDataFromVPhysics( m_ragdoll.list[i].pObject, i ); + g_pPhysSaveRestoreManager->AssociateModel( m_ragdoll.list[i].pObject, GetModelIndex() ); + physcollision->CollideGetAABB( &m_ragdollMins[i], &m_ragdollMaxs[i], m_ragdoll.list[i].pObject->GetCollide(), vec3_origin, vec3_angle ); + } + VPhysicsSetObject( m_ragdoll.list[0].pObject ); + + CalcRagdollSize(); +} + +void CRagdollProp::SetDebrisThink() +{ + SetCollisionGroup( COLLISION_GROUP_DEBRIS ); + RecheckCollisionFilter(); +} + +void CRagdollProp::SetDamageEntity( CBaseEntity *pEntity ) +{ + // Damage passing + m_hDamageEntity = pEntity; + + // Set our takedamage to match it + if ( pEntity ) + { + m_takedamage = pEntity->m_takedamage; + } + else + { + m_takedamage = DAMAGE_EVENTS_ONLY; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CRagdollProp::OnTakeDamage( const CTakeDamageInfo &info ) +{ + // If we have a damage entity, we want to pass damage to it. Add the + // Never Ragdoll flag, on the assumption that if the entity dies, we'll + // actually be taking the role of its ragdoll. + if ( m_hDamageEntity.Get() ) + { + CTakeDamageInfo subInfo = info; + subInfo.AddDamageType( DMG_REMOVENORAGDOLL ); + return m_hDamageEntity->OnTakeDamage( subInfo ); + } + + return BaseClass::OnTakeDamage( info ); +} + +//----------------------------------------------------------------------------- +// Purpose: Force all the ragdoll's bone's physics objects to recheck their collision filters +//----------------------------------------------------------------------------- +void CRagdollProp::RecheckCollisionFilter( void ) +{ + for ( int i = 0; i < m_ragdoll.listCount; i++ ) + { + m_ragdoll.list[i].pObject->RecheckCollisionFilter(); + } +} + + +void CRagdollProp::TraceAttack( const CTakeDamageInfo &info, const Vector &dir, trace_t *ptr, CDmgAccumulator *pAccumulator ) +{ + if ( ptr->physicsbone >= 0 && ptr->physicsbone < m_ragdoll.listCount ) + { + VPhysicsSwapObject( m_ragdoll.list[ptr->physicsbone].pObject ); + } + BaseClass::TraceAttack( info, dir, ptr, pAccumulator ); +} + +void CRagdollProp::SetupBones( matrix3x4_t *pBoneToWorld, int boneMask ) +{ + // no ragdoll, fall through to base class + if ( !m_ragdoll.listCount ) + { + BaseClass::SetupBones( pBoneToWorld, boneMask ); + return; + } + + // Not really ideal, but it'll work for now + UpdateModelScale(); + + MDLCACHE_CRITICAL_SECTION(); + CStudioHdr *pStudioHdr = GetModelPtr( ); + bool sim[MAXSTUDIOBONES]; + memset( sim, 0, pStudioHdr->numbones() ); + + int i; + + CBoneAccessor boneaccessor( pBoneToWorld ); + for ( i = 0; i < m_ragdoll.listCount; i++ ) + { + // during restore this may be NULL + if ( !m_ragdoll.list[i].pObject ) + continue; + + if ( RagdollGetBoneMatrix( m_ragdoll, boneaccessor, i ) ) + { + sim[m_ragdoll.boneIndex[i]] = true; + } + } + + mstudiobone_t *pbones = pStudioHdr->pBone( 0 ); + for ( i = 0; i < pStudioHdr->numbones(); i++ ) + { + if ( sim[i] ) + continue; + + if ( !(pStudioHdr->boneFlags(i) & boneMask) ) + continue; + + matrix3x4_t matBoneLocal; + AngleMatrix( pbones[i].rot, pbones[i].pos, matBoneLocal ); + ConcatTransforms( pBoneToWorld[pbones[i].parent], matBoneLocal, pBoneToWorld[i]); + } +} + +bool CRagdollProp::TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace ) +{ +#if 0 + // PERFORMANCE: Use hitboxes for rays instead of vcollides if this is a performance problem + if ( ray.m_IsRay ) + { + return BaseClass::TestCollision( ray, mask, trace ); + } +#endif + + CStudioHdr *pStudioHdr = GetModelPtr( ); + if (!pStudioHdr) + return false; + + // Just iterate all of the elements and trace the box against each one. + // NOTE: This is pretty expensive for small/dense characters + trace_t tr; + for ( int i = 0; i < m_ragdoll.listCount; i++ ) + { + Vector position; + QAngle angles; + + if( m_ragdoll.list[i].pObject ) + { + m_ragdoll.list[i].pObject->GetPosition( &position, &angles ); + physcollision->TraceBox( ray, m_ragdoll.list[i].pObject->GetCollide(), position, angles, &tr ); + + if ( tr.fraction < trace.fraction ) + { + tr.physicsbone = i; + tr.surface.surfaceProps = m_ragdoll.list[i].pObject->GetMaterialIndex(); + trace = tr; + } + } + else + { + DevWarning("Bogus object in Ragdoll Prop's ragdoll list!\n"); + } + } + + if ( trace.fraction >= 1 ) + { + return false; + } + + return true; +} + + +void CRagdollProp::Teleport( const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity ) +{ + // newAngles is a relative transform for the entity + // But a ragdoll entity has identity orientation by design + // so we compute a relative transform here based on the previous transform + matrix3x4_t startMatrixInv; + MatrixInvert( EntityToWorldTransform(), startMatrixInv ); + matrix3x4_t endMatrix; + MatrixCopy( EntityToWorldTransform(), endMatrix ); + if ( newAngles ) + { + AngleMatrix( *newAngles, endMatrix ); + } + if ( newPosition ) + { + PositionMatrix( *newPosition, endMatrix ); + } + // now endMatrix is the refernce matrix for the entity at the target position + matrix3x4_t xform; + ConcatTransforms( endMatrix, startMatrixInv, xform ); + // now xform is the relative transform the entity must undergo + + // we need to call the base class and it will teleport our vphysics object, + // so set object 0 up and compute the origin/angles for its new position (base implementation has side effects) + VPhysicsSwapObject( m_ragdoll.list[0].pObject ); + matrix3x4_t obj0source, obj0Target; + m_ragdoll.list[0].pObject->GetPositionMatrix( &obj0source ); + ConcatTransforms( xform, obj0source, obj0Target ); + Vector obj0Pos; + QAngle obj0Angles; + MatrixAngles( obj0Target, obj0Angles, obj0Pos ); + BaseClass::Teleport( &obj0Pos, &obj0Angles, newVelocity ); + + for ( int i = 1; i < m_ragdoll.listCount; i++ ) + { + matrix3x4_t matrix, newMatrix; + m_ragdoll.list[i].pObject->GetPositionMatrix( &matrix ); + ConcatTransforms( xform, matrix, newMatrix ); + m_ragdoll.list[i].pObject->SetPositionMatrix( newMatrix, true ); + UpdateNetworkDataFromVPhysics( m_ragdoll.list[i].pObject, i ); + } + // fixup/relink object 0 + UpdateNetworkDataFromVPhysics( m_ragdoll.list[0].pObject, 0 ); +} + +void CRagdollProp::VPhysicsUpdate( IPhysicsObject *pPhysics ) +{ + if ( m_lastUpdateTickCount == (unsigned int)gpGlobals->tickcount ) + return; + + m_lastUpdateTickCount = gpGlobals->tickcount; + //NetworkStateChanged(); + + matrix3x4_t boneToWorld[MAXSTUDIOBONES]; + QAngle angles; + Vector surroundingMins, surroundingMaxs; + + int i; + for ( i = 0; i < m_ragdoll.listCount; i++ ) + { + CBoneAccessor boneaccessor( boneToWorld ); + if ( RagdollGetBoneMatrix( m_ragdoll, boneaccessor, i ) ) + { + Vector vNewPos; + MatrixAngles( boneToWorld[m_ragdoll.boneIndex[i]], angles, vNewPos ); + m_ragPos.Set( i, vNewPos ); + m_ragAngles.Set( i, angles ); + } + else + { + m_ragPos.GetForModify(i).Init(); + m_ragAngles.GetForModify(i).Init(); + } + } + + // BUGBUG: Use the ragdollmins/maxs to do this instead of the collides + m_allAsleep = RagdollIsAsleep( m_ragdoll ); + + // Don't scream after you've come to rest + if ( m_allAsleep ) + { + m_strSourceClassName = NULL_STRING; + } + else + { + if ( m_ragdoll.pGroup->IsInErrorState() ) + { + RagdollSolveSeparation( m_ragdoll, this ); + } + } + + // Interactive debris converts back to debris when it comes to rest + if ( m_allAsleep && GetCollisionGroup() == COLLISION_GROUP_INTERACTIVE_DEBRIS ) + { + SetCollisionGroup( COLLISION_GROUP_DEBRIS ); + RecheckCollisionFilter(); + SetContextThink( NULL, gpGlobals->curtime, s_pDebrisContext ); + } + + Vector vecFullMins, vecFullMaxs; + vecFullMins = m_ragPos[0]; + vecFullMaxs = m_ragPos[0]; + for ( i = 0; i < m_ragdoll.listCount; i++ ) + { + Vector mins, maxs; + matrix3x4_t update; + if ( !m_ragdoll.list[i].pObject ) + { + m_ragdollMins[i].Init(); + m_ragdollMaxs[i].Init(); + continue; + } + m_ragdoll.list[i].pObject->GetPositionMatrix( &update ); + TransformAABB( update, m_ragdollMins[i], m_ragdollMaxs[i], mins, maxs ); + for ( int j = 0; j < 3; j++ ) + { + if ( mins[j] < vecFullMins[j] ) + { + vecFullMins[j] = mins[j]; + } + if ( maxs[j] > vecFullMaxs[j] ) + { + vecFullMaxs[j] = maxs[j]; + } + } + } + + SetAbsOrigin( m_ragPos[0] ); + SetAbsAngles( vec3_angle ); + const Vector &vecOrigin = CollisionProp()->GetCollisionOrigin(); + CollisionProp()->AddSolidFlags( FSOLID_FORCE_WORLD_ALIGNED ); + CollisionProp()->SetSurroundingBoundsType( USE_COLLISION_BOUNDS_NEVER_VPHYSICS ); + SetCollisionBounds( vecFullMins - vecOrigin, vecFullMaxs - vecOrigin ); + CollisionProp()->MarkSurroundingBoundsDirty(); + + PhysicsTouchTriggers(); +} + +int CRagdollProp::VPhysicsGetObjectList( IPhysicsObject **pList, int listMax ) +{ + for ( int i = 0; i < m_ragdoll.listCount; i++ ) + { + if ( i < listMax ) + { + pList[i] = m_ragdoll.list[i].pObject; + } + } + + return m_ragdoll.listCount; +} + +void CRagdollProp::UpdateNetworkDataFromVPhysics( IPhysicsObject *pPhysics, int index ) +{ + Assert(index < m_ragdoll.listCount); + + QAngle angles; + Vector vPos; + m_ragdoll.list[index].pObject->GetPosition( &vPos, &angles ); + m_ragPos.Set( index, vPos ); + m_ragAngles.Set( index, angles ); + + // move/relink if root moved + if ( index == 0 ) + { + SetAbsOrigin( m_ragPos[0] ); + PhysicsTouchTriggers(); + } +} + + +//----------------------------------------------------------------------------- +// Fade out due to the LRU telling it do +//----------------------------------------------------------------------------- +#define FADE_OUT_LENGTH 0.5f + +void CRagdollProp::FadeOut( float flDelay, float fadeTime ) +{ + if ( IsFading() ) + return; + + m_flFadeTime = ( fadeTime == -1 ) ? FADE_OUT_LENGTH : fadeTime; + + m_flFadeOutStartTime = gpGlobals->curtime + flDelay; + m_flFadeScale = 0; + SetContextThink( &CRagdollProp::FadeOutThink, gpGlobals->curtime + flDelay + 0.01f, s_pFadeOutContext ); +} + +bool CRagdollProp::IsFading() +{ + return ( GetNextThink( s_pFadeOutContext ) >= gpGlobals->curtime ); +} + +void CRagdollProp::FadeOutThink(void) +{ + float dt = gpGlobals->curtime - m_flFadeOutStartTime; + if ( dt < 0 ) + { + SetContextThink( &CRagdollProp::FadeOutThink, gpGlobals->curtime + 0.1, s_pFadeOutContext ); + } + else if ( dt < m_flFadeTime ) + { + float alpha = 1.0f - dt / m_flFadeTime; + int nFade = (int)(alpha * 255.0f); + m_nRenderMode = kRenderTransTexture; + SetRenderColorA( nFade ); + NetworkStateChanged(); + SetContextThink( &CRagdollProp::FadeOutThink, gpGlobals->curtime + TICK_INTERVAL, s_pFadeOutContext ); + } + else + { + // Necessary to cause it to do the appropriate death cleanup + // Yeah, the player may have nothing to do with it, but + // passing NULL to TakeDamage causes bad things to happen + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + CTakeDamageInfo info( pPlayer, pPlayer, 10000.0, DMG_GENERIC ); + TakeDamage( info ); + UTIL_Remove( this ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Draw any debug text overlays +// Output : Current text offset from the top +//----------------------------------------------------------------------------- +int CRagdollProp::DrawDebugTextOverlays(void) +{ + int text_offset = BaseClass::DrawDebugTextOverlays(); + + if (m_debugOverlays & OVERLAY_TEXT_BIT) + { + if (m_ragdoll.listCount) + { + float mass = 0; + for ( int i = 0; i < m_ragdoll.listCount; i++ ) + { + if ( m_ragdoll.list[i].pObject != NULL ) + { + mass += m_ragdoll.list[i].pObject->GetMass(); + } + } + + char tempstr[512]; + Q_snprintf(tempstr, sizeof(tempstr),"Mass: %.2f kg / %.2f lb (%s)", mass, kg2lbs(mass), GetMassEquivalent(mass) ); + EntityText( text_offset, tempstr, 0); + text_offset++; + } + } + + return text_offset; +} + +void CRagdollProp::DrawDebugGeometryOverlays() +{ + if (m_debugOverlays & OVERLAY_BBOX_BIT) + { + DrawServerHitboxes(); + } + if (m_debugOverlays & OVERLAY_PIVOT_BIT) + { + for ( int i = 0; i < m_ragdoll.listCount; i++ ) + { + if ( m_ragdoll.list[i].pObject ) + { + float mass = m_ragdoll.list[i].pObject->GetMass(); + Vector pos; + m_ragdoll.list[i].pObject->GetPosition( &pos, NULL ); + CFmtStr str("mass %.1f", mass ); + NDebugOverlay::EntityTextAtPosition( pos, 0, str.Access(), 0, 0, 255, 0, 255 ); + } + } + } + BaseClass::DrawDebugGeometryOverlays(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pOther - +//----------------------------------------------------------------------------- +void CRagdollProp::SetUnragdoll( CBaseAnimating *pOther ) +{ + m_hUnragdoll = pOther; +} + +//=============================================================================================================== +// RagdollPropAttached +//=============================================================================================================== +class CRagdollPropAttached : public CRagdollProp +{ + DECLARE_CLASS( CRagdollPropAttached, CRagdollProp ); +public: + + CRagdollPropAttached() + { + m_bShouldDetach = false; + } + + ~CRagdollPropAttached() + { + physenv->DestroyConstraint( m_pAttachConstraint ); + m_pAttachConstraint = NULL; + } + + void InitRagdollAttached( IPhysicsObject *pAttached, const Vector &forceVector, int forceBone, matrix3x4_t *pPrevBones, matrix3x4_t *pBoneToWorld, float dt, int collisionGroup, CBaseAnimating *pFollow, int boneIndexRoot, const Vector &boneLocalOrigin, int parentBoneAttach, const Vector &worldAttachOrigin ); + void DetachOnNextUpdate(); + void VPhysicsUpdate( IPhysicsObject *pPhysics ); + + DECLARE_SERVERCLASS(); + DECLARE_DATADESC(); + +private: + void Detach(); + CNetworkVar( int, m_boneIndexAttached ); + CNetworkVar( int, m_ragdollAttachedObjectIndex ); + CNetworkVector( m_attachmentPointBoneSpace ); + CNetworkVector( m_attachmentPointRagdollSpace ); + bool m_bShouldDetach; + IPhysicsConstraint *m_pAttachConstraint; +}; + +LINK_ENTITY_TO_CLASS( prop_ragdoll_attached, CRagdollPropAttached ); +EXTERN_SEND_TABLE(DT_Ragdoll_Attached) + +IMPLEMENT_SERVERCLASS_ST(CRagdollPropAttached, DT_Ragdoll_Attached) + SendPropInt( SENDINFO( m_boneIndexAttached ), MAXSTUDIOBONEBITS, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_ragdollAttachedObjectIndex ), RAGDOLL_INDEX_BITS, SPROP_UNSIGNED ), + SendPropVector(SENDINFO(m_attachmentPointBoneSpace), -1, SPROP_COORD ), + SendPropVector(SENDINFO(m_attachmentPointRagdollSpace), -1, SPROP_COORD ), +END_SEND_TABLE() + +BEGIN_DATADESC(CRagdollPropAttached) + DEFINE_FIELD( m_boneIndexAttached, FIELD_INTEGER ), + DEFINE_FIELD( m_ragdollAttachedObjectIndex, FIELD_INTEGER ), + DEFINE_FIELD( m_attachmentPointBoneSpace, FIELD_VECTOR ), + DEFINE_FIELD( m_attachmentPointRagdollSpace, FIELD_VECTOR ), + DEFINE_FIELD( m_bShouldDetach, FIELD_BOOLEAN ), + DEFINE_PHYSPTR( m_pAttachConstraint ), +END_DATADESC() + + +static void SyncAnimatingWithPhysics( CBaseAnimating *pAnimating ) +{ + IPhysicsObject *pPhysics = pAnimating->VPhysicsGetObject(); + if ( pPhysics ) + { + Vector pos; + pPhysics->GetShadowPosition( &pos, NULL ); + pAnimating->SetAbsOrigin( pos ); + } +} + + +CBaseAnimating *CreateServerRagdollSubmodel( CBaseAnimating *pOwner, const char *pModelName, const Vector &position, const QAngle &angles, int collisionGroup ) +{ + CRagdollProp *pRagdoll = (CRagdollProp *)CBaseEntity::CreateNoSpawn( "prop_ragdoll", position, angles, pOwner ); + pRagdoll->SetModelName( AllocPooledString( pModelName ) ); + pRagdoll->SetModel( STRING(pRagdoll->GetModelName()) ); + matrix3x4_t pBoneToWorld[MAXSTUDIOBONES], pBoneToWorldNext[MAXSTUDIOBONES]; + pRagdoll->ResetSequence( 0 ); + + // let bone merging do the work of copying everything over for us + pRagdoll->SetParent( pOwner ); + pRagdoll->SetupBones( pBoneToWorld, BONE_USED_BY_ANYTHING ); + // HACKHACK: don't want this parent anymore + pRagdoll->SetParent( NULL ); + + memcpy( pBoneToWorldNext, pBoneToWorld, sizeof(pBoneToWorld) ); + + pRagdoll->InitRagdoll( vec3_origin, -1, vec3_origin, pBoneToWorld, pBoneToWorldNext, 0.1, collisionGroup, true ); + return pRagdoll; +} + + +CBaseEntity *CreateServerRagdoll( CBaseAnimating *pAnimating, int forceBone, const CTakeDamageInfo &info, int collisionGroup, bool bUseLRURetirement ) +{ + if ( info.GetDamageType() & (DMG_VEHICLE|DMG_CRUSH) ) + { + // if the entity was killed by physics or a vehicle, move to the vphysics shadow position before creating the ragdoll. + SyncAnimatingWithPhysics( pAnimating ); + } + CRagdollProp *pRagdoll = (CRagdollProp *)CBaseEntity::CreateNoSpawn( "prop_ragdoll", pAnimating->GetAbsOrigin(), vec3_angle, NULL ); + pRagdoll->CopyAnimationDataFrom( pAnimating ); + pRagdoll->SetOwnerEntity( pAnimating ); + + pRagdoll->InitRagdollAnimation(); + matrix3x4_t pBoneToWorld[MAXSTUDIOBONES], pBoneToWorldNext[MAXSTUDIOBONES]; + + float dt = 0.1f; + + // Copy over dissolve state... + if ( pAnimating->IsEFlagSet( EFL_NO_DISSOLVE ) ) + { + pRagdoll->AddEFlags( EFL_NO_DISSOLVE ); + } + + // NOTE: This currently is only necessary to prevent manhacks from + // colliding with server ragdolls they kill + pRagdoll->SetKiller( info.GetInflictor() ); + pRagdoll->SetSourceClassName( pAnimating->GetClassname() ); + + // NPC_STATE_DEAD npc's will have their COND_IN_PVS cleared, so this needs to force SetupBones to happen + unsigned short fPrevFlags = pAnimating->GetBoneCacheFlags(); + pAnimating->SetBoneCacheFlags( BCF_NO_ANIMATION_SKIP ); + + // UNDONE: Extract velocity from bones via animation (like we do on the client) + // UNDONE: For now, just move each bone by the total entity velocity if set. + // Get Bones positions before + // Store current cycle + float fSequenceDuration = pAnimating->SequenceDuration( pAnimating->GetSequence() ); + float fSequenceTime = pAnimating->GetCycle() * fSequenceDuration; + + if( fSequenceTime <= dt && fSequenceTime > 0.0f ) + { + // Avoid having negative cycle + dt = fSequenceTime; + } + + float fPreviousCycle = clamp(pAnimating->GetCycle()-( dt * ( 1 / fSequenceDuration ) ),0.f,1.f); + float fCurCycle = pAnimating->GetCycle(); + // Get current bones positions + pAnimating->SetupBones( pBoneToWorldNext, BONE_USED_BY_ANYTHING ); + // Get previous bones positions + pAnimating->SetCycle( fPreviousCycle ); + pAnimating->SetupBones( pBoneToWorld, BONE_USED_BY_ANYTHING ); + // Restore current cycle + pAnimating->SetCycle( fCurCycle ); + + // Reset previous bone flags + pAnimating->ClearBoneCacheFlags( BCF_NO_ANIMATION_SKIP ); + pAnimating->SetBoneCacheFlags( fPrevFlags ); + + Vector vel = pAnimating->GetAbsVelocity(); + if( ( vel.Length() == 0 ) && ( dt > 0 ) ) + { + // Compute animation velocity + CStudioHdr *pstudiohdr = pAnimating->GetModelPtr(); + if ( pstudiohdr ) + { + Vector deltaPos; + QAngle deltaAngles; + if (Studio_SeqMovement( pstudiohdr, + pAnimating->GetSequence(), + fPreviousCycle, + pAnimating->GetCycle(), + pAnimating->GetPoseParameterArray(), + deltaPos, + deltaAngles )) + { + VectorRotate( deltaPos, pAnimating->EntityToWorldTransform(), vel ); + vel /= dt; + } + } + } + + if ( vel.LengthSqr() > 0 ) + { + int numbones = pAnimating->GetModelPtr()->numbones(); + vel *= dt; + for ( int i = 0; i < numbones; i++ ) + { + Vector pos; + MatrixGetColumn( pBoneToWorld[i], 3, pos ); + pos -= vel; + MatrixSetColumn( pos, 3, pBoneToWorld[i] ); + } + } + +#if RAGDOLL_VISUALIZE + pAnimating->DrawRawSkeleton( pBoneToWorld, BONE_USED_BY_ANYTHING, true, 20, false ); + pAnimating->DrawRawSkeleton( pBoneToWorldNext, BONE_USED_BY_ANYTHING, true, 20, true ); +#endif + // Is this a vehicle / NPC collision? + if ( (info.GetDamageType() & DMG_VEHICLE) && pAnimating->MyNPCPointer() ) + { + // init the ragdoll with no forces + pRagdoll->InitRagdoll( vec3_origin, -1, vec3_origin, pBoneToWorld, pBoneToWorldNext, dt, collisionGroup, true ); + + // apply vehicle forces + // Get a list of bones with hitboxes below the plane of impact + int boxList[128]; + Vector normal(0,0,-1); + int count = pAnimating->GetHitboxesFrontside( boxList, ARRAYSIZE(boxList), normal, DotProduct( normal, info.GetDamagePosition() ) ); + + // distribute force over mass of entire character + float massScale = Studio_GetMass(pAnimating->GetModelPtr()); + massScale = clamp( massScale, 1.f, 1.e4f ); + massScale = 1.f / massScale; + + // distribute the force + // BUGBUG: This will hit the same bone twice if it has two hitboxes!!!! + ragdoll_t *pRagInfo = pRagdoll->GetRagdoll(); + for ( int i = 0; i < count; i++ ) + { + int physBone = pAnimating->GetPhysicsBone( pAnimating->GetHitboxBone( boxList[i] ) ); + IPhysicsObject *pPhysics = pRagInfo->list[physBone].pObject; + pPhysics->ApplyForceCenter( info.GetDamageForce() * pPhysics->GetMass() * massScale ); + } + } + else + { + pRagdoll->InitRagdoll( info.GetDamageForce(), forceBone, info.GetDamagePosition(), pBoneToWorld, pBoneToWorldNext, dt, collisionGroup, true ); + } + + // Are we dissolving? + if ( pAnimating->IsDissolving() ) + { + pRagdoll->TransferDissolveFrom( pAnimating ); + } + else if ( bUseLRURetirement ) + { + pRagdoll->AddSpawnFlags( SF_RAGDOLLPROP_USE_LRU_RETIREMENT ); + s_RagdollLRU.MoveToTopOfLRU( pRagdoll ); + } + + // Tracker 22598: If we don't set the OBB mins/maxs to something valid here, then the client will have a zero sized hull + // for the ragdoll for one frame until Vphysics updates the real obb bounds after the first simulation frame. Having + // a zero sized hull makes the ragdoll think it should be faded/alpha'd to zero for a frame, so you get a blink where + // the ragdoll doesn't draw initially. + Vector mins, maxs; + mins = pAnimating->CollisionProp()->OBBMins(); + maxs = pAnimating->CollisionProp()->OBBMaxs(); + pRagdoll->CollisionProp()->SetCollisionBounds( mins, maxs ); + + return pRagdoll; +} + +void CRagdollPropAttached::DetachOnNextUpdate() +{ + m_bShouldDetach = true; +} + +void CRagdollPropAttached::VPhysicsUpdate( IPhysicsObject *pPhysics ) +{ + if ( m_bShouldDetach ) + { + Detach(); + m_bShouldDetach = false; + } + BaseClass::VPhysicsUpdate( pPhysics ); +} + +void CRagdollPropAttached::Detach() +{ + SetParent(NULL); + SetOwnerEntity( NULL ); + SetAbsAngles( vec3_angle ); + SetMoveType( MOVETYPE_VPHYSICS ); + RemoveSolidFlags( FSOLID_NOT_SOLID ); + physenv->DestroyConstraint( m_pAttachConstraint ); + m_pAttachConstraint = NULL; + const float dampingScale = 1.0f / ATTACHED_DAMPING_SCALE; + for ( int i = 0; i < m_ragdoll.listCount; i++ ) + { + float damping, rotdamping; + m_ragdoll.list[i].pObject->GetDamping( &damping, &rotdamping ); + damping *= dampingScale; + rotdamping *= dampingScale; + m_ragdoll.list[i].pObject->SetDamping( &damping, &damping ); + } + + // Go non-solid + SetCollisionGroup( COLLISION_GROUP_DEBRIS ); + RecheckCollisionFilter(); +} + +void CRagdollPropAttached::InitRagdollAttached( + IPhysicsObject *pAttached, + const Vector &forceVector, + int forceBone, + matrix3x4_t *pPrevBones, + matrix3x4_t *pBoneToWorld, + float dt, + int collisionGroup, + CBaseAnimating *pFollow, + int boneIndexRoot, + const Vector &boneLocalOrigin, + int parentBoneAttach, + const Vector &worldAttachOrigin ) +{ + int ragdollAttachedIndex = 0; + if ( parentBoneAttach > 0 ) + { + CStudioHdr *pStudioHdr = GetModelPtr(); + mstudiobone_t *pBone = pStudioHdr->pBone( parentBoneAttach ); + ragdollAttachedIndex = pBone->physicsbone; + } + + InitRagdoll( forceVector, forceBone, vec3_origin, pPrevBones, pBoneToWorld, dt, collisionGroup, false ); + + IPhysicsObject *pRefObject = m_ragdoll.list[ragdollAttachedIndex].pObject; + + Vector attachmentPointRagdollSpace; + pRefObject->WorldToLocal( &attachmentPointRagdollSpace, worldAttachOrigin ); + + constraint_ragdollparams_t constraint; + constraint.Defaults(); + matrix3x4_t tmp, worldToAttached, worldToReference, constraintToWorld; + + Vector offsetWS; + pAttached->LocalToWorld( &offsetWS, boneLocalOrigin ); + + QAngle followAng = QAngle(0, pFollow->GetAbsAngles().y, 0 ); + AngleMatrix( followAng, offsetWS, constraintToWorld ); + + constraint.axes[0].SetAxisFriction( -2, 2, 20 ); + constraint.axes[1].SetAxisFriction( 0, 0, 0 ); + constraint.axes[2].SetAxisFriction( -15, 15, 20 ); + + // Exaggerate the bone's ability to pull the mass of the ragdoll around + constraint.constraint.bodyMassScale[1] = 50.0f; + + pAttached->GetPositionMatrix( &tmp ); + MatrixInvert( tmp, worldToAttached ); + + pRefObject->GetPositionMatrix( &tmp ); + MatrixInvert( tmp, worldToReference ); + + ConcatTransforms( worldToReference, constraintToWorld, constraint.constraintToReference ); + ConcatTransforms( worldToAttached, constraintToWorld, constraint.constraintToAttached ); + + // for now, just slam this to be the passed in value + MatrixSetColumn( attachmentPointRagdollSpace, 3, constraint.constraintToReference ); + + PhysDisableEntityCollisions( pAttached, m_ragdoll.list[0].pObject ); + m_pAttachConstraint = physenv->CreateRagdollConstraint( pRefObject, pAttached, m_ragdoll.pGroup, constraint ); + + SetParent( pFollow ); + SetOwnerEntity( pFollow ); + + RagdollActivate( m_ragdoll, modelinfo->GetVCollide( GetModelIndex() ), GetModelIndex() ); + + // add a bunch of dampening to the ragdoll + for ( int i = 0; i < m_ragdoll.listCount; i++ ) + { + float damping, rotdamping; + m_ragdoll.list[i].pObject->GetDamping( &damping, &rotdamping ); + damping *= ATTACHED_DAMPING_SCALE; + rotdamping *= ATTACHED_DAMPING_SCALE; + m_ragdoll.list[i].pObject->SetDamping( &damping, &rotdamping ); + } + + m_boneIndexAttached = boneIndexRoot; + m_ragdollAttachedObjectIndex = ragdollAttachedIndex; + m_attachmentPointBoneSpace = boneLocalOrigin; + + Vector vTemp; + MatrixGetColumn( constraint.constraintToReference, 3, vTemp ); + m_attachmentPointRagdollSpace = vTemp; +} + +CRagdollProp *CreateServerRagdollAttached( CBaseAnimating *pAnimating, const Vector &vecForce, int forceBone, int collisionGroup, IPhysicsObject *pAttached, CBaseAnimating *pParentEntity, int boneAttach, const Vector &originAttached, int parentBoneAttach, const Vector &boneOrigin ) +{ + // Return immediately if the model doesn't have a vcollide + if ( modelinfo->GetVCollide( pAnimating->GetModelIndex() ) == NULL ) + return NULL; + + CRagdollPropAttached *pRagdoll = (CRagdollPropAttached *)CBaseEntity::CreateNoSpawn( "prop_ragdoll_attached", pAnimating->GetAbsOrigin(), vec3_angle, NULL ); + pRagdoll->CopyAnimationDataFrom( pAnimating ); + + pRagdoll->InitRagdollAnimation(); + matrix3x4_t pBoneToWorld[MAXSTUDIOBONES]; + pAnimating->SetupBones( pBoneToWorld, BONE_USED_BY_ANYTHING ); + pRagdoll->InitRagdollAttached( pAttached, vecForce, forceBone, pBoneToWorld, pBoneToWorld, 0.1, collisionGroup, pParentEntity, boneAttach, boneOrigin, parentBoneAttach, originAttached ); + + return pRagdoll; +} + +void DetachAttachedRagdoll( CBaseEntity *pRagdollIn ) +{ + CRagdollPropAttached *pRagdoll = dynamic_cast<CRagdollPropAttached *>(pRagdollIn); + + if ( pRagdoll ) + { + pRagdoll->DetachOnNextUpdate(); + } +} + +void DetachAttachedRagdollsForEntity( CBaseEntity *pRagdollParent ) +{ + CUtlVector<CBaseEntity *> list; + GetAllChildren( pRagdollParent, list ); + for ( int i = list.Count()-1; i >= 0; --i ) + { + DetachAttachedRagdoll( list[i] ); + } +} + +bool Ragdoll_IsPropRagdoll( CBaseEntity *pEntity ) +{ + if ( dynamic_cast<CRagdollProp *>(pEntity) != NULL ) + return true; + return false; +} + +ragdoll_t *Ragdoll_GetRagdoll( CBaseEntity *pEntity ) +{ + CRagdollProp *pProp = dynamic_cast<CRagdollProp *>(pEntity); + if ( pProp ) + return pProp->GetRagdoll(); + return NULL; +} + +void CRagdollProp::GetAngleOverrideFromCurrentState( char *pOut, int size ) +{ + pOut[0] = 0; + for ( int i = 0; i < m_ragdoll.listCount; i++ ) + { + if ( i != 0 ) + { + Q_strncat( pOut, ",", size, COPY_ALL_CHARACTERS ); + + } + CFmtStr str("%d,%.2f %.2f %.2f", i, m_ragAngles[i].x, m_ragAngles[i].y, m_ragAngles[i].z ); + Q_strncat( pOut, str, size, COPY_ALL_CHARACTERS ); + } +} + +void CRagdollProp::DisableMotion( void ) +{ + for ( int iRagdoll = 0; iRagdoll < m_ragdoll.listCount; ++iRagdoll ) + { + IPhysicsObject *pPhysicsObject = m_ragdoll.list[ iRagdoll ].pObject; + if ( pPhysicsObject != NULL ) + { + pPhysicsObject->EnableMotion( false ); + } + } +} + +void CRagdollProp::InputStartRadgollBoogie( inputdata_t &inputdata ) +{ + float duration = inputdata.value.Float(); + + if( duration <= 0.0f ) + { + duration = 5.0f; + } + + CRagdollBoogie::Create( this, 100, gpGlobals->curtime, duration, 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: Enable physics motion and collision response (on by default) +//----------------------------------------------------------------------------- +void CRagdollProp::InputEnableMotion( inputdata_t &inputdata ) +{ + for ( int iRagdoll = 0; iRagdoll < m_ragdoll.listCount; ++iRagdoll ) + { + IPhysicsObject *pPhysicsObject = m_ragdoll.list[ iRagdoll ].pObject; + if ( pPhysicsObject != NULL ) + { + pPhysicsObject->EnableMotion( true ); + pPhysicsObject->Wake(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Disable any physics motion or collision response +//----------------------------------------------------------------------------- +void CRagdollProp::InputDisableMotion( inputdata_t &inputdata ) +{ + DisableMotion(); +} + +void CRagdollProp::InputTurnOn( inputdata_t &inputdata ) +{ + RemoveEffects( EF_NODRAW ); +} + +void CRagdollProp::InputTurnOff( inputdata_t &inputdata ) +{ + AddEffects( EF_NODRAW ); +} + +void CRagdollProp::InputFadeAndRemove( inputdata_t &inputdata ) +{ + float flFadeDuration = inputdata.value.Float(); + + if( flFadeDuration == 0.0f ) + flFadeDuration = 1.0f; + + FadeOut( 0.0f, flFadeDuration ); +} + +void Ragdoll_GetAngleOverrideString( char *pOut, int size, CBaseEntity *pEntity ) +{ + CRagdollProp *pRagdoll = dynamic_cast<CRagdollProp *>(pEntity); + if ( pRagdoll ) + { + pRagdoll->GetAngleOverrideFromCurrentState( pOut, size ); + } +} |