Kamay Xutax
2e5867b300
This cause looping issues due to cycle, until I find a proper solution for it, it is disabled for accuracy purposes and also because we can't trust the client. This is really hard to see though with high tickrate.
6519 lines
179 KiB
C++
6519 lines
179 KiB
C++
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//===========================================================================//
|
|
#include "cbase.h"
|
|
#include "c_baseanimating.h"
|
|
#include "c_sprite.h"
|
|
#include "cdll_client_int.h"
|
|
#include "interpolatedvar.h"
|
|
#include "model_types.h"
|
|
#include "bone_setup.h"
|
|
#include "ivrenderview.h"
|
|
#include "r_efx.h"
|
|
#include "dlight.h"
|
|
#include "beamdraw.h"
|
|
#include "cl_animevent.h"
|
|
#include "engine/IEngineSound.h"
|
|
#include "c_te_legacytempents.h"
|
|
#include "activitylist.h"
|
|
#include "animation.h"
|
|
#include "tier0/vprof.h"
|
|
#include "clienteffectprecachesystem.h"
|
|
#include "IEffects.h"
|
|
#include "engine/ivmodelinfo.h"
|
|
#include "engine/ivdebugoverlay.h"
|
|
#include "c_te_effect_dispatch.h"
|
|
#include <KeyValues.h>
|
|
#include "c_rope.h"
|
|
#include "isaverestore.h"
|
|
#include "datacache/imdlcache.h"
|
|
#include "eventlist.h"
|
|
#include "saverestore.h"
|
|
#include "physics_saverestore.h"
|
|
#include "vphysics/constraints.h"
|
|
#include "ragdoll_shared.h"
|
|
#include "view.h"
|
|
#include "c_ai_basenpc.h"
|
|
#include "c_entitydissolve.h"
|
|
#include "saverestoretypes.h"
|
|
#include "c_fire_smoke.h"
|
|
#include "input.h"
|
|
#include "soundinfo.h"
|
|
#include "tools/bonelist.h"
|
|
#include "toolframework/itoolframework.h"
|
|
#include "datacache/idatacache.h"
|
|
#include "gamestringpool.h"
|
|
#include "jigglebones.h"
|
|
#include "toolframework_client.h"
|
|
#include "vstdlib/jobthread.h"
|
|
#include "bonetoworldarray.h"
|
|
#include "posedebugger.h"
|
|
#include "tier0/icommandline.h"
|
|
#include "prediction.h"
|
|
#include "replay/replay_ragdoll.h"
|
|
#include "studio_stats.h"
|
|
#include "tier1/callqueue.h"
|
|
|
|
#ifdef TF_CLIENT_DLL
|
|
#include "c_tf_player.h"
|
|
#include "c_baseobject.h"
|
|
#endif
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
static ConVar cl_SetupAllBones( "cl_SetupAllBones", "0" );
|
|
ConVar r_sequence_debug( "r_sequence_debug", "" );
|
|
|
|
// If an NPC is moving faster than this, he should play the running footstep sound
|
|
const float RUN_SPEED_ESTIMATE_SQR = 150.0f * 150.0f;
|
|
|
|
// Removed macro used by shared code stuff
|
|
#if defined( CBaseAnimating )
|
|
#undef CBaseAnimating
|
|
#endif
|
|
|
|
|
|
#ifdef DEBUG
|
|
static ConVar dbganimmodel( "dbganimmodel", "" );
|
|
#endif
|
|
|
|
mstudioevent_t *GetEventIndexForSequence( mstudioseqdesc_t &seqdesc );
|
|
|
|
C_EntityDissolve *DissolveEffect( C_BaseEntity *pTarget, float flTime );
|
|
C_EntityFlame *FireEffect( C_BaseAnimating *pTarget, C_BaseEntity *pServerFire, float *flScaleEnd, float *flTimeStart, float *flTimeEnd );
|
|
bool NPC_IsImportantNPC( C_BaseAnimating *pAnimating );
|
|
void VCollideWireframe_ChangeCallback( IConVar *pConVar, char const *pOldString, float flOldValue );
|
|
|
|
ConVar vcollide_wireframe( "vcollide_wireframe", "0", FCVAR_CHEAT, "Render physics collision models in wireframe", VCollideWireframe_ChangeCallback );
|
|
|
|
bool C_AnimationLayer::IsActive( void )
|
|
{
|
|
return (m_nOrder != C_BaseAnimatingOverlay::MAX_OVERLAYS);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Relative lighting entity
|
|
//-----------------------------------------------------------------------------
|
|
class C_InfoLightingRelative : public C_BaseEntity
|
|
{
|
|
public:
|
|
DECLARE_CLASS( C_InfoLightingRelative, C_BaseEntity );
|
|
DECLARE_CLIENTCLASS();
|
|
|
|
void GetLightingOffset( matrix3x4_t &offset );
|
|
|
|
private:
|
|
EHANDLE m_hLightingLandmark;
|
|
};
|
|
|
|
IMPLEMENT_CLIENTCLASS_DT(C_InfoLightingRelative, DT_InfoLightingRelative, CInfoLightingRelative)
|
|
RecvPropEHandle(RECVINFO(m_hLightingLandmark)),
|
|
END_RECV_TABLE()
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Relative lighting entity
|
|
//-----------------------------------------------------------------------------
|
|
void C_InfoLightingRelative::GetLightingOffset( matrix3x4_t &offset )
|
|
{
|
|
if ( m_hLightingLandmark.Get() )
|
|
{
|
|
matrix3x4_t matWorldToLandmark;
|
|
MatrixInvert( m_hLightingLandmark->EntityToWorldTransform(), matWorldToLandmark );
|
|
ConcatTransforms( EntityToWorldTransform(), matWorldToLandmark, offset );
|
|
}
|
|
else
|
|
{
|
|
SetIdentityMatrix( offset );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Base Animating
|
|
//-----------------------------------------------------------------------------
|
|
|
|
struct clientanimating_t
|
|
{
|
|
C_BaseAnimating *pAnimating;
|
|
unsigned int flags;
|
|
clientanimating_t(C_BaseAnimating *_pAnim, unsigned int _flags ) : pAnimating(_pAnim), flags(_flags) {}
|
|
};
|
|
|
|
const unsigned int FCLIENTANIM_SEQUENCE_CYCLE = 0x00000001;
|
|
|
|
static CUtlVector< clientanimating_t > g_ClientSideAnimationList;
|
|
|
|
void RecvProxy_Sequence( const CRecvProxyData *pData, void *pStruct, void *pOut )
|
|
{
|
|
// Have the regular proxy store the data.
|
|
RecvProxy_Int32ToInt32( pData, pStruct, pOut );
|
|
|
|
C_BaseAnimating *pAnimating = (C_BaseAnimating *)pStruct;
|
|
|
|
if ( !pAnimating )
|
|
return;
|
|
|
|
pAnimating->SetReceivedSequence();
|
|
|
|
// render bounds may have changed
|
|
pAnimating->UpdateVisibility();
|
|
}
|
|
|
|
IMPLEMENT_CLIENTCLASS_DT(C_BaseAnimating, DT_BaseAnimating, CBaseAnimating)
|
|
RecvPropInt(RECVINFO(m_nSequence), 0, RecvProxy_Sequence),
|
|
RecvPropInt(RECVINFO(m_nForceBone)),
|
|
RecvPropVector(RECVINFO(m_vecForce)),
|
|
RecvPropInt(RECVINFO(m_nSkin)),
|
|
RecvPropInt(RECVINFO(m_nBody)),
|
|
RecvPropInt(RECVINFO(m_nHitboxSet)),
|
|
|
|
RecvPropFloat(RECVINFO(m_flModelScale)),
|
|
RecvPropFloat(RECVINFO_NAME(m_flModelScale, m_flModelWidthScale)), // for demo compatibility only
|
|
|
|
// RecvPropArray(RecvPropFloat(RECVINFO(m_flPoseParameter[0])), m_flPoseParameter),
|
|
RecvPropArray3(RECVINFO_ARRAY(m_flPoseParameter), RecvPropFloat(RECVINFO(m_flPoseParameter[0])) ),
|
|
|
|
RecvPropFloat(RECVINFO(m_flPlaybackRate)),
|
|
|
|
RecvPropArray3( RECVINFO_ARRAY(m_flEncodedController), RecvPropFloat(RECVINFO(m_flEncodedController[0]))),
|
|
|
|
RecvPropInt( RECVINFO( m_bClientSideAnimation )),
|
|
RecvPropInt( RECVINFO( m_bClientSideFrameReset )),
|
|
|
|
RecvPropInt( RECVINFO( m_nNewSequenceParity )),
|
|
RecvPropInt( RECVINFO( m_nResetEventsParity )),
|
|
RecvPropInt( RECVINFO( m_nMuzzleFlashParity ) ),
|
|
|
|
RecvPropEHandle(RECVINFO(m_hLightingOrigin)),
|
|
RecvPropEHandle(RECVINFO(m_hLightingOriginRelative)),
|
|
|
|
RecvPropFloat(RECVINFO(m_flCycle)),
|
|
|
|
RecvPropFloat( RECVINFO( m_fadeMinDist ) ),
|
|
RecvPropFloat( RECVINFO( m_fadeMaxDist ) ),
|
|
RecvPropFloat( RECVINFO( m_flFadeScale ) ),
|
|
|
|
END_RECV_TABLE()
|
|
|
|
BEGIN_PREDICTION_DATA( C_BaseAnimating )
|
|
|
|
DEFINE_PRED_FIELD( m_nSkin, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
|
|
DEFINE_PRED_FIELD( m_nBody, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
|
|
// DEFINE_PRED_FIELD( m_nHitboxSet, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
|
|
// DEFINE_PRED_FIELD( m_flModelScale, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
|
|
DEFINE_PRED_FIELD( m_nSequence, FIELD_INTEGER, FTYPEDESC_INSENDTABLE | FTYPEDESC_NOERRORCHECK ),
|
|
DEFINE_PRED_FIELD( m_flPlaybackRate, FIELD_FLOAT, FTYPEDESC_INSENDTABLE | FTYPEDESC_NOERRORCHECK ),
|
|
DEFINE_PRED_FIELD( m_flCycle, FIELD_FLOAT, FTYPEDESC_INSENDTABLE | FTYPEDESC_NOERRORCHECK ),
|
|
// DEFINE_PRED_ARRAY( m_flPoseParameter, FIELD_FLOAT, MAXSTUDIOPOSEPARAM, FTYPEDESC_INSENDTABLE ),
|
|
DEFINE_PRED_ARRAY_TOL( m_flEncodedController, FIELD_FLOAT, MAXSTUDIOBONECTRLS, FTYPEDESC_INSENDTABLE, 0.02f ),
|
|
|
|
DEFINE_FIELD( m_nPrevSequence, FIELD_INTEGER ),
|
|
//DEFINE_FIELD( m_flPrevEventCycle, FIELD_FLOAT ),
|
|
//DEFINE_FIELD( m_flEventCycle, FIELD_FLOAT ),
|
|
//DEFINE_FIELD( m_nEventSequence, FIELD_INTEGER ),
|
|
|
|
DEFINE_PRED_FIELD( m_nNewSequenceParity, FIELD_INTEGER, FTYPEDESC_INSENDTABLE | FTYPEDESC_NOERRORCHECK ),
|
|
DEFINE_PRED_FIELD( m_nResetEventsParity, FIELD_INTEGER, FTYPEDESC_INSENDTABLE | FTYPEDESC_NOERRORCHECK ),
|
|
// DEFINE_PRED_FIELD( m_nPrevResetEventsParity, FIELD_INTEGER, 0 ),
|
|
|
|
DEFINE_PRED_FIELD( m_nMuzzleFlashParity, FIELD_CHARACTER, FTYPEDESC_INSENDTABLE ),
|
|
//DEFINE_FIELD( m_nOldMuzzleFlashParity, FIELD_CHARACTER ),
|
|
|
|
//DEFINE_FIELD( m_nPrevNewSequenceParity, FIELD_INTEGER ),
|
|
//DEFINE_FIELD( m_nPrevResetEventsParity, FIELD_INTEGER ),
|
|
|
|
// DEFINE_PRED_FIELD( m_vecForce, FIELD_VECTOR, FTYPEDESC_INSENDTABLE ),
|
|
// DEFINE_PRED_FIELD( m_nForceBone, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
|
|
// DEFINE_PRED_FIELD( m_bClientSideAnimation, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
|
|
// DEFINE_PRED_FIELD( m_bClientSideFrameReset, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
|
|
|
|
// DEFINE_FIELD( m_pRagdollInfo, RagdollInfo_t ),
|
|
// DEFINE_FIELD( m_CachedBones, CUtlVector < CBoneCacheEntry > ),
|
|
// DEFINE_FIELD( m_pActualAttachmentAngles, FIELD_VECTOR ),
|
|
// DEFINE_FIELD( m_pActualAttachmentOrigin, FIELD_VECTOR ),
|
|
|
|
// DEFINE_FIELD( m_animationQueue, CUtlVector < C_AnimationLayer > ),
|
|
// DEFINE_FIELD( m_pIk, CIKContext ),
|
|
// DEFINE_FIELD( m_bLastClientSideFrameReset, FIELD_BOOLEAN ),
|
|
// DEFINE_FIELD( hdr, studiohdr_t ),
|
|
// DEFINE_FIELD( m_pRagdoll, IRagdoll ),
|
|
// DEFINE_FIELD( m_bStoreRagdollInfo, FIELD_BOOLEAN ),
|
|
|
|
// DEFINE_FIELD( C_BaseFlex, m_iEyeAttachment, FIELD_INTEGER ),
|
|
|
|
END_PREDICTION_DATA()
|
|
|
|
LINK_ENTITY_TO_CLASS( client_ragdoll, C_ClientRagdoll );
|
|
|
|
BEGIN_DATADESC( C_ClientRagdoll )
|
|
DEFINE_FIELD( m_bFadeOut, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_bImportant, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_iCurrentFriction, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_iMinFriction, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_iMaxFriction, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_flFrictionModTime, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_flFrictionTime, FIELD_TIME ),
|
|
DEFINE_FIELD( m_iFrictionAnimState, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_bReleaseRagdoll, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_nBody, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_nSkin, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_nRenderFX, FIELD_CHARACTER ),
|
|
DEFINE_FIELD( m_nRenderMode, FIELD_CHARACTER ),
|
|
DEFINE_FIELD( m_clrRender, FIELD_COLOR32 ),
|
|
DEFINE_FIELD( m_flEffectTime, FIELD_TIME ),
|
|
DEFINE_FIELD( m_bFadingOut, FIELD_BOOLEAN ),
|
|
|
|
DEFINE_AUTO_ARRAY( m_flScaleEnd, FIELD_FLOAT ),
|
|
DEFINE_AUTO_ARRAY( m_flScaleTimeStart, FIELD_FLOAT ),
|
|
DEFINE_AUTO_ARRAY( m_flScaleTimeEnd, FIELD_FLOAT ),
|
|
DEFINE_EMBEDDEDBYREF( m_pRagdoll ),
|
|
|
|
END_DATADESC()
|
|
|
|
C_ClientRagdoll::C_ClientRagdoll( bool bRestoring )
|
|
{
|
|
m_iCurrentFriction = 0;
|
|
m_iFrictionAnimState = RAGDOLL_FRICTION_NONE;
|
|
m_bReleaseRagdoll = false;
|
|
m_bFadeOut = false;
|
|
m_bFadingOut = false;
|
|
m_bImportant = false;
|
|
m_bNoModelParticles = false;
|
|
|
|
SetClassname("client_ragdoll");
|
|
|
|
if ( bRestoring == true )
|
|
{
|
|
m_pRagdoll = new CRagdoll;
|
|
}
|
|
}
|
|
|
|
void C_ClientRagdoll::OnSave( void )
|
|
{
|
|
}
|
|
|
|
void C_ClientRagdoll::OnRestore( void )
|
|
{
|
|
CStudioHdr *hdr = GetModelPtr();
|
|
|
|
if ( hdr == NULL )
|
|
{
|
|
const char *pModelName = STRING( GetModelName() );
|
|
SetModel( pModelName );
|
|
|
|
hdr = GetModelPtr();
|
|
|
|
if ( hdr == NULL )
|
|
return;
|
|
}
|
|
|
|
if ( m_pRagdoll == NULL )
|
|
return;
|
|
|
|
ragdoll_t *pRagdollT = m_pRagdoll->GetRagdoll();
|
|
|
|
if ( pRagdollT == NULL || pRagdollT->list[0].pObject == NULL )
|
|
{
|
|
m_bReleaseRagdoll = true;
|
|
m_pRagdoll = NULL;
|
|
Assert( !"Attempted to restore a ragdoll without physobjects!" );
|
|
return;
|
|
}
|
|
|
|
if ( GetFlags() & FL_DISSOLVING )
|
|
{
|
|
DissolveEffect( this, m_flEffectTime );
|
|
}
|
|
else if ( GetFlags() & FL_ONFIRE )
|
|
{
|
|
C_EntityFlame *pFireChild = dynamic_cast<C_EntityFlame *>( GetEffectEntity() );
|
|
C_EntityFlame *pNewFireChild = FireEffect( this, pFireChild, m_flScaleEnd, m_flScaleTimeStart, m_flScaleTimeEnd );
|
|
|
|
//Set the new fire child as the new effect entity.
|
|
SetEffectEntity( pNewFireChild );
|
|
}
|
|
|
|
VPhysicsSetObject( NULL );
|
|
VPhysicsSetObject( pRagdollT->list[0].pObject );
|
|
|
|
SetupBones( NULL, -1, BONE_USED_BY_ANYTHING, gpGlobals->curtime );
|
|
|
|
pRagdollT->list[0].parentIndex = -1;
|
|
pRagdollT->list[0].originParentSpace.Init();
|
|
|
|
RagdollActivate( *pRagdollT, modelinfo->GetVCollide( GetModelIndex() ), GetModelIndex(), true );
|
|
RagdollSetupAnimatedFriction( physenv, pRagdollT, GetModelIndex() );
|
|
|
|
m_pRagdoll->BuildRagdollBounds( this );
|
|
|
|
// UNDONE: The shadow & leaf system cleanup should probably be in C_BaseEntity::OnRestore()
|
|
// this must be recomputed because the model was NULL when this was set up
|
|
RemoveFromLeafSystem();
|
|
AddToLeafSystem( RENDER_GROUP_OPAQUE_ENTITY );
|
|
|
|
DestroyShadow();
|
|
CreateShadow();
|
|
|
|
SetNextClientThink( CLIENT_THINK_ALWAYS );
|
|
|
|
if ( m_bFadeOut == true )
|
|
{
|
|
s_RagdollLRU.MoveToTopOfLRU( this, m_bImportant );
|
|
}
|
|
|
|
NoteRagdollCreationTick( this );
|
|
|
|
BaseClass::OnRestore();
|
|
|
|
RagdollMoved();
|
|
}
|
|
|
|
void C_ClientRagdoll::ImpactTrace( trace_t *pTrace, int iDamageType, const char *pCustomImpactName )
|
|
{
|
|
VPROF( "C_ClientRagdoll::ImpactTrace" );
|
|
|
|
IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
|
|
|
|
if( !pPhysicsObject )
|
|
return;
|
|
|
|
Vector dir = pTrace->endpos - pTrace->startpos;
|
|
|
|
if ( iDamageType == DMG_BLAST )
|
|
{
|
|
dir *= 500; // adjust impact strenght
|
|
|
|
// apply force at object mass center
|
|
pPhysicsObject->ApplyForceCenter( dir );
|
|
}
|
|
else
|
|
{
|
|
Vector hitpos;
|
|
|
|
VectorMA( pTrace->startpos, pTrace->fraction, dir, hitpos );
|
|
VectorNormalize( dir );
|
|
|
|
dir *= 4000; // adjust impact strenght
|
|
|
|
// apply force where we hit it
|
|
pPhysicsObject->ApplyForceOffset( dir, hitpos );
|
|
}
|
|
|
|
m_pRagdoll->ResetRagdollSleepAfterTime();
|
|
}
|
|
|
|
ConVar g_debug_ragdoll_visualize( "g_debug_ragdoll_visualize", "0", FCVAR_CHEAT );
|
|
|
|
void C_ClientRagdoll::HandleAnimatedFriction( void )
|
|
{
|
|
if ( m_iFrictionAnimState == RAGDOLL_FRICTION_OFF )
|
|
return;
|
|
|
|
ragdoll_t *pRagdollT = NULL;
|
|
int iBoneCount = 0;
|
|
|
|
if ( m_pRagdoll )
|
|
{
|
|
pRagdollT = m_pRagdoll->GetRagdoll();
|
|
iBoneCount = m_pRagdoll->RagdollBoneCount();
|
|
|
|
}
|
|
|
|
if ( pRagdollT == NULL )
|
|
return;
|
|
|
|
switch ( m_iFrictionAnimState )
|
|
{
|
|
case RAGDOLL_FRICTION_NONE:
|
|
{
|
|
m_iMinFriction = pRagdollT->animfriction.iMinAnimatedFriction;
|
|
m_iMaxFriction = pRagdollT->animfriction.iMaxAnimatedFriction;
|
|
|
|
if ( m_iMinFriction != 0 || m_iMaxFriction != 0 )
|
|
{
|
|
m_iFrictionAnimState = RAGDOLL_FRICTION_IN;
|
|
|
|
m_flFrictionModTime = pRagdollT->animfriction.flFrictionTimeIn;
|
|
m_flFrictionTime = gpGlobals->curtime + m_flFrictionModTime;
|
|
|
|
m_iCurrentFriction = m_iMinFriction;
|
|
}
|
|
else
|
|
{
|
|
m_iFrictionAnimState = RAGDOLL_FRICTION_OFF;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case RAGDOLL_FRICTION_IN:
|
|
{
|
|
float flDeltaTime = (m_flFrictionTime - gpGlobals->curtime);
|
|
|
|
m_iCurrentFriction = RemapValClamped( flDeltaTime , m_flFrictionModTime, 0, m_iMinFriction, m_iMaxFriction );
|
|
|
|
if ( flDeltaTime <= 0.0f )
|
|
{
|
|
m_flFrictionModTime = pRagdollT->animfriction.flFrictionTimeHold;
|
|
m_flFrictionTime = gpGlobals->curtime + m_flFrictionModTime;
|
|
m_iFrictionAnimState = RAGDOLL_FRICTION_HOLD;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case RAGDOLL_FRICTION_HOLD:
|
|
{
|
|
if ( m_flFrictionTime < gpGlobals->curtime )
|
|
{
|
|
m_flFrictionModTime = pRagdollT->animfriction.flFrictionTimeOut;
|
|
m_flFrictionTime = gpGlobals->curtime + m_flFrictionModTime;
|
|
m_iFrictionAnimState = RAGDOLL_FRICTION_OUT;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case RAGDOLL_FRICTION_OUT:
|
|
{
|
|
float flDeltaTime = (m_flFrictionTime - gpGlobals->curtime);
|
|
|
|
m_iCurrentFriction = RemapValClamped( flDeltaTime , 0, m_flFrictionModTime, m_iMinFriction, m_iMaxFriction );
|
|
|
|
if ( flDeltaTime <= 0.0f )
|
|
{
|
|
m_iFrictionAnimState = RAGDOLL_FRICTION_OFF;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
for ( int i = 0; i < iBoneCount; i++ )
|
|
{
|
|
if ( pRagdollT->list[i].pConstraint )
|
|
pRagdollT->list[i].pConstraint->SetAngularMotor( 0, m_iCurrentFriction );
|
|
}
|
|
|
|
IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
|
|
|
|
if ( pPhysicsObject )
|
|
{
|
|
pPhysicsObject->Wake();
|
|
}
|
|
}
|
|
|
|
ConVar g_ragdoll_fadespeed( "g_ragdoll_fadespeed", "600" );
|
|
ConVar g_ragdoll_lvfadespeed( "g_ragdoll_lvfadespeed", "100" );
|
|
|
|
void C_ClientRagdoll::OnPVSStatusChanged( bool bInPVS )
|
|
{
|
|
if ( bInPVS )
|
|
{
|
|
CreateShadow();
|
|
}
|
|
else
|
|
{
|
|
DestroyShadow();
|
|
}
|
|
}
|
|
|
|
void C_ClientRagdoll::FadeOut( void )
|
|
{
|
|
if ( m_bFadingOut == false )
|
|
{
|
|
return;
|
|
}
|
|
|
|
int iAlpha = GetRenderColor().a;
|
|
int iFadeSpeed = ( g_RagdollLVManager.IsLowViolence() ) ? g_ragdoll_lvfadespeed.GetInt() : g_ragdoll_fadespeed.GetInt();
|
|
|
|
iAlpha = MAX( iAlpha - ( iFadeSpeed * gpGlobals->frametime ), 0 );
|
|
|
|
SetRenderMode( kRenderTransAlpha );
|
|
SetRenderColorA( iAlpha );
|
|
|
|
if ( iAlpha == 0 )
|
|
{
|
|
m_bReleaseRagdoll = true;
|
|
}
|
|
}
|
|
|
|
void C_ClientRagdoll::SUB_Remove( void )
|
|
{
|
|
m_bFadingOut = true;
|
|
SetNextClientThink( CLIENT_THINK_ALWAYS );
|
|
}
|
|
|
|
void C_ClientRagdoll::ClientThink( void )
|
|
{
|
|
if ( m_bReleaseRagdoll == true )
|
|
{
|
|
DestroyBoneAttachments();
|
|
Release();
|
|
return;
|
|
}
|
|
|
|
if ( g_debug_ragdoll_visualize.GetBool() )
|
|
{
|
|
Vector vMins, vMaxs;
|
|
|
|
Vector origin = m_pRagdoll->GetRagdollOrigin();
|
|
m_pRagdoll->GetRagdollBounds( vMins, vMaxs );
|
|
|
|
debugoverlay->AddBoxOverlay( origin, vMins, vMaxs, QAngle( 0, 0, 0 ), 0, 255, 0, 16, 0 );
|
|
}
|
|
|
|
HandleAnimatedFriction();
|
|
|
|
FadeOut();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: clear out any face/eye values stored in the material system
|
|
//-----------------------------------------------------------------------------
|
|
float C_ClientRagdoll::LastBoneChangedTime()
|
|
{
|
|
// When did this last change?
|
|
return m_pRagdoll ? m_pRagdoll->GetLastVPhysicsUpdateTime() : -FLT_MAX;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: clear out any face/eye values stored in the material system
|
|
//-----------------------------------------------------------------------------
|
|
void C_ClientRagdoll::SetupWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights )
|
|
{
|
|
BaseClass::SetupWeights( pBoneToWorld, nFlexWeightCount, pFlexWeights, pFlexDelayedWeights );
|
|
|
|
CStudioHdr *hdr = GetModelPtr();
|
|
if ( !hdr )
|
|
return;
|
|
|
|
int nFlexDescCount = hdr->numflexdesc();
|
|
if ( nFlexDescCount )
|
|
{
|
|
Assert( !pFlexDelayedWeights );
|
|
memset( pFlexWeights, 0, nFlexWeightCount * sizeof(float) );
|
|
}
|
|
|
|
if ( m_iEyeAttachment > 0 )
|
|
{
|
|
matrix3x4_t attToWorld;
|
|
if ( GetAttachment( m_iEyeAttachment, attToWorld ) )
|
|
{
|
|
Vector local, tmp;
|
|
local.Init( 1000.0f, 0.0f, 0.0f );
|
|
VectorTransform( local, attToWorld, tmp );
|
|
modelrender->SetViewTarget( GetModelPtr(), GetBody(), tmp );
|
|
}
|
|
}
|
|
}
|
|
|
|
void C_ClientRagdoll::Release( void )
|
|
{
|
|
C_BaseEntity *pChild = GetEffectEntity();
|
|
|
|
if ( pChild && pChild->IsMarkedForDeletion() == false )
|
|
{
|
|
pChild->Release();
|
|
}
|
|
|
|
if ( GetThinkHandle() != INVALID_THINK_HANDLE )
|
|
{
|
|
ClientThinkList()->RemoveThinkable( GetClientHandle() );
|
|
}
|
|
ClientEntityList().RemoveEntity( GetClientHandle() );
|
|
|
|
partition->Remove( PARTITION_CLIENT_SOLID_EDICTS | PARTITION_CLIENT_RESPONSIVE_EDICTS | PARTITION_CLIENT_NON_STATIC_EDICTS, CollisionProp()->GetPartitionHandle() );
|
|
RemoveFromLeafSystem();
|
|
|
|
BaseClass::Release();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Incremented each frame in InvalidateModelBones. Models compare this value to what it
|
|
// was last time they setup their bones to determine if they need to re-setup their bones.
|
|
static unsigned long g_iModelBoneCounter = 0;
|
|
CUtlVector<C_BaseAnimating *> g_PreviousBoneSetups;
|
|
static unsigned long g_iPreviousBoneCounter = (unsigned)-1;
|
|
|
|
class C_BaseAnimatingGameSystem : public CAutoGameSystem
|
|
{
|
|
void LevelShutdownPostEntity()
|
|
{
|
|
g_iPreviousBoneCounter = (unsigned)-1;
|
|
if ( g_PreviousBoneSetups.Count() != 0 )
|
|
{
|
|
Msg( "%d entities in bone setup array. Should have been cleaned up by now\n", g_PreviousBoneSetups.Count() );
|
|
g_PreviousBoneSetups.RemoveAll();
|
|
}
|
|
}
|
|
} g_BaseAnimatingGameSystem;
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: convert axis rotations to a quaternion
|
|
//-----------------------------------------------------------------------------
|
|
C_BaseAnimating::C_BaseAnimating()
|
|
: m_iv_flCycle( "C_BaseAnimating::m_iv_flCycle" ),
|
|
m_iv_flPoseParameter( "C_BaseAnimating::m_iv_flPoseParameter" ),
|
|
m_iv_flEncodedController("C_BaseAnimating::m_iv_flEncodedController"),
|
|
m_iv_Sequence("C_BaseAnimating::m_iv_Sequence")
|
|
{
|
|
m_vecForce.Init();
|
|
m_nForceBone = -1;
|
|
|
|
m_ClientSideAnimationListHandle = INVALID_CLIENTSIDEANIMATION_LIST_HANDLE;
|
|
|
|
m_nPrevSequence = -1;
|
|
m_nRestoreSequence = -1;
|
|
m_pRagdoll = NULL;
|
|
m_builtRagdoll = false;
|
|
m_hitboxBoneCacheHandle = 0;
|
|
m_nHitboxSet = 0;
|
|
|
|
int i;
|
|
for ( i = 0; i < ARRAYSIZE( m_flEncodedController ); i++ )
|
|
{
|
|
m_flEncodedController[ i ] = 0.0f;
|
|
}
|
|
|
|
for ( i = 0; i < ARRAYSIZE( m_flPoseParameter ); i++ )
|
|
{
|
|
m_flPoseParameter[ i ] = 0.0f;
|
|
}
|
|
|
|
m_iMostRecentModelBoneCounter = 0xFFFFFFFF;
|
|
m_iMostRecentBoneSetupRequest = g_iPreviousBoneCounter - 1;
|
|
m_flLastBoneSetupTime = -FLT_MAX;
|
|
|
|
m_vecPreRagdollMins = vec3_origin;
|
|
m_vecPreRagdollMaxs = vec3_origin;
|
|
|
|
m_bStoreRagdollInfo = false;
|
|
m_pRagdollInfo = NULL;
|
|
m_pJiggleBones = NULL;
|
|
m_pBoneMergeCache = NULL;
|
|
|
|
m_flPlaybackRate = 1.0f;
|
|
|
|
m_nEventSequence = -1;
|
|
|
|
m_pIk = NULL;
|
|
|
|
// Assume false. Derived classes might fill in a receive table entry
|
|
// and in that case this would show up as true
|
|
m_bClientSideAnimation = false;
|
|
|
|
m_nPrevNewSequenceParity = -1;
|
|
m_nPrevResetEventsParity = -1;
|
|
|
|
m_nOldMuzzleFlashParity = 0;
|
|
m_nMuzzleFlashParity = 0;
|
|
|
|
m_flModelScale = 1.0f;
|
|
|
|
m_iEyeAttachment = 0;
|
|
#ifdef _XBOX
|
|
m_iAccumulatedBoneMask = 0;
|
|
#endif
|
|
m_pStudioHdr = NULL;
|
|
m_hStudioHdr = MDLHANDLE_INVALID;
|
|
|
|
m_bReceivedSequence = false;
|
|
|
|
m_boneIndexAttached = -1;
|
|
m_flOldModelScale = 0.0f;
|
|
|
|
m_pAttachedTo = NULL;
|
|
|
|
m_bDynamicModelAllowed = false;
|
|
m_bDynamicModelPending = false;
|
|
m_bResetSequenceInfoOnLoad = false;
|
|
|
|
Q_memset(&m_mouth, 0, sizeof(m_mouth));
|
|
m_flCycle = 0;
|
|
m_flOldCycle = 0;
|
|
m_flAnimTime = gpGlobals->curtime;
|
|
m_flOldAnimTime = gpGlobals->curtime;
|
|
|
|
AddBaseAnimatingInterpolatedVars();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: cleanup
|
|
//-----------------------------------------------------------------------------
|
|
C_BaseAnimating::~C_BaseAnimating()
|
|
{
|
|
int i = g_PreviousBoneSetups.Find( this );
|
|
if ( i != -1 )
|
|
g_PreviousBoneSetups.FastRemove( i );
|
|
RemoveFromClientSideAnimationList();
|
|
RemoveBaseAnimatingInterpolatedVars();
|
|
|
|
TermRopes();
|
|
delete m_pRagdollInfo;
|
|
Assert(!m_pRagdoll);
|
|
delete m_pIk;
|
|
delete m_pBoneMergeCache;
|
|
Studio_DestroyBoneCache( m_hitboxBoneCacheHandle );
|
|
delete m_pJiggleBones;
|
|
InvalidateMdlCache();
|
|
|
|
// Kill off anything bone attached to us.
|
|
DestroyBoneAttachments();
|
|
|
|
// If we are bone attached to something, remove us from the list.
|
|
if ( m_pAttachedTo )
|
|
{
|
|
m_pAttachedTo->RemoveBoneAttachment( this );
|
|
m_pAttachedTo = NULL;
|
|
}
|
|
}
|
|
|
|
bool C_BaseAnimating::UsesPowerOfTwoFrameBufferTexture( void )
|
|
{
|
|
return modelinfo->IsUsingFBTexture( GetModel(), GetSkin(), GetBody(), GetClientRenderable() );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// VPhysics object
|
|
//-----------------------------------------------------------------------------
|
|
int C_BaseAnimating::VPhysicsGetObjectList( IPhysicsObject **pList, int listMax )
|
|
{
|
|
if ( IsRagdoll() )
|
|
{
|
|
int i;
|
|
for ( i = 0; i < m_pRagdoll->RagdollBoneCount(); ++i )
|
|
{
|
|
if ( i >= listMax )
|
|
break;
|
|
|
|
pList[i] = m_pRagdoll->GetElement(i);
|
|
}
|
|
return i;
|
|
}
|
|
|
|
return BaseClass::VPhysicsGetObjectList( pList, listMax );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Should this object cast render-to-texture shadows?
|
|
//-----------------------------------------------------------------------------
|
|
ShadowType_t C_BaseAnimating::ShadowCastType()
|
|
{
|
|
CStudioHdr *pStudioHdr = GetModelPtr();
|
|
if ( !pStudioHdr || !pStudioHdr->SequencesAvailable() )
|
|
return SHADOWS_NONE;
|
|
|
|
if ( IsEffectActive(EF_NODRAW | EF_NOSHADOW) )
|
|
return SHADOWS_NONE;
|
|
|
|
if (pStudioHdr->GetNumSeq() == 0)
|
|
return SHADOWS_RENDER_TO_TEXTURE;
|
|
|
|
if ( !IsRagdoll() )
|
|
{
|
|
// If we have pose parameters, always update
|
|
if ( pStudioHdr->GetNumPoseParameters() > 0 )
|
|
return SHADOWS_RENDER_TO_TEXTURE_DYNAMIC;
|
|
|
|
// If we have bone controllers, always update
|
|
if ( pStudioHdr->numbonecontrollers() > 0 )
|
|
return SHADOWS_RENDER_TO_TEXTURE_DYNAMIC;
|
|
|
|
// If we use IK, always update
|
|
if ( pStudioHdr->numikchains() > 0 )
|
|
return SHADOWS_RENDER_TO_TEXTURE_DYNAMIC;
|
|
}
|
|
|
|
// FIXME: Do something to check to see how many frames the current animation has
|
|
// If we do this, we have to be able to handle the case of changing ShadowCastTypes
|
|
// at the moment, they are assumed to be constant.
|
|
return SHADOWS_RENDER_TO_TEXTURE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: convert axis rotations to a quaternion
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void C_BaseAnimating::SetPredictable( bool state )
|
|
{
|
|
BaseClass::SetPredictable( state );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: sets client side animation
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::UseClientSideAnimation()
|
|
{
|
|
m_bClientSideAnimation = true;
|
|
}
|
|
|
|
void C_BaseAnimating::AddBaseAnimatingInterpolatedVars()
|
|
{
|
|
// AddVar( m_flEncodedController, &m_iv_flEncodedController, LATCH_ANIMATION_VAR );
|
|
// AddVar( m_flPoseParameter, &m_iv_flPoseParameter, LATCH_ANIMATION_VAR );
|
|
|
|
int flags = LATCH_ANIMATION_VAR;
|
|
if ( m_bClientSideAnimation )
|
|
flags |= EXCLUDE_AUTO_INTERPOLATE;
|
|
|
|
// AddVar( &m_flCycle, &m_iv_flCycle, flags );
|
|
// AddVar( &m_nSequence, &m_iv_Sequence, flags );
|
|
}
|
|
|
|
void C_BaseAnimating::RemoveBaseAnimatingInterpolatedVars()
|
|
{
|
|
RemoveVar( m_flEncodedController, false );
|
|
RemoveVar( m_flPoseParameter, false );
|
|
#ifdef HL2MP
|
|
// HACK: Don't want to remove interpolation for predictables in hl2dm, though
|
|
// The animation state stuff sets the pose parameters -- so they should interp
|
|
// but m_flCycle is not touched, so it's only set during prediction (which occurs on tick boundaries)
|
|
// and so needs to continue to be interpolated for smooth rendering of the lower body of the local player in third person, etc.
|
|
if ( !GetPredictable() )
|
|
#endif
|
|
{
|
|
RemoveVar( &m_flCycle, false );
|
|
RemoveVar( &m_nSequence, false );
|
|
}
|
|
}
|
|
|
|
void C_BaseAnimating::StudioFrameAdvanceInternal( CStudioHdr *pStudioHdr, float flCycleDelta )
|
|
{
|
|
float flNewCycle = GetCycle() + flCycleDelta;
|
|
if (flNewCycle < 0.0 || flNewCycle >= 1.0)
|
|
{
|
|
if (m_bSequenceLoops)
|
|
{
|
|
flNewCycle -= (int)(flNewCycle);
|
|
}
|
|
else
|
|
{
|
|
flNewCycle = (flNewCycle < 0.0f) ? 0.0f : 1.0f;
|
|
}
|
|
m_bSequenceFinished = true; // just in case it wasn't caught in GetEvents
|
|
}
|
|
else if (flNewCycle > GetLastVisibleCycle( pStudioHdr, GetSequence() ))
|
|
{
|
|
m_bSequenceFinished = true;
|
|
}
|
|
|
|
SetCycle( flNewCycle );
|
|
|
|
/*
|
|
if (!IsPlayer())
|
|
Msg("%s %6.3f : %6.3f %6.3f (%.3f) %.3f\n",
|
|
GetClassname(), gpGlobals->curtime,
|
|
m_flAnimTime.Get(), m_flPrevAnimTime, flInterval, GetCycle() );
|
|
*/
|
|
|
|
m_flGroundSpeed = GetSequenceGroundSpeed( pStudioHdr, GetSequence() ) * GetModelScale();
|
|
|
|
// Msg("%s : %s : %5.1f\n", GetClassname(), GetSequenceName( GetSequence() ), GetCycle() );
|
|
InvalidatePhysicsRecursive( ANIMATION_CHANGED );
|
|
|
|
InvalidateBoneCacheIfOlderThan( 0 );
|
|
}
|
|
|
|
void C_BaseAnimating::InvalidateBoneCacheIfOlderThan( float deltaTime )
|
|
{
|
|
CBoneCache *pcache = Studio_GetBoneCache( m_hitboxBoneCacheHandle );
|
|
if ( !pcache || !pcache->IsValid( gpGlobals->curtime, deltaTime ) )
|
|
{
|
|
InvalidateBoneCache();
|
|
}
|
|
}
|
|
|
|
void C_BaseAnimating::LockStudioHdr()
|
|
{
|
|
Assert( m_hStudioHdr == MDLHANDLE_INVALID && m_pStudioHdr == NULL );
|
|
|
|
AUTO_LOCK( m_StudioHdrInitLock );
|
|
|
|
if ( m_hStudioHdr != MDLHANDLE_INVALID || m_pStudioHdr != NULL )
|
|
{
|
|
Assert( m_pStudioHdr ? m_pStudioHdr->GetRenderHdr() == mdlcache->GetStudioHdr(m_hStudioHdr) : m_hStudioHdr == MDLHANDLE_INVALID );
|
|
return;
|
|
}
|
|
|
|
const model_t *mdl = GetModel();
|
|
if ( !mdl )
|
|
return;
|
|
|
|
m_hStudioHdr = modelinfo->GetCacheHandle( mdl );
|
|
if ( m_hStudioHdr == MDLHANDLE_INVALID )
|
|
return;
|
|
|
|
const studiohdr_t *pStudioHdr = mdlcache->LockStudioHdr( m_hStudioHdr );
|
|
if ( !pStudioHdr )
|
|
{
|
|
m_hStudioHdr = MDLHANDLE_INVALID;
|
|
return;
|
|
}
|
|
|
|
CStudioHdr *pNewWrapper = new CStudioHdr;
|
|
pNewWrapper->Init( pStudioHdr, mdlcache );
|
|
Assert( pNewWrapper->IsValid() );
|
|
|
|
if ( pNewWrapper->GetVirtualModel() )
|
|
{
|
|
MDLHandle_t hVirtualModel = VoidPtrToMDLHandle( pStudioHdr->VirtualModel() );
|
|
mdlcache->LockStudioHdr( hVirtualModel );
|
|
}
|
|
|
|
m_pStudioHdr = pNewWrapper; // must be last to ensure virtual model correctly set up
|
|
}
|
|
|
|
void C_BaseAnimating::UnlockStudioHdr()
|
|
{
|
|
if ( m_hStudioHdr != MDLHANDLE_INVALID )
|
|
{
|
|
studiohdr_t *pStudioHdr = mdlcache->GetStudioHdr( m_hStudioHdr );
|
|
Assert( m_pStudioHdr && m_pStudioHdr->GetRenderHdr() == pStudioHdr );
|
|
|
|
#if 0
|
|
// XXX need to figure out where to flush the queue on map change to not crash
|
|
if ( ICallQueue *pCallQueue = materials->GetRenderContext()->GetCallQueue() )
|
|
{
|
|
// Parallel rendering: don't unlock model data until end of rendering
|
|
if ( pStudioHdr->GetVirtualModel() )
|
|
{
|
|
MDLHandle_t hVirtualModel = VoidPtrToMDLHandle( m_pStudioHdr->GetRenderHdr()->VirtualModel() );
|
|
pCallQueue->QueueCall( mdlcache, &IMDLCache::UnlockStudioHdr, hVirtualModel );
|
|
}
|
|
pCallQueue->QueueCall( mdlcache, &IMDLCache::UnlockStudioHdr, m_hStudioHdr );
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
// Immediate-mode rendering, can unlock immediately
|
|
if ( pStudioHdr->GetVirtualModel() )
|
|
{
|
|
MDLHandle_t hVirtualModel = VoidPtrToMDLHandle( m_pStudioHdr->GetRenderHdr()->VirtualModel() );
|
|
mdlcache->UnlockStudioHdr( hVirtualModel );
|
|
}
|
|
mdlcache->UnlockStudioHdr( m_hStudioHdr );
|
|
}
|
|
m_hStudioHdr = MDLHANDLE_INVALID;
|
|
}
|
|
}
|
|
|
|
void C_BaseAnimating::OnModelLoadComplete( const model_t* pModel )
|
|
{
|
|
Assert( m_bDynamicModelPending && pModel == GetModel() );
|
|
if ( m_bDynamicModelPending && pModel == GetModel() )
|
|
{
|
|
m_bDynamicModelPending = false;
|
|
OnNewModel();
|
|
UpdateVisibility();
|
|
}
|
|
}
|
|
|
|
void C_BaseAnimating::ValidateModelIndex()
|
|
{
|
|
BaseClass::ValidateModelIndex();
|
|
Assert( m_nModelIndex == 0 || m_AutoRefModelIndex.Get() );
|
|
}
|
|
|
|
CStudioHdr *C_BaseAnimating::OnNewModel()
|
|
{
|
|
InvalidateMdlCache();
|
|
|
|
// remove transition animations playback
|
|
m_SequenceTransitioner.RemoveAll();
|
|
|
|
if (m_pJiggleBones)
|
|
{
|
|
delete m_pJiggleBones;
|
|
m_pJiggleBones = NULL;
|
|
}
|
|
|
|
if ( m_bDynamicModelPending )
|
|
{
|
|
modelinfo->UnregisterModelLoadCallback( -1, this );
|
|
m_bDynamicModelPending = false;
|
|
}
|
|
|
|
m_AutoRefModelIndex.Clear();
|
|
|
|
if ( !GetModel() || modelinfo->GetModelType( GetModel() ) != mod_studio )
|
|
return NULL;
|
|
|
|
// Reference (and thus start loading) dynamic model
|
|
int nNewIndex = m_nModelIndex;
|
|
if ( modelinfo->GetModel( nNewIndex ) != GetModel() )
|
|
{
|
|
// XXX what's authoritative? the model pointer or the model index? what a mess.
|
|
nNewIndex = modelinfo->GetModelIndex( modelinfo->GetModelName( GetModel() ) );
|
|
Assert( nNewIndex < 0 || modelinfo->GetModel( nNewIndex ) == GetModel() );
|
|
if ( nNewIndex < 0 )
|
|
nNewIndex = m_nModelIndex;
|
|
}
|
|
|
|
m_AutoRefModelIndex = nNewIndex;
|
|
if ( IsDynamicModelIndex( nNewIndex ) && modelinfo->IsDynamicModelLoading( nNewIndex ) )
|
|
{
|
|
m_bDynamicModelPending = true;
|
|
modelinfo->RegisterModelLoadCallback( nNewIndex, this );
|
|
}
|
|
|
|
if ( IsDynamicModelLoading() )
|
|
{
|
|
// Called while dynamic model still loading -> new model, clear deferred state
|
|
m_bResetSequenceInfoOnLoad = false;
|
|
return NULL;
|
|
}
|
|
|
|
CStudioHdr *hdr = GetModelPtr();
|
|
if (hdr == NULL)
|
|
return NULL;
|
|
|
|
InvalidateBoneCache();
|
|
if ( m_pBoneMergeCache )
|
|
{
|
|
delete m_pBoneMergeCache;
|
|
m_pBoneMergeCache = NULL;
|
|
// recreated in BuildTransformations
|
|
}
|
|
|
|
Studio_DestroyBoneCache( m_hitboxBoneCacheHandle );
|
|
m_hitboxBoneCacheHandle = 0;
|
|
|
|
// Make sure m_CachedBones has space.
|
|
if ( m_CachedBoneData.Count() != hdr->numbones() )
|
|
{
|
|
m_CachedBoneData.SetSize( hdr->numbones() );
|
|
for ( int i=0; i < hdr->numbones(); i++ )
|
|
{
|
|
SetIdentityMatrix( m_CachedBoneData[i] );
|
|
}
|
|
}
|
|
m_BoneAccessor.Init( this, m_CachedBoneData.Base() ); // Always call this in case the studiohdr_t has changed.
|
|
|
|
// Free any IK data
|
|
if (m_pIk)
|
|
{
|
|
delete m_pIk;
|
|
m_pIk = NULL;
|
|
}
|
|
|
|
// Don't reallocate unless a different size.
|
|
if ( m_Attachments.Count() != hdr->GetNumAttachments() )
|
|
{
|
|
m_Attachments.SetSize( hdr->GetNumAttachments() );
|
|
|
|
// This is to make sure we don't use the attachment before its been set up
|
|
for ( int i=0; i < m_Attachments.Count(); i++ )
|
|
{
|
|
m_Attachments[i].m_bAnglesComputed = false;
|
|
m_Attachments[i].m_nLastFramecount = 0;
|
|
#ifdef _DEBUG
|
|
m_Attachments[i].m_AttachmentToWorld.Invalidate();
|
|
m_Attachments[i].m_angRotation.Init( VEC_T_NAN, VEC_T_NAN, VEC_T_NAN );
|
|
m_Attachments[i].m_vOriginVelocity.Init( VEC_T_NAN, VEC_T_NAN, VEC_T_NAN );
|
|
#endif
|
|
}
|
|
|
|
}
|
|
|
|
Assert( hdr->GetNumPoseParameters() <= ARRAYSIZE( m_flPoseParameter ) );
|
|
|
|
m_iv_flPoseParameter.SetMaxCount(hdr->GetNumPoseParameters());
|
|
|
|
int i;
|
|
for ( i = 0; i < hdr->GetNumPoseParameters() ; i++ )
|
|
{
|
|
const mstudioposeparamdesc_t &Pose = hdr->pPoseParameter( i );
|
|
// Note: We can't do this since if we get a DATA_UPDATE_CREATED (i.e., new entity) with both a new model and some valid pose parameters this will slam the
|
|
// pose parameters to zero and if the model goes dormant the pose parameter field will never be set to the true value. We shouldn't have to zero these out
|
|
// as they are under the control of the server and should be properly set
|
|
if ( !IsServerEntity() )
|
|
{
|
|
SetPoseParameter( hdr, i, 0.0 );
|
|
}
|
|
}
|
|
|
|
int boneControllerCount = MIN( hdr->numbonecontrollers(), ARRAYSIZE( m_flEncodedController ) );
|
|
|
|
m_iv_flEncodedController.SetMaxCount( boneControllerCount );
|
|
|
|
for ( i = 0; i < boneControllerCount ; i++ )
|
|
{
|
|
bool loop = (hdr->pBonecontroller( i )->type & (STUDIO_XR | STUDIO_YR | STUDIO_ZR)) != 0;
|
|
SetBoneController( i, 0.0 );
|
|
}
|
|
|
|
InitModelEffects();
|
|
|
|
// lookup generic eye attachment, if exists
|
|
m_iEyeAttachment = LookupAttachment( "eyes" );
|
|
|
|
// If we didn't have a model before, then we might need to go in the interpolation list now.
|
|
if ( ShouldInterpolate() )
|
|
AddToInterpolationList();
|
|
|
|
// objects with attachment points need to be queryable even if they're not solid
|
|
if ( hdr->GetNumAttachments() != 0 )
|
|
{
|
|
AddEFlags( EFL_USE_PARTITION_WHEN_NOT_SOLID );
|
|
}
|
|
|
|
|
|
// Most entities clear out their sequences when they change models on the server, but
|
|
// not all entities network down their m_nSequence (like multiplayer game player entities),
|
|
// so we may need to clear it out here. Force a SetSequence call no matter what, though.
|
|
int forceSequence = ShouldResetSequenceOnNewModel() ? 0 : m_nSequence;
|
|
|
|
if ( GetSequence() >= hdr->GetNumSeq() )
|
|
{
|
|
forceSequence = 0;
|
|
}
|
|
|
|
m_nSequence = -1;
|
|
SetSequence( forceSequence );
|
|
|
|
if ( m_bResetSequenceInfoOnLoad )
|
|
{
|
|
m_bResetSequenceInfoOnLoad = false;
|
|
ResetSequenceInfo();
|
|
}
|
|
|
|
return hdr;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns index number of a given named bone
|
|
// Input : name of a bone
|
|
// Output : Bone index number or -1 if bone not found
|
|
//-----------------------------------------------------------------------------
|
|
int C_BaseAnimating::LookupBone( const char *szName )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
|
|
return Studio_BoneIndexByName( GetModelPtr(), szName );
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
void C_BaseAnimating::GetBonePosition ( int iBone, Vector &origin, QAngle &angles )
|
|
{
|
|
matrix3x4_t bonetoworld;
|
|
GetBoneTransform( iBone, bonetoworld );
|
|
|
|
MatrixAngles( bonetoworld, angles, origin );
|
|
}
|
|
|
|
void C_BaseAnimating::GetBoneTransform( int iBone, matrix3x4_t &pBoneToWorld )
|
|
{
|
|
Assert( GetModelPtr() && iBone >= 0 && iBone < GetModelPtr()->numbones() );
|
|
CBoneCache *pcache = GetBoneCache( NULL );
|
|
|
|
matrix3x4_t *pmatrix = pcache->GetCachedBone( iBone );
|
|
|
|
if ( !pmatrix )
|
|
{
|
|
MatrixCopy( EntityToWorldTransform(), pBoneToWorld );
|
|
return;
|
|
}
|
|
|
|
Assert( pmatrix );
|
|
|
|
// FIXME
|
|
MatrixCopy( *pmatrix, pBoneToWorld );
|
|
}
|
|
//=============================================================================
|
|
// HPE_BEGIN:
|
|
// [menglish] Finds the bone associated with the given hitbox
|
|
//=============================================================================
|
|
|
|
int C_BaseAnimating::GetHitboxBone( int hitboxIndex )
|
|
{
|
|
CStudioHdr *pStudioHdr = GetModelPtr();
|
|
if ( pStudioHdr )
|
|
{
|
|
mstudiohitboxset_t *set =pStudioHdr->pHitboxSet( m_nHitboxSet );
|
|
if ( set && hitboxIndex < set->numhitboxes )
|
|
{
|
|
return set->pHitbox( hitboxIndex )->bone;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//=============================================================================
|
|
// HPE_END
|
|
//=============================================================================
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Setup to initialize our model effects once the model's loaded
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::InitModelEffects( void )
|
|
{
|
|
m_bInitModelEffects = true;
|
|
TermRopes();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Load the model's keyvalues section and create effects listed inside it
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::DelayedInitModelEffects( void )
|
|
{
|
|
m_bInitModelEffects = false;
|
|
|
|
// Parse the keyvalues and see if they want to make ropes on this model.
|
|
KeyValues * modelKeyValues = new KeyValues("");
|
|
if ( modelKeyValues->LoadFromBuffer( modelinfo->GetModelName( GetModel() ), modelinfo->GetModelKeyValueText( GetModel() ) ) )
|
|
{
|
|
// Do we have a cables section?
|
|
KeyValues *pkvAllCables = modelKeyValues->FindKey("Cables");
|
|
if ( pkvAllCables )
|
|
{
|
|
// Start grabbing the sounds and slotting them in
|
|
for ( KeyValues *pSingleCable = pkvAllCables->GetFirstSubKey(); pSingleCable; pSingleCable = pSingleCable->GetNextKey() )
|
|
{
|
|
C_RopeKeyframe *pRope = C_RopeKeyframe::CreateFromKeyValues( this, pSingleCable );
|
|
m_Ropes.AddToTail( pRope );
|
|
}
|
|
}
|
|
|
|
if ( !m_bNoModelParticles )
|
|
{
|
|
// Do we have a particles section?
|
|
KeyValues *pkvAllParticleEffects = modelKeyValues->FindKey("Particles");
|
|
if ( pkvAllParticleEffects )
|
|
{
|
|
// Start grabbing the sounds and slotting them in
|
|
for ( KeyValues *pSingleEffect = pkvAllParticleEffects->GetFirstSubKey(); pSingleEffect; pSingleEffect = pSingleEffect->GetNextKey() )
|
|
{
|
|
const char *pszParticleEffect = pSingleEffect->GetString( "name", "" );
|
|
const char *pszAttachment = pSingleEffect->GetString( "attachment_point", "" );
|
|
const char *pszAttachType = pSingleEffect->GetString( "attachment_type", "" );
|
|
|
|
// Convert attach type
|
|
int iAttachType = GetAttachTypeFromString( pszAttachType );
|
|
if ( iAttachType == -1 )
|
|
{
|
|
Warning("Invalid attach type specified for particle effect in model '%s' keyvalues section. Trying to spawn effect '%s' with attach type of '%s'\n", GetModelName(), pszParticleEffect, pszAttachType );
|
|
return;
|
|
}
|
|
|
|
// Convert attachment point
|
|
int iAttachment = atoi(pszAttachment);
|
|
// See if we can find any attachment points matching the name
|
|
if ( pszAttachment[0] != '0' && iAttachment == 0 )
|
|
{
|
|
iAttachment = LookupAttachment( pszAttachment );
|
|
if ( iAttachment <= 0 )
|
|
{
|
|
Warning("Failed to find attachment point specified for particle effect in model '%s' keyvalues section. Trying to spawn effect '%s' on attachment named '%s'\n", GetModelName(), pszParticleEffect, pszAttachment );
|
|
return;
|
|
}
|
|
}
|
|
#ifdef TF_CLIENT_DLL
|
|
// Halloween Hack for Sentry Rockets
|
|
if ( !V_strcmp( "sentry_rocket", pszParticleEffect ) )
|
|
{
|
|
// Halloween Spell Effect Check
|
|
int iHalloweenSpell = 0;
|
|
// if the owner is a Sentry, Check its owner
|
|
CBaseObject *pSentry = dynamic_cast<CBaseObject*>( GetOwnerEntity() );
|
|
if ( pSentry )
|
|
{
|
|
CALL_ATTRIB_HOOK_INT_ON_OTHER( pSentry->GetOwner(), iHalloweenSpell, halloween_pumpkin_explosions );
|
|
}
|
|
else
|
|
{
|
|
CALL_ATTRIB_HOOK_INT_ON_OTHER( GetOwnerEntity(), iHalloweenSpell, halloween_pumpkin_explosions );
|
|
}
|
|
|
|
if ( iHalloweenSpell > 0 )
|
|
{
|
|
pszParticleEffect = "halloween_rockettrail";
|
|
}
|
|
}
|
|
#endif
|
|
// Spawn the particle effect
|
|
ParticleProp()->Create( pszParticleEffect, (ParticleAttachment_t)iAttachType, iAttachment );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
modelKeyValues->deleteThis();
|
|
}
|
|
|
|
|
|
void C_BaseAnimating::TermRopes()
|
|
{
|
|
FOR_EACH_LL( m_Ropes, i )
|
|
m_Ropes[i]->Release();
|
|
|
|
m_Ropes.Purge();
|
|
}
|
|
|
|
|
|
// FIXME: redundant?
|
|
void C_BaseAnimating::GetBoneControllers(float controllers[MAXSTUDIOBONECTRLS])
|
|
{
|
|
// interpolate two 0..1 encoded controllers to a single 0..1 controller
|
|
int i;
|
|
for( i=0; i < MAXSTUDIOBONECTRLS; i++)
|
|
{
|
|
controllers[ i ] = m_flEncodedController[ i ];
|
|
}
|
|
}
|
|
|
|
float C_BaseAnimating::GetPoseParameter( int iPoseParameter )
|
|
{
|
|
CStudioHdr *pStudioHdr = GetModelPtr();
|
|
|
|
if ( pStudioHdr == NULL )
|
|
return 0.0f;
|
|
|
|
if ( pStudioHdr->GetNumPoseParameters() < iPoseParameter )
|
|
return 0.0f;
|
|
|
|
if ( iPoseParameter < 0 )
|
|
return 0.0f;
|
|
|
|
return m_flPoseParameter[iPoseParameter];
|
|
}
|
|
|
|
// FIXME: redundant?
|
|
void C_BaseAnimating::GetPoseParameters( CStudioHdr *pStudioHdr, float poseParameter[MAXSTUDIOPOSEPARAM])
|
|
{
|
|
if ( !pStudioHdr )
|
|
return;
|
|
|
|
// interpolate pose parameters
|
|
int i;
|
|
for( i=0; i < pStudioHdr->GetNumPoseParameters(); i++)
|
|
{
|
|
poseParameter[i] = m_flPoseParameter[i];
|
|
}
|
|
|
|
|
|
#if 0 // _DEBUG
|
|
if (/* Q_stristr( pStudioHdr->pszName(), r_sequence_debug.GetString()) != NULL || */ r_sequence_debug.GetInt() == entindex())
|
|
{
|
|
DevMsgRT( "%s\n", pStudioHdr->pszName() );
|
|
DevMsgRT( "%6.2f : ", gpGlobals->curtime );
|
|
for( i=0; i < pStudioHdr->GetNumPoseParameters(); i++)
|
|
{
|
|
const mstudioposeparamdesc_t &Pose = pStudioHdr->pPoseParameter( i );
|
|
|
|
DevMsgRT( "%s %6.2f ", Pose.pszName(), poseParameter[i] * Pose.end + (1 - poseParameter[i]) * Pose.start );
|
|
}
|
|
DevMsgRT( "\n" );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
float C_BaseAnimating::ClampCycle( float flCycle, bool isLooping )
|
|
{
|
|
if (isLooping)
|
|
{
|
|
// FIXME: does this work with negative framerate?
|
|
flCycle -= (int)flCycle;
|
|
if (flCycle < 0.0f)
|
|
{
|
|
flCycle += 1.0f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
flCycle = clamp( flCycle, 0.0f, 0.999f );
|
|
}
|
|
return flCycle;
|
|
}
|
|
|
|
|
|
void C_BaseAnimating::GetCachedBoneMatrix( int boneIndex, matrix3x4_t &out )
|
|
{
|
|
MatrixCopy( GetBone( boneIndex ), out );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: move position and rotation transforms into global matrices
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::BuildTransformations( CStudioHdr *hdr, Vector *pos, Quaternion *q, const matrix3x4_t &cameraTransform, int boneMask, CBoneBitList &boneComputed, float flCurrentTime )
|
|
{
|
|
VPROF_BUDGET( "C_BaseAnimating::BuildTransformations", VPROF_BUDGETGROUP_CLIENT_ANIMATION );
|
|
|
|
if ( !hdr )
|
|
return;
|
|
|
|
matrix3x4_t bonematrix;
|
|
bool boneSimulated[MAXSTUDIOBONES];
|
|
|
|
// no bones have been simulated
|
|
memset( boneSimulated, 0, sizeof(boneSimulated) );
|
|
mstudiobone_t *pbones = hdr->pBone( 0 );
|
|
|
|
if ( m_pRagdoll )
|
|
{
|
|
// simulate bones and update flags
|
|
int oldWritableBones = m_BoneAccessor.GetWritableBones();
|
|
int oldReadableBones = m_BoneAccessor.GetReadableBones();
|
|
m_BoneAccessor.SetWritableBones( BONE_USED_BY_ANYTHING );
|
|
m_BoneAccessor.SetReadableBones( BONE_USED_BY_ANYTHING );
|
|
|
|
#if defined( REPLAY_ENABLED )
|
|
// If we're playing back a demo, override the ragdoll bones with cached version if available - otherwise, simulate.
|
|
if ( ( !engine->IsPlayingDemo() && !engine->IsPlayingTimeDemo() ) ||
|
|
!CReplayRagdollCache::Instance().IsInitialized() ||
|
|
!CReplayRagdollCache::Instance().GetFrame( this, engine->GetDemoPlaybackTick(), boneSimulated, &m_BoneAccessor ) )
|
|
#endif
|
|
{
|
|
m_pRagdoll->RagdollBone( this, pbones, hdr->numbones(), boneSimulated, m_BoneAccessor );
|
|
}
|
|
|
|
m_BoneAccessor.SetWritableBones( oldWritableBones );
|
|
m_BoneAccessor.SetReadableBones( oldReadableBones );
|
|
}
|
|
|
|
// For EF_BONEMERGE entities, copy the bone matrices for any bones that have matching names.
|
|
bool boneMerge = IsEffectActive(EF_BONEMERGE);
|
|
if ( boneMerge || m_pBoneMergeCache )
|
|
{
|
|
if ( boneMerge )
|
|
{
|
|
if ( !m_pBoneMergeCache )
|
|
{
|
|
m_pBoneMergeCache = new CBoneMergeCache;
|
|
m_pBoneMergeCache->Init( this );
|
|
}
|
|
m_pBoneMergeCache->MergeMatchingBones( boneMask );
|
|
}
|
|
else
|
|
{
|
|
delete m_pBoneMergeCache;
|
|
m_pBoneMergeCache = NULL;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < hdr->numbones(); i++)
|
|
{
|
|
// Only update bones reference by the bone mask.
|
|
if ( !( hdr->boneFlags( i ) & boneMask ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( m_pBoneMergeCache && m_pBoneMergeCache->IsBoneMerged( i ) )
|
|
continue;
|
|
|
|
// animate all non-simulated bones
|
|
if ( boneSimulated[i] || CalcProceduralBone( hdr, i, m_BoneAccessor ))
|
|
{
|
|
continue;
|
|
}
|
|
// skip bones that the IK has already setup
|
|
else if (boneComputed.IsBoneMarked( i ))
|
|
{
|
|
// dummy operation, just used to verify in debug that this should have happened
|
|
GetBoneForWrite( i );
|
|
}
|
|
else
|
|
{
|
|
QuaternionMatrix( q[i], pos[i], bonematrix );
|
|
|
|
Assert( fabs( pos[i].x ) < 100000 );
|
|
Assert( fabs( pos[i].y ) < 100000 );
|
|
Assert( fabs( pos[i].z ) < 100000 );
|
|
|
|
if ( (hdr->boneFlags( i ) & BONE_ALWAYS_PROCEDURAL) &&
|
|
(hdr->pBone( i )->proctype & STUDIO_PROC_JIGGLE) )
|
|
{
|
|
//
|
|
// Physics-based "jiggle" bone
|
|
// Bone is assumed to be along the Z axis
|
|
// Pitch around X, yaw around Y
|
|
//
|
|
|
|
// compute desired bone orientation
|
|
matrix3x4_t goalMX;
|
|
|
|
if (pbones[i].parent == -1)
|
|
{
|
|
ConcatTransforms( cameraTransform, bonematrix, goalMX );
|
|
}
|
|
else
|
|
{
|
|
ConcatTransforms( GetBone( pbones[i].parent ), bonematrix, goalMX );
|
|
}
|
|
|
|
// get jiggle properties from QC data
|
|
mstudiojigglebone_t *jiggleInfo = (mstudiojigglebone_t *)pbones[i].pProcedure( );
|
|
|
|
if (!m_pJiggleBones)
|
|
{
|
|
m_pJiggleBones = new CJiggleBones;
|
|
}
|
|
|
|
// do jiggle physics
|
|
m_pJiggleBones->BuildJiggleTransformations( i, flCurrentTime, jiggleInfo, goalMX, GetBoneForWrite( i ) );
|
|
|
|
}
|
|
else if (hdr->boneParent(i) == -1)
|
|
{
|
|
ConcatTransforms( cameraTransform, bonematrix, GetBoneForWrite( i ) );
|
|
}
|
|
else
|
|
{
|
|
ConcatTransforms( GetBone( hdr->boneParent(i) ), bonematrix, GetBoneForWrite( i ) );
|
|
}
|
|
}
|
|
|
|
if (hdr->boneParent(i) == -1)
|
|
{
|
|
// Apply client-side effects to the transformation matrix
|
|
ApplyBoneMatrixTransform( GetBoneForWrite( i ) );
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Special effects
|
|
// Input : transform -
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::ApplyBoneMatrixTransform( matrix3x4_t& transform )
|
|
{
|
|
switch( m_nRenderFX )
|
|
{
|
|
case kRenderFxDistort:
|
|
case kRenderFxHologram:
|
|
if ( RandomInt(0,49) == 0 )
|
|
{
|
|
int axis = RandomInt(0,1);
|
|
if ( axis == 1 ) // Choose between x & z
|
|
axis = 2;
|
|
VectorScale( transform[axis], RandomFloat(1,1.484), transform[axis] );
|
|
}
|
|
else if ( RandomInt(0,49) == 0 )
|
|
{
|
|
float offset;
|
|
int axis = RandomInt(0,1);
|
|
if ( axis == 1 ) // Choose between x & z
|
|
axis = 2;
|
|
offset = RandomFloat(-10,10);
|
|
transform[RandomInt(0,2)][3] += offset;
|
|
}
|
|
break;
|
|
case kRenderFxExplode:
|
|
{
|
|
float scale;
|
|
|
|
scale = 1.0 + (gpGlobals->curtime - m_flAnimTime) * 10.0;
|
|
if ( scale > 2 ) // Don't blow up more than 200%
|
|
scale = 2;
|
|
transform[0][1] *= scale;
|
|
transform[1][1] *= scale;
|
|
transform[2][1] *= scale;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
|
|
}
|
|
|
|
if ( IsModelScaled() )
|
|
{
|
|
// The bone transform is in worldspace, so to scale this, we need to translate it back
|
|
float scale = GetModelScale();
|
|
|
|
Vector pos;
|
|
MatrixGetColumn( transform, 3, pos );
|
|
pos -= GetRenderOrigin();
|
|
pos *= scale;
|
|
pos += GetRenderOrigin();
|
|
MatrixSetColumn( pos, 3, transform );
|
|
|
|
VectorScale( transform[0], scale, transform[0] );
|
|
VectorScale( transform[1], scale, transform[1] );
|
|
VectorScale( transform[2], scale, transform[2] );
|
|
}
|
|
}
|
|
|
|
void C_BaseAnimating::CreateUnragdollInfo( C_BaseAnimating *pRagdoll )
|
|
{
|
|
CStudioHdr *hdr = GetModelPtr();
|
|
if ( !hdr )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// It's already an active ragdoll, sigh
|
|
if ( m_pRagdollInfo && m_pRagdollInfo->m_bActive )
|
|
{
|
|
Assert( 0 );
|
|
return;
|
|
}
|
|
|
|
// Now do the current bone setup
|
|
pRagdoll->SetupBones( NULL, -1, BONE_USED_BY_ANYTHING, gpGlobals->curtime );
|
|
|
|
matrix3x4_t parentTransform;
|
|
QAngle newAngles( 0, pRagdoll->GetAbsAngles()[YAW], 0 );
|
|
|
|
AngleMatrix( GetAbsAngles(), GetAbsOrigin(), parentTransform );
|
|
// pRagdoll->SaveRagdollInfo( hdr->numbones, parentTransform, m_BoneAccessor );
|
|
|
|
if ( !m_pRagdollInfo )
|
|
{
|
|
m_pRagdollInfo = new RagdollInfo_t;
|
|
Assert( m_pRagdollInfo );
|
|
if ( !m_pRagdollInfo )
|
|
{
|
|
Msg( "Memory allocation of RagdollInfo_t failed!\n" );
|
|
return;
|
|
}
|
|
}
|
|
|
|
Q_memset( m_pRagdollInfo, 0, sizeof( *m_pRagdollInfo ) );
|
|
|
|
int numbones = hdr->numbones();
|
|
|
|
m_pRagdollInfo->m_bActive = true;
|
|
m_pRagdollInfo->m_flSaveTime = gpGlobals->curtime;
|
|
m_pRagdollInfo->m_nNumBones = numbones;
|
|
|
|
for ( int i = 0; i < numbones; i++ )
|
|
{
|
|
matrix3x4_t inverted;
|
|
matrix3x4_t output;
|
|
|
|
if ( hdr->boneParent(i) == -1 )
|
|
{
|
|
// Decompose into parent space
|
|
MatrixInvert( parentTransform, inverted );
|
|
}
|
|
else
|
|
{
|
|
MatrixInvert( pRagdoll->m_BoneAccessor.GetBone( hdr->boneParent(i) ), inverted );
|
|
}
|
|
|
|
ConcatTransforms( inverted, pRagdoll->m_BoneAccessor.GetBone( i ), output );
|
|
|
|
MatrixAngles( output,
|
|
m_pRagdollInfo->m_rgBoneQuaternion[ i ],
|
|
m_pRagdollInfo->m_rgBonePos[ i ] );
|
|
}
|
|
}
|
|
|
|
void C_BaseAnimating::SaveRagdollInfo( int numbones, const matrix3x4_t &cameraTransform, CBoneAccessor &pBoneToWorld )
|
|
{
|
|
CStudioHdr *hdr = GetModelPtr();
|
|
if ( !hdr )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( !m_pRagdollInfo )
|
|
{
|
|
m_pRagdollInfo = new RagdollInfo_t;
|
|
Assert( m_pRagdollInfo );
|
|
if ( !m_pRagdollInfo )
|
|
{
|
|
Msg( "Memory allocation of RagdollInfo_t failed!\n" );
|
|
return;
|
|
}
|
|
memset( m_pRagdollInfo, 0, sizeof( *m_pRagdollInfo ) );
|
|
}
|
|
|
|
mstudiobone_t *pbones = hdr->pBone( 0 );
|
|
|
|
m_pRagdollInfo->m_bActive = true;
|
|
m_pRagdollInfo->m_flSaveTime = gpGlobals->curtime;
|
|
m_pRagdollInfo->m_nNumBones = numbones;
|
|
|
|
for ( int i = 0; i < numbones; i++ )
|
|
{
|
|
matrix3x4_t inverted;
|
|
matrix3x4_t output;
|
|
|
|
if ( pbones[i].parent == -1 )
|
|
{
|
|
// Decompose into parent space
|
|
MatrixInvert( cameraTransform, inverted );
|
|
}
|
|
else
|
|
{
|
|
MatrixInvert( pBoneToWorld.GetBone( pbones[ i ].parent ), inverted );
|
|
}
|
|
|
|
ConcatTransforms( inverted, pBoneToWorld.GetBone( i ), output );
|
|
|
|
MatrixAngles( output,
|
|
m_pRagdollInfo->m_rgBoneQuaternion[ i ],
|
|
m_pRagdollInfo->m_rgBonePos[ i ] );
|
|
}
|
|
}
|
|
|
|
bool C_BaseAnimating::RetrieveRagdollInfo( Vector *pos, Quaternion *q )
|
|
{
|
|
if ( !m_bStoreRagdollInfo || !m_pRagdollInfo || !m_pRagdollInfo->m_bActive )
|
|
return false;
|
|
|
|
for ( int i = 0; i < m_pRagdollInfo->m_nNumBones; i++ )
|
|
{
|
|
pos[ i ] = m_pRagdollInfo->m_rgBonePos[ i ];
|
|
q[ i ] = m_pRagdollInfo->m_rgBoneQuaternion[ i ];
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Should we collide?
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CollideType_t C_BaseAnimating::GetCollideType( void )
|
|
{
|
|
if ( IsRagdoll() )
|
|
return ENTITY_SHOULD_RESPOND;
|
|
|
|
return BaseClass::GetCollideType();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: if the active sequence changes, keep track of the previous ones and decay them based on their decay rate
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::MaintainSequenceTransitions( IBoneSetup &boneSetup, float flCycle, float currentTime, Vector pos[], Quaternion q[] )
|
|
{
|
|
VPROF( "C_BaseAnimating::MaintainSequenceTransitions" );
|
|
|
|
if ( !boneSetup.GetStudioHdr() )
|
|
return;
|
|
|
|
if ( prediction->InPrediction() )
|
|
{
|
|
m_nPrevNewSequenceParity = m_nNewSequenceParity;
|
|
return;
|
|
}
|
|
|
|
m_SequenceTransitioner.CheckForSequenceChange(
|
|
boneSetup.GetStudioHdr(),
|
|
GetSequence(),
|
|
m_nNewSequenceParity != m_nPrevNewSequenceParity,
|
|
!IsNoInterpolationFrame()
|
|
);
|
|
|
|
m_nPrevNewSequenceParity = m_nNewSequenceParity;
|
|
|
|
// Update the transition sequence list.
|
|
m_SequenceTransitioner.UpdateCurrent(
|
|
boneSetup.GetStudioHdr(),
|
|
GetSequence(),
|
|
flCycle,
|
|
m_flPlaybackRate,
|
|
currentTime
|
|
);
|
|
|
|
|
|
// process previous sequences
|
|
for (int i = m_SequenceTransitioner.m_animationQueue.Count() - 2; i >= 0; i--)
|
|
{
|
|
C_AnimationLayer *blend = &m_SequenceTransitioner.m_animationQueue[i];
|
|
|
|
float dt = (currentTime - blend->m_flLayerAnimtime);
|
|
flCycle = blend->m_flCycle + dt * blend->m_flPlaybackRate * GetSequenceCycleRate( boneSetup.GetStudioHdr(), blend->m_nSequence );
|
|
flCycle = ClampCycle( flCycle, IsSequenceLooping( boneSetup.GetStudioHdr(), blend->m_nSequence ) );
|
|
|
|
#if 1 // _DEBUG
|
|
if (/*Q_stristr( hdr->pszName(), r_sequence_debug.GetString()) != NULL || */ r_sequence_debug.GetInt() == entindex())
|
|
{
|
|
DevMsgRT( "%8.4f : %30s : %5.3f : %4.2f +\n", currentTime, boneSetup.GetStudioHdr()->pSeqdesc( blend->m_nSequence ).pszLabel(), flCycle, (float)blend->m_flWeight );
|
|
}
|
|
#endif
|
|
|
|
boneSetup.AccumulatePose( pos, q, blend->m_nSequence, flCycle, blend->m_flWeight, currentTime, m_pIk );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *hdr -
|
|
// pos[] -
|
|
// q[] -
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::UnragdollBlend( CStudioHdr *hdr, Vector pos[], Quaternion q[], float currentTime )
|
|
{
|
|
if ( !hdr )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( !m_pRagdollInfo || !m_pRagdollInfo->m_bActive )
|
|
return;
|
|
|
|
float dt = currentTime - m_pRagdollInfo->m_flSaveTime;
|
|
if ( dt > 0.2f )
|
|
{
|
|
m_pRagdollInfo->m_bActive = false;
|
|
return;
|
|
}
|
|
|
|
// Slerp bone sets together
|
|
float frac = dt / 0.2f;
|
|
frac = clamp( frac, 0.0f, 1.0f );
|
|
|
|
int i;
|
|
for ( i = 0; i < hdr->numbones(); i++ )
|
|
{
|
|
VectorLerp( m_pRagdollInfo->m_rgBonePos[ i ], pos[ i ], frac, pos[ i ] );
|
|
QuaternionSlerp( m_pRagdollInfo->m_rgBoneQuaternion[ i ], q[ i ], frac, q[ i ] );
|
|
}
|
|
}
|
|
|
|
void C_BaseAnimating::AccumulateLayers( IBoneSetup &boneSetup, Vector pos[], Quaternion q[], float currentTime )
|
|
{
|
|
// Nothing here
|
|
}
|
|
|
|
void C_BaseAnimating::ChildLayerBlend( Vector pos[], Quaternion q[], float currentTime, int boneMask )
|
|
{
|
|
return;
|
|
|
|
Vector childPos[MAXSTUDIOBONES];
|
|
Quaternion childQ[MAXSTUDIOBONES];
|
|
float childPoseparam[MAXSTUDIOPOSEPARAM];
|
|
|
|
// go through all children
|
|
for ( C_BaseEntity *pChild = FirstMoveChild(); pChild; pChild = pChild->NextMovePeer() )
|
|
{
|
|
C_BaseAnimating *pChildAnimating = pChild->GetBaseAnimating();
|
|
|
|
if ( pChildAnimating )
|
|
{
|
|
CStudioHdr *pChildHdr = pChildAnimating->GetModelPtr();
|
|
|
|
// FIXME: needs a new type of EF_BONEMERGE (EF_CHILDMERGE?)
|
|
if ( pChildHdr && pChild->IsEffectActive( EF_BONEMERGE ) && pChildHdr->SequencesAvailable() && pChildAnimating->m_pBoneMergeCache )
|
|
{
|
|
// FIXME: these should Inherit from the parent
|
|
GetPoseParameters( pChildHdr, childPoseparam );
|
|
|
|
IBoneSetup childBoneSetup( pChildHdr, boneMask, childPoseparam );
|
|
childBoneSetup.InitPose( childPos, childQ );
|
|
|
|
// set up the child into the parent's current pose
|
|
pChildAnimating->m_pBoneMergeCache->CopyParentToChild( pos, q, childPos, childQ, boneMask );
|
|
|
|
// FIXME: needs some kind of sequence
|
|
// merge over whatever bones the childs sequence modifies
|
|
childBoneSetup.AccumulatePose( childPos, childQ, 0, GetCycle(), 1.0, currentTime, NULL );
|
|
|
|
// copy the result back into the parents bones
|
|
pChildAnimating->m_pBoneMergeCache->CopyChildToParent( childPos, childQ, pos, q, boneMask );
|
|
|
|
// probably needs an IK merge system of some sort =(
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Do the default sequence blending rules as done in HL1
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::StandardBlendingRules( CStudioHdr *hdr, Vector pos[], Quaternion q[], float currentTime, int boneMask )
|
|
{
|
|
VPROF( "C_BaseAnimating::StandardBlendingRules" );
|
|
|
|
float poseparam[MAXSTUDIOPOSEPARAM];
|
|
|
|
if ( !hdr )
|
|
return;
|
|
|
|
if ( !hdr->SequencesAvailable() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (GetSequence() >= hdr->GetNumSeq() || GetSequence() == -1 )
|
|
{
|
|
SetSequence( 0 );
|
|
}
|
|
|
|
GetPoseParameters( hdr, poseparam );
|
|
|
|
// build root animation
|
|
float fCycle = GetCycle();
|
|
|
|
#if 1 //_DEBUG
|
|
if (/* Q_stristr( hdr->pszName(), r_sequence_debug.GetString()) != NULL || */ r_sequence_debug.GetInt() == entindex())
|
|
{
|
|
DevMsgRT( "%8.4f : %30s : %5.3f : %4.2f\n", currentTime, hdr->pSeqdesc( GetSequence() ).pszLabel(), fCycle, 1.0 );
|
|
}
|
|
#endif
|
|
|
|
IBoneSetup boneSetup( hdr, boneMask, poseparam );
|
|
boneSetup.InitPose( pos, q );
|
|
boneSetup.AccumulatePose( pos, q, GetSequence(), fCycle, 1.0, currentTime, m_pIk );
|
|
|
|
// debugoverlay->AddTextOverlay( GetAbsOrigin() + Vector( 0, 0, 64 ), 0, 0, "%30s %6.2f : %6.2f", hdr->pSeqdesc( GetSequence() )->pszLabel( ), fCycle, 1.0 );
|
|
|
|
// TODO_ENHANCED: Do that only for client side animations
|
|
// if (m_bClientSideAnimation)
|
|
MaintainSequenceTransitions( boneSetup, fCycle, currentTime, pos, q );
|
|
|
|
AccumulateLayers( boneSetup, pos, q, currentTime );
|
|
|
|
if ( m_pIk )
|
|
{
|
|
CIKContext auto_ik;
|
|
auto_ik.Init( hdr, GetRenderAngles(), GetRenderOrigin(), currentTime, gpGlobals->framecount, boneMask );
|
|
boneSetup.CalcAutoplaySequences( pos, q, currentTime, &auto_ik );
|
|
}
|
|
else
|
|
{
|
|
boneSetup.CalcAutoplaySequences( pos, q, currentTime, NULL );
|
|
}
|
|
|
|
if ( hdr->numbonecontrollers() )
|
|
{
|
|
float controllers[MAXSTUDIOBONECTRLS];
|
|
GetBoneControllers(controllers);
|
|
boneSetup.CalcBoneAdj( pos, q, controllers );
|
|
}
|
|
|
|
ChildLayerBlend( pos, q, currentTime, boneMask );
|
|
|
|
UnragdollBlend( hdr, pos, q, currentTime );
|
|
|
|
#ifdef STUDIO_ENABLE_PERF_COUNTERS
|
|
#if _DEBUG
|
|
if (Q_stristr( hdr->pszName(), r_sequence_debug.GetString()) != NULL)
|
|
{
|
|
DevMsgRT( "layers %4d : bones %4d : animated %4d\n", hdr->m_nPerfAnimationLayers, hdr->m_nPerfUsedBones, hdr->m_nPerfAnimatedBones );
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Put a value into an attachment point by index
|
|
// Input : number - which point
|
|
// Output : float * - the attachment point
|
|
//-----------------------------------------------------------------------------
|
|
bool C_BaseAnimating::PutAttachment( int number, const matrix3x4_t &attachmentToWorld )
|
|
{
|
|
if ( number < 1 || number > m_Attachments.Count() )
|
|
return false;
|
|
|
|
CAttachmentData *pAtt = &m_Attachments[number-1];
|
|
if ( gpGlobals->frametime > 0 && pAtt->m_nLastFramecount > 0 && pAtt->m_nLastFramecount == gpGlobals->framecount - 1 )
|
|
{
|
|
Vector vecPreviousOrigin, vecOrigin;
|
|
MatrixPosition( pAtt->m_AttachmentToWorld, vecPreviousOrigin );
|
|
MatrixPosition( attachmentToWorld, vecOrigin );
|
|
pAtt->m_vOriginVelocity = (vecOrigin - vecPreviousOrigin) / gpGlobals->frametime;
|
|
}
|
|
else
|
|
{
|
|
pAtt->m_vOriginVelocity.Init();
|
|
}
|
|
pAtt->m_nLastFramecount = gpGlobals->framecount;
|
|
pAtt->m_bAnglesComputed = false;
|
|
pAtt->m_AttachmentToWorld = attachmentToWorld;
|
|
|
|
#ifdef _DEBUG
|
|
pAtt->m_angRotation.Init( VEC_T_NAN, VEC_T_NAN, VEC_T_NAN );
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void C_BaseAnimating::SetupBones_AttachmentHelper( CStudioHdr *hdr )
|
|
{
|
|
if ( !hdr || !hdr->GetNumAttachments() )
|
|
return;
|
|
|
|
// calculate attachment points
|
|
matrix3x4_t world;
|
|
for (int i = 0; i < hdr->GetNumAttachments(); i++)
|
|
{
|
|
const mstudioattachment_t &pattachment = hdr->pAttachment( i );
|
|
int iBone = hdr->GetAttachmentBone( i );
|
|
if ( (pattachment.flags & ATTACHMENT_FLAG_WORLD_ALIGN) == 0 )
|
|
{
|
|
ConcatTransforms( GetBone( iBone ), pattachment.local, world );
|
|
}
|
|
else
|
|
{
|
|
Vector vecLocalBonePos, vecWorldBonePos;
|
|
MatrixGetColumn( pattachment.local, 3, vecLocalBonePos );
|
|
VectorTransform( vecLocalBonePos, GetBone( iBone ), vecWorldBonePos );
|
|
|
|
SetIdentityMatrix( world );
|
|
MatrixSetColumn( vecWorldBonePos, 3, world );
|
|
}
|
|
|
|
// FIXME: this shouldn't be here, it should client side on-demand only and hooked into the bone cache!!
|
|
FormatViewModelAttachment( i, world );
|
|
PutAttachment( i + 1, world );
|
|
}
|
|
}
|
|
|
|
bool C_BaseAnimating::CalcAttachments()
|
|
{
|
|
VPROF( "C_BaseAnimating::CalcAttachments" );
|
|
|
|
|
|
// Make sure m_CachedBones is valid.
|
|
return SetupBones( NULL, -1, BONE_USED_BY_ATTACHMENT, gpGlobals->curtime );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns the world location and world angles of an attachment
|
|
// Input : attachment name
|
|
// Output : location and angles
|
|
//-----------------------------------------------------------------------------
|
|
bool C_BaseAnimating::GetAttachment( const char *szName, Vector &absOrigin, QAngle &absAngles )
|
|
{
|
|
return GetAttachment( LookupAttachment( szName ), absOrigin, absAngles );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Get attachment point by index
|
|
// Input : number - which point
|
|
// Output : float * - the attachment point
|
|
//-----------------------------------------------------------------------------
|
|
bool C_BaseAnimating::GetAttachment( int number, Vector &origin, QAngle &angles )
|
|
{
|
|
// Note: this could be more efficient, but we want the matrix3x4_t version of GetAttachment to be the origin of
|
|
// attachment generation, so a derived class that wants to fudge attachments only
|
|
// has to reimplement that version. This also makes it work like the server in that regard.
|
|
if ( number < 1 || number > m_Attachments.Count() || !CalcAttachments() )
|
|
{
|
|
// Set this to the model origin/angles so that we don't have stack fungus in origin and angles.
|
|
origin = GetAbsOrigin();
|
|
angles = GetAbsAngles();
|
|
return false;
|
|
}
|
|
|
|
CAttachmentData *pData = &m_Attachments[number-1];
|
|
if ( !pData->m_bAnglesComputed )
|
|
{
|
|
MatrixAngles( pData->m_AttachmentToWorld, pData->m_angRotation );
|
|
pData->m_bAnglesComputed = true;
|
|
}
|
|
angles = pData->m_angRotation;
|
|
MatrixPosition( pData->m_AttachmentToWorld, origin );
|
|
return true;
|
|
}
|
|
|
|
bool C_BaseAnimating::GetAttachment( int number, matrix3x4_t& matrix )
|
|
{
|
|
if ( number < 1 || number > m_Attachments.Count() )
|
|
return false;
|
|
|
|
if ( !CalcAttachments() )
|
|
return false;
|
|
|
|
matrix = m_Attachments[number-1].m_AttachmentToWorld;
|
|
return true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Get attachment point by index (position only)
|
|
// Input : number - which point
|
|
//-----------------------------------------------------------------------------
|
|
bool C_BaseAnimating::GetAttachment( int number, Vector &origin )
|
|
{
|
|
// Note: this could be more efficient, but we want the matrix3x4_t version of GetAttachment to be the origin of
|
|
// attachment generation, so a derived class that wants to fudge attachments only
|
|
// has to reimplement that version. This also makes it work like the server in that regard.
|
|
matrix3x4_t attachmentToWorld;
|
|
if ( !GetAttachment( number, attachmentToWorld ) )
|
|
{
|
|
// Set this to the model origin/angles so that we don't have stack fungus in origin and angles.
|
|
origin = GetAbsOrigin();
|
|
return false;
|
|
}
|
|
|
|
MatrixPosition( attachmentToWorld, origin );
|
|
return true;
|
|
}
|
|
|
|
|
|
bool C_BaseAnimating::GetAttachment( const char *szName, Vector &absOrigin )
|
|
{
|
|
return GetAttachment( LookupAttachment( szName ), absOrigin );
|
|
}
|
|
|
|
|
|
|
|
bool C_BaseAnimating::GetAttachmentVelocity( int number, Vector &originVel, Quaternion &angleVel )
|
|
{
|
|
if ( number < 1 || number > m_Attachments.Count() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( !CalcAttachments() )
|
|
return false;
|
|
|
|
originVel = m_Attachments[number-1].m_vOriginVelocity;
|
|
angleVel.Init();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Returns the attachment in local space
|
|
//-----------------------------------------------------------------------------
|
|
bool C_BaseAnimating::GetAttachmentLocal( int iAttachment, matrix3x4_t &attachmentToLocal )
|
|
{
|
|
matrix3x4_t attachmentToWorld;
|
|
if (!GetAttachment(iAttachment, attachmentToWorld))
|
|
return false;
|
|
|
|
matrix3x4_t worldToEntity;
|
|
MatrixInvert( EntityToWorldTransform(), worldToEntity );
|
|
ConcatTransforms( worldToEntity, attachmentToWorld, attachmentToLocal );
|
|
return true;
|
|
}
|
|
|
|
bool C_BaseAnimating::GetAttachmentLocal( int iAttachment, Vector &origin, QAngle &angles )
|
|
{
|
|
matrix3x4_t attachmentToEntity;
|
|
|
|
if ( GetAttachmentLocal( iAttachment, attachmentToEntity ) )
|
|
{
|
|
origin.Init( attachmentToEntity[0][3], attachmentToEntity[1][3], attachmentToEntity[2][3] );
|
|
MatrixAngles( attachmentToEntity, angles );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool C_BaseAnimating::GetAttachmentLocal( int iAttachment, Vector &origin )
|
|
{
|
|
matrix3x4_t attachmentToEntity;
|
|
|
|
if ( GetAttachmentLocal( iAttachment, attachmentToEntity ) )
|
|
{
|
|
MatrixPosition( attachmentToEntity, origin );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool C_BaseAnimating::GetRootBone( matrix3x4_t &rootBone )
|
|
{
|
|
Assert( !IsDynamicModelLoading() );
|
|
|
|
if ( IsEffectActive( EF_BONEMERGE ) && GetMoveParent() && m_pBoneMergeCache )
|
|
return m_pBoneMergeCache->GetRootBone( rootBone );
|
|
|
|
GetBoneTransform( 0, rootBone );
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Move sound location to center of body
|
|
//-----------------------------------------------------------------------------
|
|
bool C_BaseAnimating::GetSoundSpatialization( SpatializationInfo_t& info )
|
|
{
|
|
{
|
|
C_BaseAnimating::AutoAllowBoneAccess boneaccess( true, false );
|
|
if ( !BaseClass::GetSoundSpatialization( info ) )
|
|
return false;
|
|
}
|
|
|
|
// move sound origin to center if npc has IK
|
|
if ( info.pOrigin && IsNPC() && m_pIk)
|
|
{
|
|
*info.pOrigin = GetAbsOrigin();
|
|
|
|
Vector mins, maxs, center;
|
|
|
|
modelinfo->GetModelBounds( GetModel(), mins, maxs );
|
|
VectorAdd( mins, maxs, center );
|
|
VectorScale( center, 0.5f, center );
|
|
|
|
(*info.pOrigin) += center;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
bool C_BaseAnimating::IsViewModel() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool C_BaseAnimating::IsMenuModel() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// UNDONE: Seems kind of silly to have this when we also have the cached bones in C_BaseAnimating
|
|
CBoneCache *C_BaseAnimating::GetBoneCache( CStudioHdr *pStudioHdr )
|
|
{
|
|
int boneMask = BONE_USED_BY_HITBOX;
|
|
CBoneCache *pcache = Studio_GetBoneCache( m_hitboxBoneCacheHandle );
|
|
if ( pcache )
|
|
{
|
|
if ( pcache->IsValid( gpGlobals->curtime, 0.0 ) )
|
|
{
|
|
// in memory and still valid, use it!
|
|
return pcache;
|
|
}
|
|
// in memory, but not the same bone set, destroy & rebuild
|
|
if ( (pcache->m_boneMask & boneMask) != boneMask )
|
|
{
|
|
Studio_DestroyBoneCache( m_hitboxBoneCacheHandle );
|
|
m_hitboxBoneCacheHandle = 0;
|
|
pcache = NULL;
|
|
}
|
|
}
|
|
|
|
if ( !pStudioHdr )
|
|
pStudioHdr = GetModelPtr( );
|
|
Assert(pStudioHdr);
|
|
|
|
C_BaseAnimating::PushAllowBoneAccess( true, false, "GetBoneCache" );
|
|
SetupBones( NULL, -1, boneMask, gpGlobals->curtime );
|
|
C_BaseAnimating::PopBoneAccess( "GetBoneCache" );
|
|
|
|
if ( pcache )
|
|
{
|
|
// still in memory but out of date, refresh the bones.
|
|
pcache->UpdateBones( m_CachedBoneData.Base(), pStudioHdr->numbones(), gpGlobals->curtime );
|
|
}
|
|
else
|
|
{
|
|
bonecacheparams_t params;
|
|
params.pStudioHdr = pStudioHdr;
|
|
// HACKHACK: We need the pointer to all bones here
|
|
params.pBoneToWorld = m_CachedBoneData.Base();
|
|
params.curtime = gpGlobals->curtime;
|
|
params.boneMask = boneMask;
|
|
|
|
m_hitboxBoneCacheHandle = Studio_CreateBoneCache( params );
|
|
pcache = Studio_GetBoneCache( m_hitboxBoneCacheHandle );
|
|
}
|
|
Assert(pcache);
|
|
return pcache;
|
|
}
|
|
|
|
|
|
class CTraceFilterSkipNPCsAndPlayers : public CTraceFilterSimple
|
|
{
|
|
public:
|
|
CTraceFilterSkipNPCsAndPlayers( const IHandleEntity *passentity, int collisionGroup )
|
|
: CTraceFilterSimple( passentity, collisionGroup )
|
|
{
|
|
}
|
|
|
|
virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
|
|
{
|
|
if ( CTraceFilterSimple::ShouldHitEntity(pServerEntity, contentsMask) )
|
|
{
|
|
C_BaseEntity *pEntity = EntityFromEntityHandle( pServerEntity );
|
|
if ( !pEntity )
|
|
return true;
|
|
|
|
if ( pEntity->IsNPC() || pEntity->IsPlayer() )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
|
|
/*
|
|
void drawLine(const Vector& origin, const Vector& dest, int r, int g, int b, bool noDepthTest, float duration)
|
|
{
|
|
debugoverlay->AddLineOverlay( origin, dest, r, g, b, noDepthTest, duration );
|
|
}
|
|
*/
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: update latched IK contacts if they're in a moving reference frame.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void C_BaseAnimating::UpdateIKLocks( float currentTime )
|
|
{
|
|
if (!m_pIk)
|
|
return;
|
|
|
|
int targetCount = m_pIk->m_target.Count();
|
|
if ( targetCount == 0 )
|
|
return;
|
|
|
|
for (int i = 0; i < targetCount; i++)
|
|
{
|
|
CIKTarget *pTarget = &m_pIk->m_target[i];
|
|
|
|
if (!pTarget->IsActive())
|
|
continue;
|
|
|
|
if (pTarget->GetOwner() != -1)
|
|
{
|
|
C_BaseEntity *pOwner = cl_entitylist->GetEnt( pTarget->GetOwner() );
|
|
if (pOwner != NULL)
|
|
{
|
|
pTarget->UpdateOwner( pOwner->entindex(), pOwner->GetAbsOrigin(), pOwner->GetAbsAngles() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Find the ground or external attachment points needed by IK rules
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void C_BaseAnimating::CalculateIKLocks( float currentTime )
|
|
{
|
|
if (!m_pIk)
|
|
return;
|
|
|
|
int targetCount = m_pIk->m_target.Count();
|
|
if ( targetCount == 0 )
|
|
return;
|
|
|
|
// In TF, we might be attaching a player's view to a walking model that's using IK. If we are, it can
|
|
// get in here during the view setup code, and it's not normally supposed to be able to access the spatial
|
|
// partition that early in the rendering loop. So we allow access right here for that special case.
|
|
SpatialPartitionListMask_t curSuppressed = partition->GetSuppressedLists();
|
|
partition->SuppressLists( PARTITION_ALL_CLIENT_EDICTS, false );
|
|
CBaseEntity::PushEnableAbsRecomputations( false );
|
|
|
|
Ray_t ray;
|
|
CTraceFilterSkipNPCsAndPlayers traceFilter( this, GetCollisionGroup() );
|
|
|
|
// FIXME: trace based on gravity or trace based on angles?
|
|
Vector up;
|
|
AngleVectors( GetRenderAngles(), NULL, NULL, &up );
|
|
|
|
// FIXME: check number of slots?
|
|
float minHeight = FLT_MAX;
|
|
float maxHeight = -FLT_MAX;
|
|
|
|
for (int i = 0; i < targetCount; i++)
|
|
{
|
|
trace_t trace;
|
|
CIKTarget *pTarget = &m_pIk->m_target[i];
|
|
|
|
if (!pTarget->IsActive())
|
|
continue;
|
|
|
|
switch( pTarget->type)
|
|
{
|
|
case IK_GROUND:
|
|
{
|
|
Vector estGround;
|
|
Vector p1, p2;
|
|
|
|
// adjust ground to original ground position
|
|
estGround = (pTarget->est.pos - GetRenderOrigin());
|
|
estGround = estGround - (estGround * up) * up;
|
|
estGround = GetAbsOrigin() + estGround + pTarget->est.floor * up;
|
|
|
|
VectorMA( estGround, pTarget->est.height, up, p1 );
|
|
VectorMA( estGround, -pTarget->est.height, up, p2 );
|
|
|
|
float r = MAX( pTarget->est.radius, 1);
|
|
|
|
// don't IK to other characters
|
|
ray.Init( p1, p2, Vector(-r,-r,0), Vector(r,r,r*2) );
|
|
enginetrace->TraceRay( ray, PhysicsSolidMaskForEntity(), &traceFilter, &trace );
|
|
|
|
if ( trace.m_pEnt != NULL && trace.m_pEnt->GetMoveType() == MOVETYPE_PUSH )
|
|
{
|
|
pTarget->SetOwner( trace.m_pEnt->entindex(), trace.m_pEnt->GetAbsOrigin(), trace.m_pEnt->GetAbsAngles() );
|
|
}
|
|
else
|
|
{
|
|
pTarget->ClearOwner( );
|
|
}
|
|
|
|
if (trace.startsolid)
|
|
{
|
|
// trace from back towards hip
|
|
Vector tmp = estGround - pTarget->trace.closest;
|
|
tmp.NormalizeInPlace();
|
|
ray.Init( estGround - tmp * pTarget->est.height, estGround, Vector(-r,-r,0), Vector(r,r,1) );
|
|
|
|
// debugoverlay->AddLineOverlay( ray.m_Start, ray.m_Start + ray.m_Delta, 255, 0, 0, 0, 0 );
|
|
|
|
enginetrace->TraceRay( ray, MASK_SOLID, &traceFilter, &trace );
|
|
|
|
if (!trace.startsolid)
|
|
{
|
|
p1 = trace.endpos;
|
|
VectorMA( p1, - pTarget->est.height, up, p2 );
|
|
ray.Init( p1, p2, Vector(-r,-r,0), Vector(r,r,1) );
|
|
|
|
enginetrace->TraceRay( ray, MASK_SOLID, &traceFilter, &trace );
|
|
}
|
|
|
|
// debugoverlay->AddLineOverlay( ray.m_Start, ray.m_Start + ray.m_Delta, 0, 255, 0, 0, 0 );
|
|
}
|
|
|
|
|
|
if (!trace.startsolid)
|
|
{
|
|
if (trace.DidHitWorld())
|
|
{
|
|
// clamp normal to 33 degrees
|
|
const float limit = 0.832;
|
|
float dot = DotProduct(trace.plane.normal, up);
|
|
if (dot < limit)
|
|
{
|
|
Assert( dot >= 0 );
|
|
// subtract out up component
|
|
Vector diff = trace.plane.normal - up * dot;
|
|
// scale remainder such that it and the up vector are a unit vector
|
|
float d = sqrt( (1 - limit * limit) / DotProduct( diff, diff ) );
|
|
trace.plane.normal = up * limit + d * diff;
|
|
}
|
|
// FIXME: this is wrong with respect to contact position and actual ankle offset
|
|
pTarget->SetPosWithNormalOffset( trace.endpos, trace.plane.normal );
|
|
pTarget->SetNormal( trace.plane.normal );
|
|
pTarget->SetOnWorld( true );
|
|
|
|
// only do this on forward tracking or commited IK ground rules
|
|
if (pTarget->est.release < 0.1)
|
|
{
|
|
// keep track of ground height
|
|
float offset = DotProduct( pTarget->est.pos, up );
|
|
if (minHeight > offset )
|
|
minHeight = offset;
|
|
|
|
if (maxHeight < offset )
|
|
maxHeight = offset;
|
|
}
|
|
// FIXME: if we don't drop legs, running down hills looks horrible
|
|
/*
|
|
if (DotProduct( pTarget->est.pos, up ) < DotProduct( estGround, up ))
|
|
{
|
|
pTarget->est.pos = estGround;
|
|
}
|
|
*/
|
|
}
|
|
else if (trace.DidHitNonWorldEntity())
|
|
{
|
|
pTarget->SetPos( trace.endpos );
|
|
pTarget->SetAngles( GetRenderAngles() );
|
|
|
|
// only do this on forward tracking or commited IK ground rules
|
|
if (pTarget->est.release < 0.1)
|
|
{
|
|
float offset = DotProduct( pTarget->est.pos, up );
|
|
if (minHeight > offset )
|
|
minHeight = offset;
|
|
|
|
if (maxHeight < offset )
|
|
maxHeight = offset;
|
|
}
|
|
// FIXME: if we don't drop legs, running down hills looks horrible
|
|
/*
|
|
if (DotProduct( pTarget->est.pos, up ) < DotProduct( estGround, up ))
|
|
{
|
|
pTarget->est.pos = estGround;
|
|
}
|
|
*/
|
|
}
|
|
else
|
|
{
|
|
pTarget->IKFailed( );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!trace.DidHitWorld())
|
|
{
|
|
pTarget->IKFailed( );
|
|
}
|
|
else
|
|
{
|
|
pTarget->SetPos( trace.endpos );
|
|
pTarget->SetAngles( GetRenderAngles() );
|
|
pTarget->SetOnWorld( true );
|
|
}
|
|
}
|
|
|
|
/*
|
|
debugoverlay->AddTextOverlay( p1, i, 0, "%d %.1f %.1f %.1f ", i,
|
|
pTarget->latched.deltaPos.x, pTarget->latched.deltaPos.y, pTarget->latched.deltaPos.z );
|
|
debugoverlay->AddBoxOverlay( pTarget->est.pos, Vector( -r, -r, -1 ), Vector( r, r, 1), QAngle( 0, 0, 0 ), 255, 0, 0, 0, 0 );
|
|
*/
|
|
// debugoverlay->AddBoxOverlay( pTarget->latched.pos, Vector( -2, -2, 2 ), Vector( 2, 2, 6), QAngle( 0, 0, 0 ), 0, 255, 0, 0, 0 );
|
|
}
|
|
break;
|
|
|
|
case IK_ATTACHMENT:
|
|
{
|
|
C_BaseEntity *pEntity = NULL;
|
|
float flDist = pTarget->est.radius;
|
|
|
|
// FIXME: make entity finding sticky!
|
|
// FIXME: what should the radius check be?
|
|
for ( CEntitySphereQuery sphere( pTarget->est.pos, 64 ); ( pEntity = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() )
|
|
{
|
|
C_BaseAnimating *pAnim = pEntity->GetBaseAnimating( );
|
|
if (!pAnim)
|
|
continue;
|
|
|
|
int iAttachment = pAnim->LookupAttachment( pTarget->offset.pAttachmentName );
|
|
if (iAttachment <= 0)
|
|
continue;
|
|
|
|
Vector origin;
|
|
QAngle angles;
|
|
pAnim->GetAttachment( iAttachment, origin, angles );
|
|
|
|
// debugoverlay->AddBoxOverlay( origin, Vector( -1, -1, -1 ), Vector( 1, 1, 1 ), QAngle( 0, 0, 0 ), 255, 0, 0, 0, 0 );
|
|
|
|
float d = (pTarget->est.pos - origin).Length();
|
|
|
|
if ( d >= flDist)
|
|
continue;
|
|
|
|
flDist = d;
|
|
pTarget->SetPos( origin );
|
|
pTarget->SetAngles( angles );
|
|
// debugoverlay->AddBoxOverlay( pTarget->est.pos, Vector( -pTarget->est.radius, -pTarget->est.radius, -pTarget->est.radius ), Vector( pTarget->est.radius, pTarget->est.radius, pTarget->est.radius), QAngle( 0, 0, 0 ), 0, 255, 0, 0, 0 );
|
|
}
|
|
|
|
if (flDist >= pTarget->est.radius)
|
|
{
|
|
// debugoverlay->AddBoxOverlay( pTarget->est.pos, Vector( -pTarget->est.radius, -pTarget->est.radius, -pTarget->est.radius ), Vector( pTarget->est.radius, pTarget->est.radius, pTarget->est.radius), QAngle( 0, 0, 0 ), 0, 0, 255, 0, 0 );
|
|
// no solution, disable ik rule
|
|
pTarget->IKFailed( );
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if defined( HL2_CLIENT_DLL )
|
|
if (minHeight < FLT_MAX)
|
|
{
|
|
input->AddIKGroundContactInfo( entindex(), minHeight, maxHeight );
|
|
}
|
|
#endif
|
|
|
|
CBaseEntity::PopEnableAbsRecomputations();
|
|
partition->SuppressLists( curSuppressed, true );
|
|
}
|
|
|
|
bool C_BaseAnimating::GetPoseParameterRange( int index, float &minValue, float &maxValue )
|
|
{
|
|
CStudioHdr *pStudioHdr = GetModelPtr();
|
|
|
|
if (pStudioHdr)
|
|
{
|
|
if (index >= 0 && index < pStudioHdr->GetNumPoseParameters())
|
|
{
|
|
const mstudioposeparamdesc_t &pose = pStudioHdr->pPoseParameter( index );
|
|
minValue = pose.start;
|
|
maxValue = pose.end;
|
|
return true;
|
|
}
|
|
}
|
|
minValue = 0.0f;
|
|
maxValue = 1.0f;
|
|
return false;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Do HL1 style lipsynch
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::ControlMouth( CStudioHdr *pstudiohdr )
|
|
{
|
|
if ( !MouthInfo().NeedsEnvelope() )
|
|
return;
|
|
|
|
if ( !pstudiohdr )
|
|
return;
|
|
|
|
int index = LookupPoseParameter( pstudiohdr, LIPSYNC_POSEPARAM_NAME );
|
|
|
|
if ( index != -1 )
|
|
{
|
|
float value = GetMouth()->mouthopen / 64.0;
|
|
|
|
float raw = value;
|
|
|
|
if ( value > 1.0 )
|
|
value = 1.0;
|
|
|
|
float start, end;
|
|
GetPoseParameterRange( index, start, end );
|
|
|
|
value = (1.0 - value) * start + value * end;
|
|
|
|
//Adrian - Set the pose parameter value.
|
|
//It has to be called "mouth".
|
|
SetPoseParameter( pstudiohdr, index, value );
|
|
}
|
|
}
|
|
|
|
CMouthInfo *C_BaseAnimating::GetMouth( void )
|
|
{
|
|
return &m_mouth;
|
|
}
|
|
|
|
#ifdef DEBUG_BONE_SETUP_THREADING
|
|
ConVar cl_warn_thread_contested_bone_setup("cl_warn_thread_contested_bone_setup", "0" );
|
|
#endif
|
|
ConVar cl_threaded_bone_setup("cl_threaded_bone_setup", "0", 0, "Enable parallel processing of C_BaseAnimating::SetupBones()" );
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Do the default sequence blending rules as done in HL1
|
|
//-----------------------------------------------------------------------------
|
|
|
|
static void SetupBonesOnBaseAnimating( C_BaseAnimating *&pBaseAnimating )
|
|
{
|
|
if ( !pBaseAnimating->GetMoveParent() )
|
|
pBaseAnimating->SetupBones( NULL, -1, -1, gpGlobals->curtime );
|
|
}
|
|
|
|
static void PreThreadedBoneSetup()
|
|
{
|
|
mdlcache->BeginLock();
|
|
}
|
|
|
|
static void PostThreadedBoneSetup()
|
|
{
|
|
mdlcache->EndLock();
|
|
}
|
|
|
|
static bool g_bInThreadedBoneSetup;
|
|
static bool g_bDoThreadedBoneSetup;
|
|
|
|
void C_BaseAnimating::InitBoneSetupThreadPool()
|
|
{
|
|
}
|
|
|
|
void C_BaseAnimating::ShutdownBoneSetupThreadPool()
|
|
{
|
|
}
|
|
|
|
void C_BaseAnimating::ThreadedBoneSetup()
|
|
{
|
|
g_bDoThreadedBoneSetup = cl_threaded_bone_setup.GetBool();
|
|
if ( g_bDoThreadedBoneSetup )
|
|
{
|
|
int nCount = g_PreviousBoneSetups.Count();
|
|
if ( nCount > 1 )
|
|
{
|
|
g_bInThreadedBoneSetup = true;
|
|
|
|
ParallelProcess( "C_BaseAnimating::ThreadedBoneSetup", g_PreviousBoneSetups.Base(), nCount, &SetupBonesOnBaseAnimating, &PreThreadedBoneSetup, &PostThreadedBoneSetup );
|
|
|
|
g_bInThreadedBoneSetup = false;
|
|
}
|
|
}
|
|
g_iPreviousBoneCounter++;
|
|
g_PreviousBoneSetups.RemoveAll();
|
|
}
|
|
|
|
void C_BaseAnimating::BuildMatricesWithBoneMerge(
|
|
const CStudioHdr *pStudioHdr,
|
|
const QAngle& angles,
|
|
const Vector& origin,
|
|
const Vector pos[MAXSTUDIOBONES],
|
|
const Quaternion q[MAXSTUDIOBONES],
|
|
matrix3x4_t bonetoworld[MAXSTUDIOBONES],
|
|
C_BaseAnimating *pParent,
|
|
CBoneCache *pParentCache
|
|
)
|
|
{
|
|
CStudioHdr *fhdr = pParent->GetModelPtr();
|
|
mstudiobone_t *pbones = pStudioHdr->pBone( 0 );
|
|
|
|
matrix3x4_t rotationmatrix; // model to world transformation
|
|
AngleMatrix( angles, origin, rotationmatrix);
|
|
|
|
for ( int i=0; i < pStudioHdr->numbones(); i++ )
|
|
{
|
|
// Now find the bone in the parent entity.
|
|
bool merged = false;
|
|
int parentBoneIndex = Studio_BoneIndexByName( fhdr, pbones[i].pszName() );
|
|
if ( parentBoneIndex >= 0 )
|
|
{
|
|
matrix3x4_t *pMat = pParentCache->GetCachedBone( parentBoneIndex );
|
|
if ( pMat )
|
|
{
|
|
MatrixCopy( *pMat, bonetoworld[ i ] );
|
|
merged = true;
|
|
}
|
|
}
|
|
|
|
if ( !merged )
|
|
{
|
|
// If we get down here, then the bone wasn't merged.
|
|
matrix3x4_t bonematrix;
|
|
QuaternionMatrix( q[i], pos[i], bonematrix );
|
|
|
|
if (pbones[i].parent == -1)
|
|
{
|
|
ConcatTransforms (rotationmatrix, bonematrix, bonetoworld[i]);
|
|
}
|
|
else
|
|
{
|
|
ConcatTransforms (bonetoworld[pbones[i].parent], bonematrix, bonetoworld[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void C_BaseAnimating::GetSkeleton( CStudioHdr *pStudioHdr, Vector pos[], Quaternion q[], int boneMask, float currentTime )
|
|
{
|
|
if(!pStudioHdr)
|
|
{
|
|
Assert(!"C_BaseAnimating::GetSkeleton() without a model");
|
|
return;
|
|
}
|
|
|
|
IBoneSetup boneSetup( pStudioHdr, boneMask, m_flPoseParameter );
|
|
boneSetup.InitPose( pos, q );
|
|
|
|
boneSetup.AccumulatePose( pos, q, GetSequence(), GetCycle(), 1.0, currentTime, m_pIk );
|
|
|
|
if ( m_pIk )
|
|
{
|
|
CIKContext auto_ik;
|
|
auto_ik.Init( pStudioHdr, GetRenderAngles(), GetRenderOrigin(), currentTime, 0, boneMask );
|
|
boneSetup.CalcAutoplaySequences( pos, q, currentTime, &auto_ik );
|
|
}
|
|
else
|
|
{
|
|
boneSetup.CalcAutoplaySequences( pos, q, currentTime, NULL );
|
|
}
|
|
boneSetup.CalcBoneAdj( pos, q, m_flEncodedController );
|
|
}
|
|
|
|
bool C_BaseAnimating::SetupBones( matrix3x4_t *pBoneToWorldOut, int nMaxBones, int boneMask, float currentTime )
|
|
{
|
|
VPROF_BUDGET( "C_BaseAnimating::SetupBones", VPROF_BUDGETGROUP_CLIENT_ANIMATION );
|
|
|
|
//=============================================================================
|
|
// HPE_BEGIN:
|
|
// [pfreese] Added the check for pBoneToWorldOut != NULL in this debug warning
|
|
// code. SetupBones is called in the CSS anytime an attachment wants its
|
|
// parent's transform, hence this warning is hit extremely frequently.
|
|
// I'm not actually sure if this is the right "fix" for this, as the bones are
|
|
// actually accessed as part of the setup process, but since I'm not clear on the
|
|
// purpose of this dev warning, I'm including this comment block.
|
|
//=============================================================================
|
|
|
|
if ( pBoneToWorldOut != NULL && !IsBoneAccessAllowed() )
|
|
{
|
|
static float lastWarning = 0.0f;
|
|
|
|
// Prevent spammage!!!
|
|
if ( gpGlobals->realtime >= lastWarning + 1.0f )
|
|
{
|
|
DevMsgRT( "*** ERROR: Bone access not allowed (entity %i:%s)\n", index, GetClassname() );
|
|
lastWarning = gpGlobals->realtime;
|
|
}
|
|
}
|
|
|
|
//boneMask = BONE_USED_BY_ANYTHING; // HACK HACK - this is a temp fix until we have accessors for bones to find out where problems are.
|
|
|
|
if ( GetSequence() == -1 )
|
|
return false;
|
|
|
|
if ( boneMask == -1 )
|
|
{
|
|
boneMask = m_iPrevBoneMask;
|
|
}
|
|
|
|
// We should get rid of this someday when we have solutions for the odd cases where a bone doesn't
|
|
// get setup and its transform is asked for later.
|
|
if ( cl_SetupAllBones.GetInt() )
|
|
{
|
|
boneMask |= BONE_USED_BY_ANYTHING;
|
|
}
|
|
|
|
// Set up all bones if recording, too
|
|
if ( IsToolRecording() )
|
|
{
|
|
boneMask |= BONE_USED_BY_ANYTHING;
|
|
}
|
|
|
|
if ( g_bInThreadedBoneSetup )
|
|
{
|
|
if ( !m_BoneSetupLock.TryLock() )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_BONE_SETUP_THREADING
|
|
if ( cl_warn_thread_contested_bone_setup.GetBool() )
|
|
{
|
|
if ( !m_BoneSetupLock.TryLock() )
|
|
{
|
|
Msg( "Contested bone setup in frame %d!\n", gpGlobals->framecount );
|
|
}
|
|
else
|
|
{
|
|
m_BoneSetupLock.Unlock();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
AUTO_LOCK( m_BoneSetupLock );
|
|
|
|
if ( g_bInThreadedBoneSetup )
|
|
{
|
|
m_BoneSetupLock.Unlock();
|
|
}
|
|
|
|
if ( m_iMostRecentModelBoneCounter != g_iModelBoneCounter )
|
|
{
|
|
// Clear out which bones we've touched this frame if this is
|
|
// the first time we've seen this object this frame.
|
|
if ( LastBoneChangedTime() >= m_flLastBoneSetupTime )
|
|
{
|
|
m_BoneAccessor.SetReadableBones( 0 );
|
|
m_BoneAccessor.SetWritableBones( 0 );
|
|
m_flLastBoneSetupTime = currentTime;
|
|
}
|
|
m_iPrevBoneMask = m_iAccumulatedBoneMask;
|
|
m_iAccumulatedBoneMask = 0;
|
|
|
|
#ifdef STUDIO_ENABLE_PERF_COUNTERS
|
|
CStudioHdr *hdr = GetModelPtr();
|
|
if (hdr)
|
|
{
|
|
hdr->ClearPerfCounters();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
int nBoneCount = m_CachedBoneData.Count();
|
|
if ( g_bDoThreadedBoneSetup && !g_bInThreadedBoneSetup && ( nBoneCount >= 16 ) && !GetMoveParent() && m_iMostRecentBoneSetupRequest != g_iPreviousBoneCounter )
|
|
{
|
|
m_iMostRecentBoneSetupRequest = g_iPreviousBoneCounter;
|
|
Assert( g_PreviousBoneSetups.Find( this ) == -1 );
|
|
g_PreviousBoneSetups.AddToTail( this );
|
|
}
|
|
|
|
// Keep track of everthing asked for over the entire frame
|
|
m_iAccumulatedBoneMask |= boneMask;
|
|
|
|
// Make sure that we know that we've already calculated some bone stuff this time around.
|
|
m_iMostRecentModelBoneCounter = g_iModelBoneCounter;
|
|
|
|
// Have we cached off all bones meeting the flag set?
|
|
if( ( m_BoneAccessor.GetReadableBones() & boneMask ) != boneMask )
|
|
{
|
|
MDLCACHE_CRITICAL_SECTION();
|
|
|
|
CStudioHdr *hdr = GetModelPtr();
|
|
if ( !hdr || !hdr->SequencesAvailable() )
|
|
return false;
|
|
|
|
// Setup our transform based on render angles and origin.
|
|
matrix3x4_t parentTransform;
|
|
AngleMatrix( GetRenderAngles(), GetRenderOrigin(), parentTransform );
|
|
|
|
// Load the boneMask with the total of what was asked for last frame.
|
|
boneMask |= m_iPrevBoneMask;
|
|
|
|
// Allow access to the bones we're setting up so we don't get asserts in here.
|
|
int oldReadableBones = m_BoneAccessor.GetReadableBones();
|
|
m_BoneAccessor.SetWritableBones( m_BoneAccessor.GetReadableBones() | boneMask );
|
|
m_BoneAccessor.SetReadableBones( m_BoneAccessor.GetWritableBones() );
|
|
|
|
if (hdr->flags() & STUDIOHDR_FLAGS_STATIC_PROP)
|
|
{
|
|
MatrixCopy( parentTransform, GetBoneForWrite( 0 ) );
|
|
}
|
|
else
|
|
{
|
|
TrackBoneSetupEnt( this );
|
|
|
|
// This is necessary because it's possible that CalculateIKLocks will trigger our move children
|
|
// to call GetAbsOrigin(), and they'll use our OLD bone transforms to get their attachments
|
|
// since we're right in the middle of setting up our new transforms.
|
|
//
|
|
// Setting this flag forces move children to keep their abs transform invalidated.
|
|
AddFlag( EFL_SETTING_UP_BONES );
|
|
|
|
// NOTE: For model scaling, we need to opt out of IK because it will mark the bones as already being calculated
|
|
if ( !IsModelScaled() )
|
|
{
|
|
// only allocate an ik block if the npc can use it
|
|
if ( !m_pIk && hdr->numikchains() > 0 && !(m_EntClientFlags & ENTCLIENTFLAG_DONTUSEIK) )
|
|
{
|
|
m_pIk = new CIKContext;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Reset the IK
|
|
if ( m_pIk )
|
|
{
|
|
delete m_pIk;
|
|
m_pIk = NULL;
|
|
}
|
|
}
|
|
|
|
Vector pos[MAXSTUDIOBONES];
|
|
Quaternion q[MAXSTUDIOBONES];
|
|
#if defined(FP_EXCEPTIONS_ENABLED) || defined(DBGFLAG_ASSERT)
|
|
// Having these uninitialized means that some bugs are very hard
|
|
// to reproduce. A memset of 0xFF is a simple way of getting NaNs.
|
|
memset( pos, 0xFF, sizeof(pos) );
|
|
memset( q, 0xFF, sizeof(q) );
|
|
#endif
|
|
|
|
int bonesMaskNeedRecalc = boneMask | oldReadableBones; // Hack to always recalc bones, to fix the arm jitter in the new CS player anims until Ken makes the real fix
|
|
|
|
if ( m_pIk )
|
|
{
|
|
if (Teleported() || IsNoInterpolationFrame())
|
|
m_pIk->ClearTargets();
|
|
|
|
m_pIk->Init( hdr, GetRenderAngles(), GetRenderOrigin(), currentTime, gpGlobals->framecount, bonesMaskNeedRecalc );
|
|
}
|
|
|
|
// Let pose debugger know that we are blending
|
|
g_pPoseDebugger->StartBlending( this, hdr );
|
|
|
|
StandardBlendingRules( hdr, pos, q, currentTime, bonesMaskNeedRecalc );
|
|
|
|
CBoneBitList boneComputed;
|
|
// don't calculate IK on ragdolls
|
|
if ( m_pIk && !IsRagdoll() )
|
|
{
|
|
UpdateIKLocks( currentTime );
|
|
|
|
m_pIk->UpdateTargets( pos, q, m_BoneAccessor.GetBoneArrayForWrite(), boneComputed );
|
|
|
|
CalculateIKLocks( currentTime );
|
|
m_pIk->SolveDependencies( pos, q, m_BoneAccessor.GetBoneArrayForWrite(), boneComputed );
|
|
}
|
|
|
|
BuildTransformations( hdr, pos, q, parentTransform, bonesMaskNeedRecalc, boneComputed, currentTime );
|
|
|
|
RemoveFlag( EFL_SETTING_UP_BONES );
|
|
ControlMouth( hdr );
|
|
}
|
|
|
|
if( !( oldReadableBones & BONE_USED_BY_ATTACHMENT ) && ( boneMask & BONE_USED_BY_ATTACHMENT ) )
|
|
{
|
|
SetupBones_AttachmentHelper( hdr );
|
|
}
|
|
}
|
|
|
|
// Do they want to get at the bone transforms? If it's just making sure an aiment has
|
|
// its bones setup, it doesn't need the transforms yet.
|
|
if ( pBoneToWorldOut )
|
|
{
|
|
if ( nMaxBones >= m_CachedBoneData.Count() )
|
|
{
|
|
memcpy( pBoneToWorldOut, m_CachedBoneData.Base(), sizeof( matrix3x4_t ) * m_CachedBoneData.Count() );
|
|
}
|
|
else
|
|
{
|
|
Warning( "SetupBones: invalid bone array size (%d - needs %d)\n", nMaxBones, m_CachedBoneData.Count() );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
C_BaseAnimating* C_BaseAnimating::FindFollowedEntity()
|
|
{
|
|
C_BaseEntity *follow = GetFollowedEntity();
|
|
|
|
if ( !follow )
|
|
return NULL;
|
|
|
|
if ( follow->IsDormant() )
|
|
return NULL;
|
|
|
|
if ( !follow->GetModel() )
|
|
{
|
|
Warning( "mod_studio: MOVETYPE_FOLLOW with no model.\n" );
|
|
return NULL;
|
|
}
|
|
|
|
if ( modelinfo->GetModelType( follow->GetModel() ) != mod_studio )
|
|
{
|
|
Warning( "Attached %s (mod_studio) to %s (%d)\n",
|
|
modelinfo->GetModelName( GetModel() ),
|
|
modelinfo->GetModelName( follow->GetModel() ),
|
|
modelinfo->GetModelType( follow->GetModel() ) );
|
|
return NULL;
|
|
}
|
|
|
|
return assert_cast< C_BaseAnimating* >( follow );
|
|
}
|
|
|
|
|
|
|
|
void C_BaseAnimating::InvalidateBoneCache()
|
|
{
|
|
m_iMostRecentModelBoneCounter = g_iModelBoneCounter - 1;
|
|
m_flLastBoneSetupTime = -FLT_MAX;
|
|
}
|
|
|
|
|
|
bool C_BaseAnimating::IsBoneCacheValid() const
|
|
{
|
|
return m_iMostRecentModelBoneCounter == g_iModelBoneCounter;
|
|
}
|
|
|
|
|
|
// Causes an assert to happen if bones or attachments are used while this is false.
|
|
struct BoneAccess
|
|
{
|
|
BoneAccess()
|
|
{
|
|
bAllowBoneAccessForNormalModels = false;
|
|
bAllowBoneAccessForViewModels = false;
|
|
tag = NULL;
|
|
}
|
|
|
|
bool bAllowBoneAccessForNormalModels;
|
|
bool bAllowBoneAccessForViewModels;
|
|
char const *tag;
|
|
};
|
|
|
|
static CUtlVector< BoneAccess > g_BoneAccessStack;
|
|
static BoneAccess g_BoneAcessBase;
|
|
|
|
bool C_BaseAnimating::IsBoneAccessAllowed() const
|
|
{
|
|
if ( IsViewModel() )
|
|
return g_BoneAcessBase.bAllowBoneAccessForViewModels;
|
|
else
|
|
return g_BoneAcessBase.bAllowBoneAccessForNormalModels;
|
|
}
|
|
|
|
// (static function)
|
|
void C_BaseAnimating::PushAllowBoneAccess( bool bAllowForNormalModels, bool bAllowForViewModels, char const *tagPush )
|
|
{
|
|
BoneAccess save = g_BoneAcessBase;
|
|
g_BoneAccessStack.AddToTail( save );
|
|
|
|
Assert( g_BoneAccessStack.Count() < 32 ); // Most likely we are leaking "PushAllowBoneAccess" calls if PopBoneAccess is never called. Consider using AutoAllowBoneAccess.
|
|
g_BoneAcessBase.bAllowBoneAccessForNormalModels = bAllowForNormalModels;
|
|
g_BoneAcessBase.bAllowBoneAccessForViewModels = bAllowForViewModels;
|
|
g_BoneAcessBase.tag = tagPush;
|
|
}
|
|
|
|
void C_BaseAnimating::PopBoneAccess( char const *tagPop )
|
|
{
|
|
// Validate that pop matches the push
|
|
Assert( ( g_BoneAcessBase.tag == tagPop ) || ( g_BoneAcessBase.tag && g_BoneAcessBase.tag != ( char const * ) 1 && tagPop && tagPop != ( char const * ) 1 && !strcmp( g_BoneAcessBase.tag, tagPop ) ) );
|
|
int lastIndex = g_BoneAccessStack.Count() - 1;
|
|
if ( lastIndex < 0 )
|
|
{
|
|
Assert( !"C_BaseAnimating::PopBoneAccess: Stack is empty!!!" );
|
|
return;
|
|
}
|
|
g_BoneAcessBase = g_BoneAccessStack[lastIndex ];
|
|
g_BoneAccessStack.Remove( lastIndex );
|
|
}
|
|
|
|
C_BaseAnimating::AutoAllowBoneAccess::AutoAllowBoneAccess( bool bAllowForNormalModels, bool bAllowForViewModels )
|
|
{
|
|
C_BaseAnimating::PushAllowBoneAccess( bAllowForNormalModels, bAllowForViewModels, ( char const * ) 1 );
|
|
}
|
|
|
|
C_BaseAnimating::AutoAllowBoneAccess::~AutoAllowBoneAccess( )
|
|
{
|
|
C_BaseAnimating::PopBoneAccess( ( char const * ) 1 );
|
|
}
|
|
|
|
// (static function)
|
|
void C_BaseAnimating::InvalidateBoneCaches()
|
|
{
|
|
g_iModelBoneCounter++;
|
|
}
|
|
|
|
bool C_BaseAnimating::ShouldDraw()
|
|
{
|
|
return !IsDynamicModelLoading() && BaseClass::ShouldDraw();
|
|
}
|
|
|
|
ConVar r_drawothermodels( "r_drawothermodels", "1", FCVAR_CHEAT, "0=Off, 1=Normal, 2=Wireframe" );
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Draws the object
|
|
// Input : flags -
|
|
//-----------------------------------------------------------------------------
|
|
int C_BaseAnimating::DrawModel( int flags )
|
|
{
|
|
VPROF_BUDGET( "C_BaseAnimating::DrawModel", VPROF_BUDGETGROUP_MODEL_RENDERING );
|
|
if ( !m_bReadyToDraw )
|
|
return 0;
|
|
|
|
int drawn = 0;
|
|
|
|
#ifdef TF_CLIENT_DLL
|
|
ValidateModelIndex();
|
|
#endif
|
|
|
|
if ( r_drawothermodels.GetInt() )
|
|
{
|
|
MDLCACHE_CRITICAL_SECTION();
|
|
|
|
int extraFlags = 0;
|
|
if ( r_drawothermodels.GetInt() == 2 )
|
|
{
|
|
extraFlags |= STUDIO_WIREFRAME;
|
|
}
|
|
|
|
if ( flags & STUDIO_SHADOWDEPTHTEXTURE )
|
|
{
|
|
extraFlags |= STUDIO_SHADOWDEPTHTEXTURE;
|
|
}
|
|
|
|
if ( flags & STUDIO_SSAODEPTHTEXTURE )
|
|
{
|
|
extraFlags |= STUDIO_SSAODEPTHTEXTURE;
|
|
}
|
|
|
|
if ( ( flags & ( STUDIO_SSAODEPTHTEXTURE | STUDIO_SHADOWDEPTHTEXTURE ) ) == 0 &&
|
|
g_pStudioStatsEntity != NULL && g_pStudioStatsEntity == GetClientRenderable() )
|
|
{
|
|
extraFlags |= STUDIO_GENERATE_STATS;
|
|
}
|
|
|
|
// Necessary for lighting blending
|
|
CreateModelInstance();
|
|
|
|
if ( !IsFollowingEntity() )
|
|
{
|
|
drawn = InternalDrawModel( flags|extraFlags );
|
|
}
|
|
else
|
|
{
|
|
// this doesn't draw unless master entity is visible and it's a studio model!!!
|
|
C_BaseAnimating *follow = FindFollowedEntity();
|
|
if ( follow )
|
|
{
|
|
// recompute master entity bone structure
|
|
int baseDrawn = follow->DrawModel( 0 );
|
|
|
|
// draw entity
|
|
// FIXME: Currently only draws if aiment is drawn.
|
|
// BUGBUG: Fixup bbox and do a separate cull for follow object
|
|
if ( baseDrawn )
|
|
{
|
|
drawn = InternalDrawModel( STUDIO_RENDER|extraFlags );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we're visualizing our bboxes, draw them
|
|
DrawBBoxVisualizations();
|
|
|
|
return drawn;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Gets the hitbox-to-world transforms, returns false if there was a problem
|
|
//-----------------------------------------------------------------------------
|
|
bool C_BaseAnimating::HitboxToWorldTransforms( matrix3x4_t *pHitboxToWorld[MAXSTUDIOBONES] )
|
|
{
|
|
MDLCACHE_CRITICAL_SECTION();
|
|
|
|
if ( !GetModel() )
|
|
return false;
|
|
|
|
CStudioHdr *pStudioHdr = GetModelPtr();
|
|
if (!pStudioHdr)
|
|
return false;
|
|
|
|
mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( GetHitboxSet() );
|
|
if ( !set )
|
|
return false;
|
|
|
|
if ( !set->numhitboxes )
|
|
return false;
|
|
|
|
CBoneCache *pCache = GetBoneCache( pStudioHdr );
|
|
pCache->ReadCachedBonePointers( pHitboxToWorld, pStudioHdr->numbones() );
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
bool C_BaseAnimating::OnPostInternalDrawModel( ClientModelRenderInfo_t *pInfo )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
bool C_BaseAnimating::OnInternalDrawModel( ClientModelRenderInfo_t *pInfo )
|
|
{
|
|
if ( m_hLightingOriginRelative.Get() )
|
|
{
|
|
C_InfoLightingRelative *pInfoLighting = assert_cast<C_InfoLightingRelative*>( m_hLightingOriginRelative.Get() );
|
|
pInfoLighting->GetLightingOffset( pInfo->lightingOffset );
|
|
pInfo->pLightingOffset = &pInfo->lightingOffset;
|
|
}
|
|
if ( m_hLightingOrigin )
|
|
{
|
|
pInfo->pLightingOrigin = &(m_hLightingOrigin->GetAbsOrigin());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::DoInternalDrawModel( ClientModelRenderInfo_t *pInfo, DrawModelState_t *pState, matrix3x4_t *pBoneToWorldArray )
|
|
{
|
|
if ( pState)
|
|
{
|
|
modelrender->DrawModelExecute( *pState, *pInfo, pBoneToWorldArray );
|
|
}
|
|
|
|
if ( vcollide_wireframe.GetBool() )
|
|
{
|
|
if ( IsRagdoll() )
|
|
{
|
|
m_pRagdoll->DrawWireframe();
|
|
}
|
|
else if ( IsSolid() && CollisionProp()->GetSolid() == SOLID_VPHYSICS )
|
|
{
|
|
vcollide_t *pCollide = modelinfo->GetVCollide( GetModelIndex() );
|
|
if ( pCollide && pCollide->solidCount == 1 )
|
|
{
|
|
static color32 debugColor = {0,255,255,0};
|
|
matrix3x4_t matrix;
|
|
AngleMatrix( GetAbsAngles(), GetAbsOrigin(), matrix );
|
|
engine->DebugDrawPhysCollide( pCollide->solids[0], NULL, matrix, debugColor );
|
|
if ( VPhysicsGetObject() )
|
|
{
|
|
static color32 debugColorPhys = {255,0,0,0};
|
|
matrix3x4_t matrix;
|
|
VPhysicsGetObject()->GetPositionMatrix( &matrix );
|
|
engine->DebugDrawPhysCollide( pCollide->solids[0], NULL, matrix, debugColorPhys );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Draws the object
|
|
// Input : flags -
|
|
//-----------------------------------------------------------------------------
|
|
int C_BaseAnimating::InternalDrawModel( int flags )
|
|
{
|
|
VPROF( "C_BaseAnimating::InternalDrawModel" );
|
|
|
|
if ( !GetModel() )
|
|
return 0;
|
|
|
|
// This should never happen, but if the server class hierarchy has bmodel entities derived from CBaseAnimating or does a
|
|
// SetModel with the wrong type of model, this could occur.
|
|
if ( modelinfo->GetModelType( GetModel() ) != mod_studio )
|
|
{
|
|
return BaseClass::DrawModel( flags );
|
|
}
|
|
|
|
// Make sure hdr is valid for drawing
|
|
if ( !GetModelPtr() )
|
|
return 0;
|
|
|
|
UpdateBoneAttachments( );
|
|
|
|
if ( IsEffectActive( EF_ITEM_BLINK ) )
|
|
{
|
|
flags |= STUDIO_ITEM_BLINK;
|
|
}
|
|
|
|
ClientModelRenderInfo_t info;
|
|
ClientModelRenderInfo_t *pInfo;
|
|
|
|
pInfo = &info;
|
|
|
|
pInfo->flags = flags;
|
|
pInfo->pRenderable = this;
|
|
pInfo->instance = GetModelInstance();
|
|
pInfo->entity_index = index;
|
|
pInfo->pModel = GetModel();
|
|
pInfo->origin = GetRenderOrigin();
|
|
pInfo->angles = GetRenderAngles();
|
|
pInfo->skin = GetSkin();
|
|
pInfo->body = GetBody();
|
|
pInfo->hitboxset = m_nHitboxSet;
|
|
|
|
if ( !OnInternalDrawModel( pInfo ) )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
Assert( !pInfo->pModelToWorld);
|
|
if ( !pInfo->pModelToWorld )
|
|
{
|
|
pInfo->pModelToWorld = &pInfo->modelToWorld;
|
|
|
|
// Turns the origin + angles into a matrix
|
|
AngleMatrix( pInfo->angles, pInfo->origin, pInfo->modelToWorld );
|
|
}
|
|
|
|
DrawModelState_t state;
|
|
matrix3x4_t *pBoneToWorld = NULL;
|
|
bool bMarkAsDrawn = modelrender->DrawModelSetup( *pInfo, &state, NULL, &pBoneToWorld );
|
|
|
|
// Scale the base transform if we don't have a bone hierarchy
|
|
if ( IsModelScaled() )
|
|
{
|
|
CStudioHdr *pHdr = GetModelPtr();
|
|
if ( pHdr && pBoneToWorld && pHdr->numbones() == 1 )
|
|
{
|
|
// Scale the bone to world at this point
|
|
const float flScale = GetModelScale();
|
|
VectorScale( (*pBoneToWorld)[0], flScale, (*pBoneToWorld)[0] );
|
|
VectorScale( (*pBoneToWorld)[1], flScale, (*pBoneToWorld)[1] );
|
|
VectorScale( (*pBoneToWorld)[2], flScale, (*pBoneToWorld)[2] );
|
|
}
|
|
}
|
|
|
|
DoInternalDrawModel( pInfo, ( bMarkAsDrawn && ( pInfo->flags & STUDIO_RENDER ) ) ? &state : NULL, pBoneToWorld );
|
|
|
|
OnPostInternalDrawModel( pInfo );
|
|
|
|
return bMarkAsDrawn;
|
|
}
|
|
|
|
extern ConVar muzzleflash_light;
|
|
|
|
void C_BaseAnimating::ProcessMuzzleFlashEvent()
|
|
{
|
|
// If we have an attachment, then stick a light on it.
|
|
if ( muzzleflash_light.GetBool() )
|
|
{
|
|
//FIXME: We should really use a named attachment for this
|
|
if ( m_Attachments.Count() > 0 )
|
|
{
|
|
Vector vAttachment;
|
|
QAngle dummyAngles;
|
|
GetAttachment( 1, vAttachment, dummyAngles );
|
|
|
|
// Make an elight
|
|
dlight_t *el = effects->CL_AllocElight( LIGHT_INDEX_MUZZLEFLASH + index );
|
|
el->origin = vAttachment;
|
|
el->radius = random->RandomInt( 32, 64 );
|
|
el->decay = el->radius / 0.05f;
|
|
el->die = gpGlobals->curtime + 0.05f;
|
|
el->color.r = 255;
|
|
el->color.g = 192;
|
|
el->color.b = 64;
|
|
el->color.exponent = 5;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Internal routine to process animation events for studiomodels
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::DoAnimationEvents( CStudioHdr *pStudioHdr )
|
|
{
|
|
if ( !pStudioHdr )
|
|
return;
|
|
|
|
#ifdef DEBUG
|
|
bool watch = dbganimmodel.GetString()[0] && V_stristr( pStudioHdr->pszName(), dbganimmodel.GetString() );
|
|
#else
|
|
bool watch = false; // Q_strstr( hdr->name, "rifle" ) ? true : false;
|
|
#endif
|
|
|
|
//Adrian: eh? This should never happen.
|
|
if ( GetSequence() == -1 )
|
|
return;
|
|
|
|
// build root animation
|
|
float flEventCycle = GetCycle();
|
|
|
|
// If we're invisible, don't draw the muzzle flash
|
|
bool bIsInvisible = !IsVisible() && !IsViewModel() && !IsMenuModel();
|
|
|
|
if ( bIsInvisible && !clienttools->IsInRecordingMode() )
|
|
return;
|
|
|
|
// add in muzzleflash effect
|
|
if ( ShouldMuzzleFlash() )
|
|
{
|
|
DisableMuzzleFlash();
|
|
|
|
ProcessMuzzleFlashEvent();
|
|
}
|
|
|
|
// If we're invisible, don't process animation events.
|
|
if ( bIsInvisible )
|
|
return;
|
|
|
|
// If we don't have any sequences, don't do anything
|
|
int nStudioNumSeq = pStudioHdr->GetNumSeq();
|
|
if ( nStudioNumSeq < 1 )
|
|
{
|
|
Warning( "%s[%d]: no sequences?\n", GetDebugName(), entindex() );
|
|
Assert( nStudioNumSeq >= 1 );
|
|
return;
|
|
}
|
|
|
|
int nSeqNum = GetSequence();
|
|
if ( nSeqNum >= nStudioNumSeq )
|
|
{
|
|
// This can happen e.g. while reloading Heavy's shotgun, switch to the minigun.
|
|
Warning( "%s[%d]: Playing sequence %d but there's only %d in total?\n", GetDebugName(), entindex(), nSeqNum, nStudioNumSeq );
|
|
return;
|
|
}
|
|
|
|
mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( nSeqNum );
|
|
|
|
if (seqdesc.numevents == 0)
|
|
return;
|
|
|
|
// Forces anim event indices to get set and returns pEvent(0);
|
|
mstudioevent_t *pevent = GetEventIndexForSequence( seqdesc );
|
|
|
|
if ( watch )
|
|
{
|
|
Msg( "%i cycle %f\n", gpGlobals->tickcount, GetCycle() );
|
|
}
|
|
|
|
bool resetEvents = m_nResetEventsParity != m_nPrevResetEventsParity;
|
|
m_nPrevResetEventsParity = m_nResetEventsParity;
|
|
|
|
if (m_nEventSequence != GetSequence() || resetEvents )
|
|
{
|
|
if ( watch )
|
|
{
|
|
Msg( "new seq: %i - old seq: %i - reset: %s - m_flCycle %f - Model Name: %s - (time %.3f)\n",
|
|
GetSequence(), m_nEventSequence,
|
|
resetEvents ? "true" : "false",
|
|
GetCycle(), pStudioHdr->pszName(),
|
|
gpGlobals->curtime);
|
|
}
|
|
|
|
m_nEventSequence = GetSequence();
|
|
flEventCycle = 0.0f;
|
|
m_flPrevEventCycle = -0.01; // back up to get 0'th frame animations
|
|
}
|
|
|
|
// stalled?
|
|
if (flEventCycle == m_flPrevEventCycle)
|
|
return;
|
|
|
|
if ( watch )
|
|
{
|
|
Msg( "%i (seq %d cycle %.3f ) evcycle %.3f prevevcycle %.3f (time %.3f)\n",
|
|
gpGlobals->tickcount,
|
|
GetSequence(),
|
|
GetCycle(),
|
|
flEventCycle,
|
|
m_flPrevEventCycle,
|
|
gpGlobals->curtime );
|
|
}
|
|
|
|
// check for looping
|
|
BOOL bLooped = false;
|
|
if (flEventCycle <= m_flPrevEventCycle)
|
|
{
|
|
if (m_flPrevEventCycle - flEventCycle > 0.5)
|
|
{
|
|
bLooped = true;
|
|
}
|
|
else
|
|
{
|
|
// things have backed up, which is bad since it'll probably result in a hitch in the animation playback
|
|
// but, don't play events again for the same time slice
|
|
return;
|
|
}
|
|
}
|
|
|
|
// This makes sure events that occur at the end of a sequence occur are
|
|
// sent before events that occur at the beginning of a sequence.
|
|
if (bLooped)
|
|
{
|
|
for (int i = 0; i < (int)seqdesc.numevents; i++)
|
|
{
|
|
// ignore all non-client-side events
|
|
|
|
if ( pevent[i].type & AE_TYPE_NEWEVENTSYSTEM )
|
|
{
|
|
if ( !( pevent[i].type & AE_TYPE_CLIENT ) )
|
|
continue;
|
|
}
|
|
else if ( pevent[i].event < 5000 ) //Adrian - Support the old event system
|
|
continue;
|
|
|
|
if ( pevent[i].cycle <= m_flPrevEventCycle )
|
|
continue;
|
|
|
|
if ( watch )
|
|
{
|
|
Msg( "%i FE %i Looped cycle %f, prev %f ev %f (time %.3f)\n",
|
|
gpGlobals->tickcount,
|
|
pevent[i].event,
|
|
pevent[i].cycle,
|
|
m_flPrevEventCycle,
|
|
flEventCycle,
|
|
gpGlobals->curtime );
|
|
}
|
|
|
|
|
|
FireEvent( GetAbsOrigin(), GetAbsAngles(), pevent[ i ].event, pevent[ i ].pszOptions() );
|
|
}
|
|
|
|
// Necessary to get the next loop working
|
|
m_flPrevEventCycle = -0.01;
|
|
}
|
|
|
|
for (int i = 0; i < (int)seqdesc.numevents; i++)
|
|
{
|
|
if ( pevent[i].type & AE_TYPE_NEWEVENTSYSTEM )
|
|
{
|
|
if ( !( pevent[i].type & AE_TYPE_CLIENT ) )
|
|
continue;
|
|
}
|
|
else if ( pevent[i].event < 5000 ) //Adrian - Support the old event system
|
|
continue;
|
|
|
|
if ( (pevent[i].cycle > m_flPrevEventCycle && pevent[i].cycle <= flEventCycle) )
|
|
{
|
|
if ( watch )
|
|
{
|
|
Msg( "%i (seq: %d) FE %i Normal cycle %f, prev %f ev %f (time %.3f)\n",
|
|
gpGlobals->tickcount,
|
|
GetSequence(),
|
|
pevent[i].event,
|
|
pevent[i].cycle,
|
|
m_flPrevEventCycle,
|
|
flEventCycle,
|
|
gpGlobals->curtime );
|
|
}
|
|
|
|
FireEvent( GetAbsOrigin(), GetAbsAngles(), pevent[ i ].event, pevent[ i ].pszOptions() );
|
|
}
|
|
}
|
|
|
|
m_flPrevEventCycle = flEventCycle;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Parses a muzzle effect event and sends it out for drawing
|
|
// Input : *options - event parameters in text format
|
|
// isFirstPerson - whether this is coming from an NPC or the player
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool C_BaseAnimating::DispatchMuzzleEffect( const char *options, bool isFirstPerson )
|
|
{
|
|
const char *p = options;
|
|
char token[128];
|
|
int weaponType = 0;
|
|
|
|
// Get the first parameter
|
|
p = nexttoken( token, p, ' ' );
|
|
|
|
// Find the weapon type
|
|
if ( token )
|
|
{
|
|
//TODO: Parse the type from a list instead
|
|
if ( Q_stricmp( token, "COMBINE" ) == 0 )
|
|
{
|
|
weaponType = MUZZLEFLASH_COMBINE;
|
|
}
|
|
else if ( Q_stricmp( token, "SMG1" ) == 0 )
|
|
{
|
|
weaponType = MUZZLEFLASH_SMG1;
|
|
}
|
|
else if ( Q_stricmp( token, "PISTOL" ) == 0 )
|
|
{
|
|
weaponType = MUZZLEFLASH_PISTOL;
|
|
}
|
|
else if ( Q_stricmp( token, "SHOTGUN" ) == 0 )
|
|
{
|
|
weaponType = MUZZLEFLASH_SHOTGUN;
|
|
}
|
|
else if ( Q_stricmp( token, "357" ) == 0 )
|
|
{
|
|
weaponType = MUZZLEFLASH_357;
|
|
}
|
|
else if ( Q_stricmp( token, "RPG" ) == 0 )
|
|
{
|
|
weaponType = MUZZLEFLASH_RPG;
|
|
}
|
|
else
|
|
{
|
|
//NOTENOTE: This means you specified an invalid muzzleflash type, check your spelling?
|
|
Assert( 0 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//NOTENOTE: This means that there wasn't a proper parameter passed into the animevent
|
|
Assert( 0 );
|
|
return false;
|
|
}
|
|
|
|
// Get the second parameter
|
|
p = nexttoken( token, p, ' ' );
|
|
|
|
int attachmentIndex = -1;
|
|
|
|
// Find the attachment name
|
|
if ( token )
|
|
{
|
|
attachmentIndex = LookupAttachment( token );
|
|
|
|
// Found an invalid attachment
|
|
if ( attachmentIndex <= 0 )
|
|
{
|
|
//NOTENOTE: This means that the attachment you're trying to use is invalid
|
|
Assert( 0 );
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//NOTENOTE: This means that there wasn't a proper parameter passed into the animevent
|
|
Assert( 0 );
|
|
return false;
|
|
}
|
|
|
|
// Send it out
|
|
tempents->MuzzleFlash( weaponType, GetRefEHandle(), attachmentIndex, isFirstPerson );
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void MaterialFootstepSound( C_BaseAnimating *pEnt, bool bLeftFoot, float flVolume )
|
|
{
|
|
trace_t tr;
|
|
Vector traceStart;
|
|
QAngle angles;
|
|
|
|
int attachment;
|
|
|
|
//!!!PERF - These string lookups here aren't the swiftest, but
|
|
// this doesn't get called very frequently unless a lot of NPCs
|
|
// are using this code.
|
|
if( bLeftFoot )
|
|
{
|
|
attachment = pEnt->LookupAttachment( "LeftFoot" );
|
|
}
|
|
else
|
|
{
|
|
attachment = pEnt->LookupAttachment( "RightFoot" );
|
|
}
|
|
|
|
if( attachment == -1 )
|
|
{
|
|
// Exit if this NPC doesn't have the proper attachments.
|
|
return;
|
|
}
|
|
|
|
pEnt->GetAttachment( attachment, traceStart, angles );
|
|
|
|
UTIL_TraceLine( traceStart, traceStart - Vector( 0, 0, 48.0f), MASK_SHOT_HULL, pEnt, COLLISION_GROUP_NONE, &tr );
|
|
if( tr.fraction < 1.0 && tr.m_pEnt )
|
|
{
|
|
surfacedata_t *psurf = physprops->GetSurfaceData( tr.surface.surfaceProps );
|
|
if( psurf )
|
|
{
|
|
EmitSound_t params;
|
|
if( bLeftFoot )
|
|
{
|
|
params.m_pSoundName = physprops->GetString(psurf->sounds.stepleft);
|
|
}
|
|
else
|
|
{
|
|
params.m_pSoundName = physprops->GetString(psurf->sounds.stepright);
|
|
}
|
|
|
|
CPASAttenuationFilter filter( pEnt, params.m_pSoundName );
|
|
|
|
params.m_bWarnOnDirectWaveReference = true;
|
|
params.m_flVolume = flVolume;
|
|
|
|
pEnt->EmitSound( filter, pEnt->entindex(), params );
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *origin -
|
|
// *angles -
|
|
// event -
|
|
// *options -
|
|
// numAttachments -
|
|
// attachments[] -
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::FireEvent( const Vector& origin, const QAngle& angles, int event, const char *options )
|
|
{
|
|
Vector attachOrigin;
|
|
QAngle attachAngles;
|
|
|
|
switch( event )
|
|
{
|
|
case AE_CL_CREATE_PARTICLE_EFFECT:
|
|
{
|
|
int iAttachment = -1;
|
|
int iAttachType = PATTACH_ABSORIGIN_FOLLOW;
|
|
char token[256];
|
|
char szParticleEffect[256];
|
|
|
|
// Get the particle effect name
|
|
const char *p = options;
|
|
p = nexttoken(token, p, ' ');
|
|
if ( token )
|
|
{
|
|
const char* mtoken = ModifyEventParticles( token );
|
|
if ( !mtoken || mtoken[0] == '\0' )
|
|
return;
|
|
Q_strncpy( szParticleEffect, mtoken, sizeof(szParticleEffect) );
|
|
}
|
|
|
|
// Get the attachment type
|
|
p = nexttoken(token, p, ' ');
|
|
if ( token )
|
|
{
|
|
iAttachType = GetAttachTypeFromString( token );
|
|
if ( iAttachType == -1 )
|
|
{
|
|
Warning("Invalid attach type specified for particle effect anim event. Trying to spawn effect '%s' with attach type of '%s'\n", szParticleEffect, token );
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Get the attachment point index
|
|
p = nexttoken(token, p, ' ');
|
|
if ( token )
|
|
{
|
|
iAttachment = atoi(token);
|
|
|
|
// See if we can find any attachment points matching the name
|
|
if ( token[0] != '0' && iAttachment == 0 )
|
|
{
|
|
iAttachment = LookupAttachment( token );
|
|
if ( iAttachment <= 0 )
|
|
{
|
|
Warning( "Failed to find attachment point specified for particle effect anim event. Trying to spawn effect '%s' on attachment named '%s'\n", szParticleEffect, token );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Spawn the particle effect
|
|
ParticleProp()->Create( szParticleEffect, (ParticleAttachment_t)iAttachType, iAttachment );
|
|
}
|
|
break;
|
|
|
|
case AE_CL_PLAYSOUND:
|
|
{
|
|
CLocalPlayerFilter filter;
|
|
|
|
if ( m_Attachments.Count() > 0)
|
|
{
|
|
GetAttachment( 1, attachOrigin, attachAngles );
|
|
EmitSound( filter, GetSoundSourceIndex(), options, &attachOrigin );
|
|
}
|
|
else
|
|
{
|
|
EmitSound( filter, GetSoundSourceIndex(), options, &GetAbsOrigin() );
|
|
}
|
|
}
|
|
break;
|
|
case AE_CL_STOPSOUND:
|
|
{
|
|
StopSound( GetSoundSourceIndex(), options );
|
|
}
|
|
break;
|
|
|
|
case CL_EVENT_FOOTSTEP_LEFT:
|
|
{
|
|
#ifndef HL2MP
|
|
char pSoundName[256];
|
|
if ( !options || !options[0] )
|
|
{
|
|
options = "NPC_CombineS";
|
|
}
|
|
|
|
Vector vel;
|
|
EstimateAbsVelocity( vel );
|
|
|
|
// If he's moving fast enough, play the run sound
|
|
if ( vel.Length2DSqr() > RUN_SPEED_ESTIMATE_SQR )
|
|
{
|
|
Q_snprintf( pSoundName, 256, "%s.RunFootstepLeft", options );
|
|
}
|
|
else
|
|
{
|
|
Q_snprintf( pSoundName, 256, "%s.FootstepLeft", options );
|
|
}
|
|
EmitSound( pSoundName );
|
|
#endif
|
|
}
|
|
break;
|
|
|
|
case CL_EVENT_FOOTSTEP_RIGHT:
|
|
{
|
|
#ifndef HL2MP
|
|
char pSoundName[256];
|
|
if ( !options || !options[0] )
|
|
{
|
|
options = "NPC_CombineS";
|
|
}
|
|
|
|
Vector vel;
|
|
EstimateAbsVelocity( vel );
|
|
// If he's moving fast enough, play the run sound
|
|
if ( vel.Length2DSqr() > RUN_SPEED_ESTIMATE_SQR )
|
|
{
|
|
Q_snprintf( pSoundName, 256, "%s.RunFootstepRight", options );
|
|
}
|
|
else
|
|
{
|
|
Q_snprintf( pSoundName, 256, "%s.FootstepRight", options );
|
|
}
|
|
EmitSound( pSoundName );
|
|
#endif
|
|
}
|
|
break;
|
|
|
|
case CL_EVENT_MFOOTSTEP_LEFT:
|
|
{
|
|
MaterialFootstepSound( this, true, VOL_NORM * 0.5f );
|
|
}
|
|
break;
|
|
|
|
case CL_EVENT_MFOOTSTEP_RIGHT:
|
|
{
|
|
MaterialFootstepSound( this, false, VOL_NORM * 0.5f );
|
|
}
|
|
break;
|
|
|
|
case CL_EVENT_MFOOTSTEP_LEFT_LOUD:
|
|
{
|
|
MaterialFootstepSound( this, true, VOL_NORM );
|
|
}
|
|
break;
|
|
|
|
case CL_EVENT_MFOOTSTEP_RIGHT_LOUD:
|
|
{
|
|
MaterialFootstepSound( this, false, VOL_NORM );
|
|
}
|
|
break;
|
|
|
|
// Eject brass
|
|
case CL_EVENT_EJECTBRASS1:
|
|
if ( m_Attachments.Count() > 0 )
|
|
{
|
|
if ( MainViewOrigin().DistToSqr( GetAbsOrigin() ) < (256 * 256) )
|
|
{
|
|
Vector attachOrigin;
|
|
QAngle attachAngles;
|
|
|
|
if( GetAttachment( 2, attachOrigin, attachAngles ) )
|
|
{
|
|
tempents->EjectBrass( attachOrigin, attachAngles, GetAbsAngles(), atoi( options ) );
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case AE_MUZZLEFLASH:
|
|
{
|
|
// Send out the effect for a player
|
|
DispatchMuzzleEffect( options, true );
|
|
break;
|
|
}
|
|
|
|
case AE_NPC_MUZZLEFLASH:
|
|
{
|
|
// Send out the effect for an NPC
|
|
DispatchMuzzleEffect( options, false );
|
|
break;
|
|
}
|
|
|
|
// OBSOLETE EVENTS. REPLACED BY NEWER SYSTEMS.
|
|
// See below in FireObsoleteEvent() for comments on what to use instead.
|
|
case AE_CLIENT_EFFECT_ATTACH:
|
|
case CL_EVENT_DISPATCHEFFECT0:
|
|
case CL_EVENT_DISPATCHEFFECT1:
|
|
case CL_EVENT_DISPATCHEFFECT2:
|
|
case CL_EVENT_DISPATCHEFFECT3:
|
|
case CL_EVENT_DISPATCHEFFECT4:
|
|
case CL_EVENT_DISPATCHEFFECT5:
|
|
case CL_EVENT_DISPATCHEFFECT6:
|
|
case CL_EVENT_DISPATCHEFFECT7:
|
|
case CL_EVENT_DISPATCHEFFECT8:
|
|
case CL_EVENT_DISPATCHEFFECT9:
|
|
case CL_EVENT_MUZZLEFLASH0:
|
|
case CL_EVENT_MUZZLEFLASH1:
|
|
case CL_EVENT_MUZZLEFLASH2:
|
|
case CL_EVENT_MUZZLEFLASH3:
|
|
case CL_EVENT_NPC_MUZZLEFLASH0:
|
|
case CL_EVENT_NPC_MUZZLEFLASH1:
|
|
case CL_EVENT_NPC_MUZZLEFLASH2:
|
|
case CL_EVENT_NPC_MUZZLEFLASH3:
|
|
case CL_EVENT_SPARK0:
|
|
case CL_EVENT_SOUND:
|
|
FireObsoleteEvent( origin, angles, event, options );
|
|
break;
|
|
|
|
case AE_CL_ENABLE_BODYGROUP:
|
|
{
|
|
int index = FindBodygroupByName( options );
|
|
if ( index >= 0 )
|
|
{
|
|
SetBodygroup( index, 1 );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case AE_CL_DISABLE_BODYGROUP:
|
|
{
|
|
int index = FindBodygroupByName( options );
|
|
if ( index >= 0 )
|
|
{
|
|
SetBodygroup( index, 0 );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case AE_CL_BODYGROUP_SET_VALUE:
|
|
{
|
|
char szBodygroupName[256];
|
|
int value = 0;
|
|
|
|
char token[256];
|
|
|
|
const char *p = options;
|
|
|
|
// Bodygroup Name
|
|
p = nexttoken(token, p, ' ');
|
|
if ( token )
|
|
{
|
|
Q_strncpy( szBodygroupName, token, sizeof(szBodygroupName) );
|
|
}
|
|
|
|
// Get the desired value
|
|
p = nexttoken(token, p, ' ');
|
|
if ( token )
|
|
{
|
|
value = atoi( token );
|
|
}
|
|
|
|
int index = FindBodygroupByName( szBodygroupName );
|
|
if ( index >= 0 )
|
|
{
|
|
SetBodygroup( index, value );
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: These events are all obsolete events, left here to support old games.
|
|
// Their systems have all been replaced with better ones.
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::FireObsoleteEvent( const Vector& origin, const QAngle& angles, int event, const char *options )
|
|
{
|
|
Vector attachOrigin;
|
|
QAngle attachAngles;
|
|
|
|
switch( event )
|
|
{
|
|
// Obsolete. Use the AE_CL_CREATE_PARTICLE_EFFECT event instead, which uses the artist driven particle system & editor.
|
|
case AE_CLIENT_EFFECT_ATTACH:
|
|
{
|
|
int iAttachment = -1;
|
|
int iParam = 0;
|
|
char token[128];
|
|
char effectFunc[128];
|
|
|
|
const char *p = options;
|
|
|
|
p = nexttoken(token, p, ' ');
|
|
|
|
if( token )
|
|
{
|
|
Q_strncpy( effectFunc, token, sizeof(effectFunc) );
|
|
}
|
|
|
|
p = nexttoken(token, p, ' ');
|
|
|
|
if( token )
|
|
{
|
|
iAttachment = atoi(token);
|
|
}
|
|
|
|
p = nexttoken(token, p, ' ');
|
|
|
|
if( token )
|
|
{
|
|
iParam = atoi(token);
|
|
}
|
|
|
|
if ( iAttachment != -1 && m_Attachments.Count() >= iAttachment )
|
|
{
|
|
GetAttachment( iAttachment, attachOrigin, attachAngles );
|
|
|
|
// Fill out the generic data
|
|
CEffectData data;
|
|
data.m_vOrigin = attachOrigin;
|
|
data.m_vAngles = attachAngles;
|
|
AngleVectors( attachAngles, &data.m_vNormal );
|
|
data.m_hEntity = GetRefEHandle();
|
|
data.m_nAttachmentIndex = iAttachment + 1;
|
|
data.m_fFlags = iParam;
|
|
|
|
DispatchEffect( effectFunc, data );
|
|
}
|
|
}
|
|
break;
|
|
|
|
// Obsolete. Use the AE_CL_CREATE_PARTICLE_EFFECT event instead, which uses the artist driven particle system & editor.
|
|
case CL_EVENT_DISPATCHEFFECT0:
|
|
case CL_EVENT_DISPATCHEFFECT1:
|
|
case CL_EVENT_DISPATCHEFFECT2:
|
|
case CL_EVENT_DISPATCHEFFECT3:
|
|
case CL_EVENT_DISPATCHEFFECT4:
|
|
case CL_EVENT_DISPATCHEFFECT5:
|
|
case CL_EVENT_DISPATCHEFFECT6:
|
|
case CL_EVENT_DISPATCHEFFECT7:
|
|
case CL_EVENT_DISPATCHEFFECT8:
|
|
case CL_EVENT_DISPATCHEFFECT9:
|
|
{
|
|
int iAttachment = -1;
|
|
|
|
// First person muzzle flashes
|
|
switch (event)
|
|
{
|
|
case CL_EVENT_DISPATCHEFFECT0:
|
|
iAttachment = 0;
|
|
break;
|
|
|
|
case CL_EVENT_DISPATCHEFFECT1:
|
|
iAttachment = 1;
|
|
break;
|
|
|
|
case CL_EVENT_DISPATCHEFFECT2:
|
|
iAttachment = 2;
|
|
break;
|
|
|
|
case CL_EVENT_DISPATCHEFFECT3:
|
|
iAttachment = 3;
|
|
break;
|
|
|
|
case CL_EVENT_DISPATCHEFFECT4:
|
|
iAttachment = 4;
|
|
break;
|
|
|
|
case CL_EVENT_DISPATCHEFFECT5:
|
|
iAttachment = 5;
|
|
break;
|
|
|
|
case CL_EVENT_DISPATCHEFFECT6:
|
|
iAttachment = 6;
|
|
break;
|
|
|
|
case CL_EVENT_DISPATCHEFFECT7:
|
|
iAttachment = 7;
|
|
break;
|
|
|
|
case CL_EVENT_DISPATCHEFFECT8:
|
|
iAttachment = 8;
|
|
break;
|
|
|
|
case CL_EVENT_DISPATCHEFFECT9:
|
|
iAttachment = 9;
|
|
break;
|
|
}
|
|
|
|
if ( iAttachment != -1 && m_Attachments.Count() > iAttachment )
|
|
{
|
|
GetAttachment( iAttachment+1, attachOrigin, attachAngles );
|
|
|
|
// Fill out the generic data
|
|
CEffectData data;
|
|
data.m_vOrigin = attachOrigin;
|
|
data.m_vAngles = attachAngles;
|
|
AngleVectors( attachAngles, &data.m_vNormal );
|
|
data.m_hEntity = GetRefEHandle();
|
|
data.m_nAttachmentIndex = iAttachment + 1;
|
|
|
|
DispatchEffect( options, data );
|
|
}
|
|
}
|
|
break;
|
|
|
|
// Obsolete. Use the AE_MUZZLEFLASH / AE_NPC_MUZZLEFLASH events instead.
|
|
case CL_EVENT_MUZZLEFLASH0:
|
|
case CL_EVENT_MUZZLEFLASH1:
|
|
case CL_EVENT_MUZZLEFLASH2:
|
|
case CL_EVENT_MUZZLEFLASH3:
|
|
case CL_EVENT_NPC_MUZZLEFLASH0:
|
|
case CL_EVENT_NPC_MUZZLEFLASH1:
|
|
case CL_EVENT_NPC_MUZZLEFLASH2:
|
|
case CL_EVENT_NPC_MUZZLEFLASH3:
|
|
{
|
|
int iAttachment = -1;
|
|
bool bFirstPerson = true;
|
|
|
|
// First person muzzle flashes
|
|
switch (event)
|
|
{
|
|
case CL_EVENT_MUZZLEFLASH0:
|
|
iAttachment = 0;
|
|
break;
|
|
|
|
case CL_EVENT_MUZZLEFLASH1:
|
|
iAttachment = 1;
|
|
break;
|
|
|
|
case CL_EVENT_MUZZLEFLASH2:
|
|
iAttachment = 2;
|
|
break;
|
|
|
|
case CL_EVENT_MUZZLEFLASH3:
|
|
iAttachment = 3;
|
|
break;
|
|
|
|
// Third person muzzle flashes
|
|
case CL_EVENT_NPC_MUZZLEFLASH0:
|
|
iAttachment = 0;
|
|
bFirstPerson = false;
|
|
break;
|
|
|
|
case CL_EVENT_NPC_MUZZLEFLASH1:
|
|
iAttachment = 1;
|
|
bFirstPerson = false;
|
|
break;
|
|
|
|
case CL_EVENT_NPC_MUZZLEFLASH2:
|
|
iAttachment = 2;
|
|
bFirstPerson = false;
|
|
break;
|
|
|
|
case CL_EVENT_NPC_MUZZLEFLASH3:
|
|
iAttachment = 3;
|
|
bFirstPerson = false;
|
|
break;
|
|
}
|
|
|
|
if ( iAttachment != -1 && m_Attachments.Count() > iAttachment )
|
|
{
|
|
GetAttachment( iAttachment+1, attachOrigin, attachAngles );
|
|
int entId = render->GetViewEntity();
|
|
ClientEntityHandle_t hEntity = ClientEntityList().EntIndexToHandle( entId );
|
|
tempents->MuzzleFlash( attachOrigin, attachAngles, atoi( options ), hEntity, bFirstPerson );
|
|
}
|
|
}
|
|
break;
|
|
|
|
// Obsolete: Use the AE_CL_CREATE_PARTICLE_EFFECT event instead, which uses the artist driven particle system & editor.
|
|
case CL_EVENT_SPARK0:
|
|
{
|
|
Vector vecForward;
|
|
GetAttachment( 1, attachOrigin, attachAngles );
|
|
AngleVectors( attachAngles, &vecForward );
|
|
g_pEffects->Sparks( attachOrigin, atoi( options ), 1, &vecForward );
|
|
}
|
|
break;
|
|
|
|
// Obsolete: Use the AE_CL_PLAYSOUND event instead, which doesn't rely on a magic number in the .qc
|
|
case CL_EVENT_SOUND:
|
|
{
|
|
CLocalPlayerFilter filter;
|
|
|
|
if ( m_Attachments.Count() > 0)
|
|
{
|
|
GetAttachment( 1, attachOrigin, attachAngles );
|
|
EmitSound( filter, GetSoundSourceIndex(), options, &attachOrigin );
|
|
}
|
|
else
|
|
{
|
|
EmitSound( filter, GetSoundSourceIndex(), options );
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool C_BaseAnimating::IsSelfAnimating()
|
|
{
|
|
if ( m_bClientSideAnimation )
|
|
return true;
|
|
|
|
// Yes, we use animtime.
|
|
int iMoveType = GetMoveType();
|
|
if ( iMoveType != MOVETYPE_STEP &&
|
|
iMoveType != MOVETYPE_NONE &&
|
|
iMoveType != MOVETYPE_WALK &&
|
|
iMoveType != MOVETYPE_FLY &&
|
|
iMoveType != MOVETYPE_FLYGRAVITY )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Called by networking code when an entity is new to the PVS or comes down with the EF_NOINTERP flag set.
|
|
// The position history data is flushed out right after this call, so we need to store off the current data
|
|
// in the latched fields so we try to interpolate
|
|
// Input : *ent -
|
|
// full_reset -
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::ResetLatched( void )
|
|
{
|
|
// Reset the IK
|
|
if ( m_pIk )
|
|
{
|
|
delete m_pIk;
|
|
m_pIk = NULL;
|
|
}
|
|
|
|
BaseClass::ResetLatched();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool C_BaseAnimating::Interpolate( float flCurrentTime )
|
|
{
|
|
// ragdolls don't need interpolation
|
|
if ( m_pRagdoll )
|
|
return true;
|
|
|
|
VPROF( "C_BaseAnimating::Interpolate" );
|
|
|
|
Vector oldOrigin;
|
|
QAngle oldAngles;
|
|
Vector oldVel;
|
|
float flOldCycle = GetCycle();
|
|
int nChangeFlags = 0;
|
|
|
|
int bNoMoreChanges;
|
|
int retVal = BaseInterpolatePart1( flCurrentTime, oldOrigin, oldAngles, oldVel, bNoMoreChanges );
|
|
if ( retVal == INTERPOLATE_STOP )
|
|
{
|
|
if ( bNoMoreChanges )
|
|
RemoveFromInterpolationList();
|
|
return true;
|
|
}
|
|
|
|
|
|
// Did cycle change?
|
|
if( GetCycle() != flOldCycle )
|
|
nChangeFlags |= ANIMATION_CHANGED;
|
|
|
|
if ( bNoMoreChanges )
|
|
RemoveFromInterpolationList();
|
|
|
|
BaseInterpolatePart2( oldOrigin, oldAngles, oldVel, nChangeFlags );
|
|
return true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// returns true if we're currently being ragdolled
|
|
//-----------------------------------------------------------------------------
|
|
bool C_BaseAnimating::IsRagdoll() const
|
|
{
|
|
return m_pRagdoll && (m_nRenderFX == kRenderFxRagdoll);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// returns true if we're currently being ragdolled
|
|
//-----------------------------------------------------------------------------
|
|
bool C_BaseAnimating::IsAboutToRagdoll() const
|
|
{
|
|
return (m_nRenderFX == kRenderFxRagdoll);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Lets us check our sequence number after a network update
|
|
//-----------------------------------------------------------------------------
|
|
int C_BaseAnimating::RestoreData( const char *context, int slot, int type )
|
|
{
|
|
int retVal = BaseClass::RestoreData( context, slot, type );
|
|
CStudioHdr *pHdr = GetModelPtr();
|
|
if( pHdr && m_nSequence >= pHdr->GetNumSeq() )
|
|
{
|
|
// Don't let a network update give us an invalid sequence
|
|
m_nSequence = 0;
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// implements these so ragdolls can handle frustum culling & leaf visibility
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void C_BaseAnimating::GetRenderBounds( Vector& theMins, Vector& theMaxs )
|
|
{
|
|
if ( IsRagdoll() )
|
|
{
|
|
m_pRagdoll->GetRagdollBounds( theMins, theMaxs );
|
|
}
|
|
else if ( GetModel() )
|
|
{
|
|
CStudioHdr *pStudioHdr = GetModelPtr();
|
|
if ( !pStudioHdr|| !pStudioHdr->SequencesAvailable() || GetSequence() == -1 )
|
|
{
|
|
theMins = vec3_origin;
|
|
theMaxs = vec3_origin;
|
|
return;
|
|
}
|
|
if (!VectorCompare( vec3_origin, pStudioHdr->view_bbmin() ) || !VectorCompare( vec3_origin, pStudioHdr->view_bbmax() ))
|
|
{
|
|
// clipping bounding box
|
|
VectorCopy ( pStudioHdr->view_bbmin(), theMins);
|
|
VectorCopy ( pStudioHdr->view_bbmax(), theMaxs);
|
|
}
|
|
else
|
|
{
|
|
// movement bounding box
|
|
VectorCopy ( pStudioHdr->hull_min(), theMins);
|
|
VectorCopy ( pStudioHdr->hull_max(), theMaxs);
|
|
}
|
|
|
|
mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( GetSequence() );
|
|
VectorMin( seqdesc.bbmin, theMins, theMins );
|
|
VectorMax( seqdesc.bbmax, theMaxs, theMaxs );
|
|
}
|
|
else
|
|
{
|
|
theMins = vec3_origin;
|
|
theMaxs = vec3_origin;
|
|
}
|
|
|
|
// Scale this up depending on if our model is currently scaling
|
|
const float flScale = GetModelScale();
|
|
theMaxs *= flScale;
|
|
theMins *= flScale;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// implements these so ragdolls can handle frustum culling & leaf visibility
|
|
//-----------------------------------------------------------------------------
|
|
const Vector& C_BaseAnimating::GetRenderOrigin( void )
|
|
{
|
|
if ( IsRagdoll() )
|
|
{
|
|
return m_pRagdoll->GetRagdollOrigin();
|
|
}
|
|
else
|
|
{
|
|
return BaseClass::GetRenderOrigin();
|
|
}
|
|
}
|
|
|
|
const QAngle& C_BaseAnimating::GetRenderAngles( void )
|
|
{
|
|
if ( IsRagdoll() )
|
|
{
|
|
return vec3_angle;
|
|
|
|
}
|
|
else
|
|
{
|
|
return BaseClass::GetRenderAngles();
|
|
}
|
|
}
|
|
|
|
void C_BaseAnimating::RagdollMoved( void )
|
|
{
|
|
SetAbsOrigin( m_pRagdoll->GetRagdollOrigin() );
|
|
SetAbsAngles( vec3_angle );
|
|
|
|
Vector mins, maxs;
|
|
m_pRagdoll->GetRagdollBounds( mins, maxs );
|
|
SetCollisionBounds( mins, maxs );
|
|
|
|
// If the ragdoll moves, its render-to-texture shadow is dirty
|
|
InvalidatePhysicsRecursive( ANIMATION_CHANGED );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: My physics object has been updated, react or extract data
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::VPhysicsUpdate( IPhysicsObject *pPhysics )
|
|
{
|
|
// FIXME: Should make sure the physics objects being passed in
|
|
// is the ragdoll physics object, but I think it's pretty safe not to check
|
|
if (IsRagdoll())
|
|
{
|
|
m_pRagdoll->VPhysicsUpdate( pPhysics );
|
|
|
|
RagdollMoved();
|
|
|
|
return;
|
|
}
|
|
|
|
BaseClass::VPhysicsUpdate( pPhysics );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : updateType -
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::PreDataUpdate( DataUpdateType_t updateType )
|
|
{
|
|
VPROF( "C_BaseAnimating::PreDataUpdate" );
|
|
|
|
m_flOldCycle = GetCycle();
|
|
m_nOldSequence = GetSequence();
|
|
m_flOldModelScale = GetModelScale();
|
|
|
|
int i;
|
|
for ( i=0;i<MAXSTUDIOBONECTRLS;i++ )
|
|
{
|
|
m_flOldEncodedController[i] = m_flEncodedController[i];
|
|
}
|
|
|
|
for ( i=0;i<MAXSTUDIOPOSEPARAM;i++ )
|
|
{
|
|
m_flOldPoseParameters[i] = m_flPoseParameter[i];
|
|
}
|
|
|
|
BaseClass::PreDataUpdate( updateType );
|
|
}
|
|
|
|
void C_BaseAnimating::NotifyShouldTransmit( ShouldTransmitState_t state )
|
|
{
|
|
BaseClass::NotifyShouldTransmit( state );
|
|
|
|
if ( state == SHOULDTRANSMIT_START )
|
|
{
|
|
// If he's been firing a bunch, then he comes back into the PVS, his muzzle flash
|
|
// will show up even if he isn't firing now.
|
|
DisableMuzzleFlash();
|
|
|
|
m_nPrevResetEventsParity = m_nResetEventsParity;
|
|
m_nEventSequence = GetSequence();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : updateType -
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::PostDataUpdate( DataUpdateType_t updateType )
|
|
{
|
|
BaseClass::PostDataUpdate( updateType );
|
|
|
|
if ( m_bClientSideAnimation )
|
|
{
|
|
SetCycle( m_flOldCycle );
|
|
AddToClientSideAnimationList();
|
|
}
|
|
else
|
|
{
|
|
RemoveFromClientSideAnimationList();
|
|
}
|
|
|
|
bool bBoneControllersChanged = false;
|
|
|
|
int i;
|
|
for ( i=0;i<MAXSTUDIOBONECTRLS && !bBoneControllersChanged;i++ )
|
|
{
|
|
if ( m_flOldEncodedController[i] != m_flEncodedController[i] )
|
|
{
|
|
bBoneControllersChanged = true;
|
|
}
|
|
}
|
|
|
|
bool bPoseParametersChanged = false;
|
|
|
|
for ( i=0;i<MAXSTUDIOPOSEPARAM && !bPoseParametersChanged;i++ )
|
|
{
|
|
if ( m_flOldPoseParameters[i] != m_flPoseParameter[i] )
|
|
{
|
|
bPoseParametersChanged = true;
|
|
}
|
|
}
|
|
|
|
// Cycle change? Then re-render
|
|
bool bAnimationChanged = m_flOldCycle != GetCycle() || bBoneControllersChanged || bPoseParametersChanged;
|
|
bool bSequenceChanged = m_nOldSequence != GetSequence();
|
|
bool bScaleChanged = ( m_flOldModelScale != GetModelScale() );
|
|
if ( bAnimationChanged || bSequenceChanged || bScaleChanged )
|
|
{
|
|
InvalidatePhysicsRecursive( ANIMATION_CHANGED );
|
|
}
|
|
|
|
if ( bAnimationChanged || bSequenceChanged )
|
|
{
|
|
if ( m_bClientSideAnimation )
|
|
{
|
|
ClientSideAnimationChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : bnewentity -
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::OnPreDataChanged( DataUpdateType_t updateType )
|
|
{
|
|
BaseClass::OnPreDataChanged( updateType );
|
|
|
|
m_bLastClientSideFrameReset = m_bClientSideFrameReset;
|
|
}
|
|
|
|
void C_BaseAnimating::ForceSetupBonesAtTime( matrix3x4_t *pBonesOut, float flTime )
|
|
{
|
|
// blow the cached prev bones
|
|
InvalidateBoneCache();
|
|
|
|
// reset root position to flTime
|
|
Interpolate( flTime );
|
|
|
|
// Setup bone state at the given time
|
|
SetupBones( pBonesOut, MAXSTUDIOBONES, BONE_USED_BY_ANYTHING, flTime );
|
|
}
|
|
|
|
void C_BaseAnimating::GetRagdollInitBoneArrays( matrix3x4_t *pDeltaBones0, matrix3x4_t *pDeltaBones1, matrix3x4_t *pCurrentBones, float boneDt )
|
|
{
|
|
ForceSetupBonesAtTime( pDeltaBones0, gpGlobals->curtime - boneDt );
|
|
ForceSetupBonesAtTime( pDeltaBones1, gpGlobals->curtime );
|
|
float ragdollCreateTime = PhysGetSyncCreateTime();
|
|
if ( ragdollCreateTime != gpGlobals->curtime )
|
|
{
|
|
// The next simulation frame begins before the end of this frame
|
|
// so initialize the ragdoll at that time so that it will reach the current
|
|
// position at curtime. Otherwise the ragdoll will simulate forward from curtime
|
|
// and pop into the future a bit at this point of transition
|
|
ForceSetupBonesAtTime( pCurrentBones, ragdollCreateTime );
|
|
}
|
|
else
|
|
{
|
|
memcpy( pCurrentBones, m_CachedBoneData.Base(), sizeof( matrix3x4_t ) * m_CachedBoneData.Count() );
|
|
}
|
|
}
|
|
|
|
C_BaseAnimating *C_BaseAnimating::CreateRagdollCopy()
|
|
{
|
|
//Adrian: We now create a separate entity that becomes this entity's ragdoll.
|
|
//That way the server side version of this entity can go away.
|
|
//Plus we can hook save/restore code to these ragdolls so they don't fall on restore anymore.
|
|
C_ClientRagdoll *pRagdoll = new C_ClientRagdoll( false );
|
|
if ( pRagdoll == NULL )
|
|
return NULL;
|
|
|
|
TermRopes();
|
|
|
|
const model_t *model = GetModel();
|
|
const char *pModelName = modelinfo->GetModelName( model );
|
|
|
|
if ( pRagdoll->InitializeAsClientEntity( pModelName, RENDER_GROUP_OPAQUE_ENTITY ) == false )
|
|
{
|
|
pRagdoll->Release();
|
|
return NULL;
|
|
}
|
|
|
|
// move my current model instance to the ragdoll's so decals are preserved.
|
|
SnatchModelInstance( pRagdoll );
|
|
|
|
// We need to take these from the entity
|
|
pRagdoll->SetAbsOrigin( GetAbsOrigin() );
|
|
pRagdoll->SetAbsAngles( GetAbsAngles() );
|
|
|
|
pRagdoll->IgniteRagdoll( this );
|
|
pRagdoll->TransferDissolveFrom( this );
|
|
pRagdoll->InitModelEffects();
|
|
|
|
if ( AddRagdollToFadeQueue() == true )
|
|
{
|
|
pRagdoll->m_bImportant = NPC_IsImportantNPC( this );
|
|
s_RagdollLRU.MoveToTopOfLRU( pRagdoll, pRagdoll->m_bImportant );
|
|
pRagdoll->m_bFadeOut = true;
|
|
}
|
|
|
|
m_builtRagdoll = true;
|
|
AddEffects( EF_NODRAW );
|
|
|
|
if ( IsEffectActive( EF_NOSHADOW ) )
|
|
{
|
|
pRagdoll->AddEffects( EF_NOSHADOW );
|
|
}
|
|
|
|
pRagdoll->m_nRenderFX = kRenderFxRagdoll;
|
|
pRagdoll->SetRenderMode( GetRenderMode() );
|
|
pRagdoll->SetRenderColor( GetRenderColor().r, GetRenderColor().g, GetRenderColor().b, GetRenderColor().a );
|
|
|
|
pRagdoll->m_nBody = m_nBody;
|
|
pRagdoll->m_nSkin = GetSkin();
|
|
pRagdoll->m_vecForce = m_vecForce;
|
|
pRagdoll->m_nForceBone = m_nForceBone;
|
|
pRagdoll->SetNextClientThink( CLIENT_THINK_ALWAYS );
|
|
|
|
pRagdoll->SetModelName( AllocPooledString(pModelName) );
|
|
pRagdoll->SetModelScale( GetModelScale() );
|
|
return pRagdoll;
|
|
}
|
|
|
|
C_BaseAnimating *C_BaseAnimating::BecomeRagdollOnClient()
|
|
{
|
|
MoveToLastReceivedPosition( true );
|
|
GetAbsOrigin();
|
|
C_BaseAnimating *pRagdoll = CreateRagdollCopy();
|
|
|
|
matrix3x4_t boneDelta0[MAXSTUDIOBONES];
|
|
matrix3x4_t boneDelta1[MAXSTUDIOBONES];
|
|
matrix3x4_t currentBones[MAXSTUDIOBONES];
|
|
const float boneDt = 0.1f;
|
|
GetRagdollInitBoneArrays( boneDelta0, boneDelta1, currentBones, boneDt );
|
|
pRagdoll->InitAsClientRagdoll( boneDelta0, boneDelta1, currentBones, boneDt );
|
|
return pRagdoll;
|
|
}
|
|
|
|
bool C_BaseAnimating::InitAsClientRagdoll( const matrix3x4_t *pDeltaBones0, const matrix3x4_t *pDeltaBones1, const matrix3x4_t *pCurrentBonePosition, float boneDt, bool bFixedConstraints )
|
|
{
|
|
CStudioHdr *hdr = GetModelPtr();
|
|
if ( !hdr || m_pRagdoll || m_builtRagdoll )
|
|
return false;
|
|
|
|
m_builtRagdoll = true;
|
|
|
|
// Store off our old mins & maxs
|
|
m_vecPreRagdollMins = WorldAlignMins();
|
|
m_vecPreRagdollMaxs = WorldAlignMaxs();
|
|
|
|
|
|
// Force MOVETYPE_STEP interpolation
|
|
MoveType_t savedMovetype = GetMoveType();
|
|
SetMoveType( MOVETYPE_STEP );
|
|
|
|
// HACKHACK: force time to last interpolation position
|
|
m_flPlaybackRate = 1;
|
|
|
|
m_pRagdoll = CreateRagdoll( this, hdr, m_vecForce, m_nForceBone, pDeltaBones0, pDeltaBones1, pCurrentBonePosition, boneDt, bFixedConstraints );
|
|
|
|
// Cause the entity to recompute its shadow type and make a
|
|
// version which only updates when physics state changes
|
|
// NOTE: We have to do this after m_pRagdoll is assigned above
|
|
// because that's what ShadowCastType uses to figure out which type of shadow to use.
|
|
DestroyShadow();
|
|
CreateShadow();
|
|
|
|
// Cache off ragdoll bone positions/quaternions
|
|
if ( m_bStoreRagdollInfo && m_pRagdoll )
|
|
{
|
|
matrix3x4_t parentTransform;
|
|
AngleMatrix( GetAbsAngles(), GetAbsOrigin(), parentTransform );
|
|
// FIXME/CHECK: This might be too expensive to do every frame???
|
|
SaveRagdollInfo( hdr->numbones(), parentTransform, m_BoneAccessor );
|
|
}
|
|
|
|
SetMoveType( savedMovetype );
|
|
|
|
// Now set the dieragdoll sequence to get transforms for all
|
|
// non-simulated bones
|
|
m_nRestoreSequence = GetSequence();
|
|
SetSequence( SelectWeightedSequence( ACT_DIERAGDOLL ) );
|
|
m_nPrevSequence = GetSequence();
|
|
m_flPlaybackRate = 0;
|
|
UpdatePartitionListEntry();
|
|
|
|
NoteRagdollCreationTick( this );
|
|
|
|
UpdateVisibility();
|
|
|
|
#if defined( REPLAY_ENABLED )
|
|
// If Replay is enabled on server, add an entry to the ragdoll recorder for this entity
|
|
ConVar* pReplayEnable = (ConVar*)cvar->FindVar( "replay_enable" );
|
|
if ( m_pRagdoll && pReplayEnable && pReplayEnable->GetInt() && !engine->IsPlayingDemo() && !engine->IsPlayingTimeDemo() )
|
|
{
|
|
CReplayRagdollRecorder& RagdollRecorder = CReplayRagdollRecorder::Instance();
|
|
int nStartTick = TIME_TO_TICKS( engine->GetLastTimeStamp() );
|
|
RagdollRecorder.AddEntry( this, nStartTick, m_pRagdoll->RagdollBoneCount() );
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : bnewentity -
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::OnDataChanged( DataUpdateType_t updateType )
|
|
{
|
|
// don't let server change sequences after becoming a ragdoll
|
|
if ( m_pRagdoll && GetSequence() != m_nPrevSequence )
|
|
{
|
|
SetSequence( m_nPrevSequence );
|
|
m_flPlaybackRate = 0;
|
|
}
|
|
|
|
if ( !m_pRagdoll && m_nRestoreSequence != -1 )
|
|
{
|
|
SetSequence( m_nRestoreSequence );
|
|
m_nRestoreSequence = -1;
|
|
}
|
|
|
|
if (updateType == DATA_UPDATE_CREATED)
|
|
{
|
|
m_nPrevSequence = -1;
|
|
m_nRestoreSequence = -1;
|
|
}
|
|
|
|
|
|
|
|
bool modelchanged = false;
|
|
|
|
// UNDONE: The base class does this as well. So this is kind of ugly
|
|
// but getting a model by index is pretty cheap...
|
|
const model_t *pModel = modelinfo->GetModel( GetModelIndex() );
|
|
|
|
if ( pModel != GetModel() )
|
|
{
|
|
modelchanged = true;
|
|
}
|
|
|
|
BaseClass::OnDataChanged( updateType );
|
|
|
|
if ( (updateType == DATA_UPDATE_CREATED) || modelchanged )
|
|
{
|
|
ResetLatched();
|
|
// if you have this pose parameter, activate HL1-style lipsync/wave envelope tracking
|
|
if ( LookupPoseParameter( LIPSYNC_POSEPARAM_NAME ) != -1 )
|
|
{
|
|
MouthInfo().ActivateEnvelope();
|
|
}
|
|
}
|
|
|
|
// If there's a significant change, make sure the shadow updates
|
|
if ( modelchanged || (GetSequence() != m_nPrevSequence))
|
|
{
|
|
InvalidatePhysicsRecursive( ANIMATION_CHANGED );
|
|
m_nPrevSequence = GetSequence();
|
|
}
|
|
|
|
// Only need to think if animating client side
|
|
if ( m_bClientSideAnimation )
|
|
{
|
|
// Check to see if we should reset our frame
|
|
if ( m_bClientSideFrameReset != m_bLastClientSideFrameReset )
|
|
{
|
|
ResetClientsideFrame();
|
|
}
|
|
}
|
|
// build a ragdoll if necessary
|
|
if ( m_nRenderFX == kRenderFxRagdoll && !m_builtRagdoll )
|
|
{
|
|
BecomeRagdollOnClient();
|
|
}
|
|
|
|
//HACKHACK!!!
|
|
if ( m_nRenderFX == kRenderFxRagdoll && m_builtRagdoll == true )
|
|
{
|
|
if ( m_pRagdoll == NULL )
|
|
AddEffects( EF_NODRAW );
|
|
}
|
|
|
|
if ( m_pRagdoll && m_nRenderFX != kRenderFxRagdoll )
|
|
{
|
|
ClearRagdoll();
|
|
}
|
|
|
|
// If ragdolling and get EF_NOINTERP, we probably were dead and are now respawning,
|
|
// don't do blend out of ragdoll at respawn spot.
|
|
if ( IsNoInterpolationFrame() &&
|
|
m_pRagdollInfo &&
|
|
m_pRagdollInfo->m_bActive )
|
|
{
|
|
Msg( "delete ragdoll due to nointerp\n" );
|
|
// Remove ragdoll info
|
|
delete m_pRagdollInfo;
|
|
m_pRagdollInfo = NULL;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::AddEntity( void )
|
|
{
|
|
// Server says don't interpolate this frame, so set previous info to new info.
|
|
if ( IsNoInterpolationFrame() )
|
|
{
|
|
ResetLatched();
|
|
}
|
|
|
|
BaseClass::AddEntity();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Get the index of the attachment point with the specified name
|
|
//-----------------------------------------------------------------------------
|
|
int C_BaseAnimating::LookupAttachment( const char *pAttachmentName )
|
|
{
|
|
CStudioHdr *hdr = GetModelPtr();
|
|
if ( !hdr )
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
// NOTE: Currently, the network uses 0 to mean "no attachment"
|
|
// thus the client must add one to the index of the attachment
|
|
// UNDONE: Make the server do this too to be consistent.
|
|
return Studio_FindAttachment( hdr, pAttachmentName ) + 1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Get a random index of an attachment point with the specified substring in its name
|
|
//-----------------------------------------------------------------------------
|
|
int C_BaseAnimating::LookupRandomAttachment( const char *pAttachmentNameSubstring )
|
|
{
|
|
CStudioHdr *hdr = GetModelPtr();
|
|
if ( !hdr )
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
// NOTE: Currently, the network uses 0 to mean "no attachment"
|
|
// thus the client must add one to the index of the attachment
|
|
// UNDONE: Make the server do this too to be consistent.
|
|
return Studio_FindRandomAttachment( hdr, pAttachmentNameSubstring ) + 1;
|
|
}
|
|
|
|
|
|
void C_BaseAnimating::ClientSideAnimationChanged()
|
|
{
|
|
if ( !m_bClientSideAnimation || m_ClientSideAnimationListHandle == INVALID_CLIENTSIDEANIMATION_LIST_HANDLE )
|
|
return;
|
|
|
|
MDLCACHE_CRITICAL_SECTION();
|
|
|
|
clientanimating_t &anim = g_ClientSideAnimationList.Element(m_ClientSideAnimationListHandle);
|
|
Assert(anim.pAnimating == this);
|
|
anim.flags = ComputeClientSideAnimationFlags();
|
|
|
|
m_SequenceTransitioner.CheckForSequenceChange(
|
|
GetModelPtr(),
|
|
GetSequence(),
|
|
m_nNewSequenceParity != m_nPrevNewSequenceParity,
|
|
!IsNoInterpolationFrame()
|
|
);
|
|
}
|
|
|
|
unsigned int C_BaseAnimating::ComputeClientSideAnimationFlags()
|
|
{
|
|
return FCLIENTANIM_SEQUENCE_CYCLE;
|
|
}
|
|
|
|
void C_BaseAnimating::UpdateClientSideAnimation()
|
|
{
|
|
// Update client side animation
|
|
if ( m_bClientSideAnimation )
|
|
{
|
|
Assert( m_ClientSideAnimationListHandle != INVALID_CLIENTSIDEANIMATION_LIST_HANDLE );
|
|
if ( GetSequence() != -1 )
|
|
{
|
|
// latch old values
|
|
OnLatchInterpolatedVariables( LATCH_ANIMATION_VAR );
|
|
// move frame forward
|
|
FrameAdvance( 0.0f ); // 0 means to use the time we last advanced instead of a constant
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Assert( m_ClientSideAnimationListHandle == INVALID_CLIENTSIDEANIMATION_LIST_HANDLE );
|
|
}
|
|
}
|
|
|
|
|
|
void C_BaseAnimating::Simulate()
|
|
{
|
|
if ( m_bInitModelEffects )
|
|
{
|
|
DelayedInitModelEffects();
|
|
}
|
|
|
|
if ( gpGlobals->frametime != 0.0f )
|
|
{
|
|
DoAnimationEvents( GetModelPtr() );
|
|
}
|
|
BaseClass::Simulate();
|
|
if ( IsNoInterpolationFrame() )
|
|
{
|
|
ResetLatched();
|
|
}
|
|
if ( GetSequence() != -1 && m_pRagdoll && ( m_nRenderFX != kRenderFxRagdoll ) )
|
|
{
|
|
ClearRagdoll();
|
|
}
|
|
}
|
|
|
|
|
|
bool C_BaseAnimating::TestCollision( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr )
|
|
{
|
|
if ( ray.m_IsRay && IsSolidFlagSet( FSOLID_CUSTOMRAYTEST ))
|
|
{
|
|
if (!TestHitboxes( ray, fContentsMask, tr ))
|
|
return true;
|
|
|
|
return tr.DidHit();
|
|
}
|
|
|
|
if ( !ray.m_IsRay && IsSolidFlagSet( FSOLID_CUSTOMBOXTEST ))
|
|
{
|
|
if (!TestHitboxes( ray, fContentsMask, tr ))
|
|
return true;
|
|
|
|
return true;
|
|
}
|
|
|
|
// We shouldn't get here.
|
|
Assert(0);
|
|
return false;
|
|
}
|
|
|
|
|
|
// UNDONE: This almost works. The client entities have no control over their solid box
|
|
// Also they have no ability to expose FSOLID_ flags to the engine to force the accurate
|
|
// collision tests.
|
|
// Add those and the client hitboxes will be robust
|
|
bool C_BaseAnimating::TestHitboxes( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr )
|
|
{
|
|
VPROF( "C_BaseAnimating::TestHitboxes" );
|
|
|
|
MDLCACHE_CRITICAL_SECTION();
|
|
|
|
CStudioHdr *pStudioHdr = GetModelPtr();
|
|
if (!pStudioHdr)
|
|
return false;
|
|
|
|
mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( m_nHitboxSet );
|
|
if ( !set || !set->numhitboxes )
|
|
return false;
|
|
|
|
// Use vcollide for box traces.
|
|
if ( !ray.m_IsRay )
|
|
return false;
|
|
|
|
// This *has* to be true for the existing code to function correctly.
|
|
Assert( ray.m_StartOffset == vec3_origin );
|
|
|
|
CBoneCache *pCache = GetBoneCache( pStudioHdr );
|
|
matrix3x4_t *hitboxbones[MAXSTUDIOBONES];
|
|
pCache->ReadCachedBonePointers( hitboxbones, pStudioHdr->numbones() );
|
|
|
|
if ( TraceToStudio( physprops, ray, pStudioHdr, set, hitboxbones, fContentsMask, GetRenderOrigin(), GetModelScale(), tr ) )
|
|
{
|
|
mstudiobbox_t *pbox = set->pHitbox( tr.hitbox );
|
|
mstudiobone_t *pBone = pStudioHdr->pBone(pbox->bone);
|
|
tr.surface.name = "**studio**";
|
|
tr.surface.flags = SURF_HITBOX;
|
|
tr.surface.surfaceProps = physprops->GetSurfaceIndex( pBone->pszSurfaceProp() );
|
|
if ( IsRagdoll() )
|
|
{
|
|
IPhysicsObject *pReplace = m_pRagdoll->GetElement( tr.physicsbone );
|
|
if ( pReplace )
|
|
{
|
|
VPhysicsSetObject( NULL );
|
|
VPhysicsSetObject( pReplace );
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Check sequence framerate
|
|
// Input : iSequence -
|
|
// Output : float
|
|
//-----------------------------------------------------------------------------
|
|
float C_BaseAnimating::GetSequenceCycleRate( CStudioHdr *pStudioHdr, int iSequence )
|
|
{
|
|
if ( !pStudioHdr )
|
|
return 0.0f;
|
|
|
|
return Studio_CPS( pStudioHdr, pStudioHdr->pSeqdesc(iSequence), iSequence, m_flPoseParameter );
|
|
}
|
|
|
|
float C_BaseAnimating::GetAnimTimeInterval( void ) const
|
|
{
|
|
#define MAX_ANIMTIME_INTERVAL 0.2f
|
|
|
|
float flInterval;
|
|
if (m_flAnimTime < gpGlobals->curtime)
|
|
{
|
|
// estimate what it'll be this frame
|
|
flInterval = clamp( gpGlobals->curtime - m_flAnimTime, 0.f, MAX_ANIMTIME_INTERVAL );
|
|
}
|
|
else
|
|
{
|
|
// report actual
|
|
flInterval = clamp( m_flAnimTime - m_flOldAnimTime, 0.f, MAX_ANIMTIME_INTERVAL );
|
|
}
|
|
return flInterval;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Sets the cycle, marks the entity as being dirty
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::SetCycle( float flCycle )
|
|
{
|
|
if ( m_flCycle != flCycle )
|
|
{
|
|
m_flCycle = flCycle;
|
|
InvalidatePhysicsRecursive( ANIMATION_CHANGED );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Sets the sequence, marks the entity as being dirty
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::SetSequence( int nSequence )
|
|
{
|
|
if ( m_nSequence != nSequence )
|
|
{
|
|
/*
|
|
CStudioHdr *hdr = GetModelPtr();
|
|
// Assert( hdr );
|
|
if ( hdr )
|
|
{
|
|
Assert( nSequence >= 0 && nSequence < hdr->GetNumSeq() );
|
|
}
|
|
*/
|
|
|
|
m_nSequence = nSequence;
|
|
InvalidatePhysicsRecursive( ANIMATION_CHANGED );
|
|
if ( m_bClientSideAnimation )
|
|
{
|
|
ClientSideAnimationChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
float C_BaseAnimating::GetLastVisibleCycle( CStudioHdr *pStudioHdr, int iSequence )
|
|
{
|
|
if ( !pStudioHdr )
|
|
{
|
|
DevWarning( 2, "C_BaseAnimating::LastVisibleCycle( %d ) NULL pstudiohdr on %s!\n", iSequence, GetClassname() );
|
|
return 1.0;
|
|
}
|
|
|
|
if (!(GetSequenceFlags( pStudioHdr, iSequence ) & STUDIO_LOOPING))
|
|
{
|
|
return 1.0f - (pStudioHdr->pSeqdesc( iSequence ).fadeouttime) * GetSequenceCycleRate( pStudioHdr, iSequence ) * m_flPlaybackRate;
|
|
}
|
|
else
|
|
{
|
|
return 1.0;
|
|
}
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
// StudioFrameAdvance - advance the animation frame up some interval (default 0.1) into the future
|
|
//=========================================================
|
|
void C_BaseAnimating::StudioFrameAdvance()
|
|
{
|
|
if ( m_bClientSideAnimation )
|
|
return;
|
|
|
|
CStudioHdr *hdr = GetModelPtr();
|
|
if ( !hdr )
|
|
return;
|
|
|
|
#ifdef DEBUG
|
|
bool watch = dbganimmodel.GetString()[0] && V_stristr( hdr->pszName(), dbganimmodel.GetString() );
|
|
#else
|
|
bool watch = false; // Q_strstr( hdr->name, "rifle" ) ? true : false;
|
|
#endif
|
|
|
|
//if (!anim.prevanimtime)
|
|
//{
|
|
//anim.prevanimtime = m_flAnimTime = gpGlobals->curtime;
|
|
//}
|
|
|
|
// How long since last animtime
|
|
float flInterval = GetAnimTimeInterval();
|
|
|
|
if (flInterval <= 0.001)
|
|
{
|
|
// Msg("%s : %s : %5.3f (skip)\n", STRING(pev->classname), GetSequenceName( GetSequence() ), GetCycle() );
|
|
return;
|
|
}
|
|
|
|
UpdateModelScale();
|
|
|
|
//anim.prevanimtime = m_flAnimTime;
|
|
float cycleAdvance = flInterval * GetSequenceCycleRate( hdr, GetSequence() ) * m_flPlaybackRate;
|
|
float flNewCycle = GetCycle() + cycleAdvance;
|
|
m_flAnimTime = gpGlobals->curtime;
|
|
|
|
if ( watch )
|
|
{
|
|
Msg("%s %6.3f : %6.3f (%.3f)\n", GetClassname(), gpGlobals->curtime, m_flAnimTime, flInterval );
|
|
}
|
|
|
|
if ( flNewCycle < 0.0f || flNewCycle >= 1.0f )
|
|
{
|
|
if ( IsSequenceLooping( hdr, GetSequence() ) )
|
|
{
|
|
flNewCycle -= (int)(flNewCycle);
|
|
}
|
|
else
|
|
{
|
|
flNewCycle = (flNewCycle < 0.0f) ? 0.0f : 1.0f;
|
|
}
|
|
|
|
m_bSequenceFinished = true; // just in case it wasn't caught in GetEvents
|
|
}
|
|
|
|
SetCycle( flNewCycle );
|
|
|
|
m_flGroundSpeed = GetSequenceGroundSpeed( hdr, GetSequence() ) * GetModelScale();
|
|
|
|
InvalidatePhysicsRecursive( ANIMATION_CHANGED );
|
|
|
|
InvalidateBoneCacheIfOlderThan( 0 );
|
|
|
|
if ( watch )
|
|
{
|
|
Msg("%s : %s : %5.1f\n", GetClassname(), GetSequenceName( GetSequence() ), GetCycle() );
|
|
}
|
|
}
|
|
|
|
float C_BaseAnimating::GetSequenceGroundSpeed( CStudioHdr *pStudioHdr, int iSequence )
|
|
{
|
|
float t = SequenceDuration( pStudioHdr, iSequence );
|
|
|
|
if (t > 0)
|
|
{
|
|
return GetSequenceMoveDist( pStudioHdr, iSequence ) / t;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//
|
|
// Input : iSequence -
|
|
//
|
|
// Output : float
|
|
//-----------------------------------------------------------------------------
|
|
float C_BaseAnimating::GetSequenceMoveDist( CStudioHdr *pStudioHdr, int iSequence )
|
|
{
|
|
Vector vecReturn;
|
|
|
|
::GetSequenceLinearMotion( pStudioHdr, iSequence, m_flPoseParameter, &vecReturn );
|
|
|
|
return vecReturn.Length();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//
|
|
// Input : iSequence -
|
|
// *pVec -
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::GetSequenceLinearMotion( int iSequence, Vector *pVec )
|
|
{
|
|
::GetSequenceLinearMotion( GetModelPtr(), iSequence, m_flPoseParameter, pVec );
|
|
}
|
|
|
|
void C_BaseAnimating::GetBlendedLinearVelocity( Vector *pVec )
|
|
{
|
|
Vector vecDist;
|
|
float flDuration;
|
|
|
|
GetSequenceLinearMotion( GetSequence(), &vecDist );
|
|
flDuration = SequenceDuration( GetSequence() );
|
|
|
|
VectorScale( vecDist, 1.0 / flDuration, *pVec );
|
|
|
|
Vector tmp;
|
|
for (int i = m_SequenceTransitioner.m_animationQueue.Count() - 2; i >= 0; i--)
|
|
{
|
|
C_AnimationLayer *blend = &m_SequenceTransitioner.m_animationQueue[i];
|
|
|
|
GetSequenceLinearMotion( blend->m_nSequence, &vecDist );
|
|
flDuration = SequenceDuration( blend->m_nSequence );
|
|
|
|
VectorScale( vecDist, 1.0 / flDuration, tmp );
|
|
|
|
float flWeight = blend->GetFadeout( gpGlobals->curtime );
|
|
*pVec = Lerp( flWeight, *pVec, tmp );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : flInterval -
|
|
// Output : float
|
|
//-----------------------------------------------------------------------------
|
|
float C_BaseAnimating::FrameAdvance( float flInterval )
|
|
{
|
|
CStudioHdr *hdr = GetModelPtr();
|
|
if ( !hdr || !hdr->SequencesAvailable() )
|
|
return 0.0f;
|
|
|
|
#ifdef DEBUG
|
|
bool bWatch = dbganimmodel.GetString()[0] && V_stristr( hdr->pszName(), dbganimmodel.GetString() );
|
|
#else
|
|
bool bWatch = false; // Q_strstr( hdr->name, "medkit_large" ) ? true : false;
|
|
#endif
|
|
|
|
UpdateModelScale();
|
|
|
|
if ( !m_flOldAnimTime )
|
|
{
|
|
m_flOldAnimTime = m_flAnimTime;
|
|
}
|
|
|
|
if (flInterval == 0.0f)
|
|
{
|
|
flInterval = GetAnimTimeInterval();
|
|
if (flInterval <= 0.001f)
|
|
{
|
|
return 0.0f;
|
|
}
|
|
}
|
|
|
|
// Set current
|
|
m_flAnimTime = gpGlobals->curtime;
|
|
|
|
// Latch prev
|
|
m_flOldAnimTime = m_flAnimTime - flInterval;
|
|
|
|
float flCycleRate = GetSequenceCycleRate( hdr, GetSequence() );
|
|
float flAddCycle = flInterval * flCycleRate * m_flPlaybackRate;
|
|
|
|
float flNewCycle = GetCycle() + flAddCycle;
|
|
|
|
if ( bWatch )
|
|
{
|
|
Msg("%i CLIENT Time: %6.3f : (Interval %f) : cycle %f rate %f add %f\n",
|
|
gpGlobals->tickcount, gpGlobals->curtime, flInterval, flNewCycle, flCycleRate, flAddCycle );
|
|
}
|
|
|
|
StudioFrameAdvanceInternal( hdr, flAddCycle );
|
|
|
|
return flInterval;
|
|
}
|
|
|
|
// Stubs for weapon prediction
|
|
void C_BaseAnimating::ResetSequenceInfo( void )
|
|
{
|
|
if (GetSequence() == -1)
|
|
{
|
|
SetSequence( 0 );
|
|
}
|
|
|
|
if ( IsDynamicModelLoading() )
|
|
{
|
|
m_bResetSequenceInfoOnLoad = true;
|
|
return;
|
|
}
|
|
|
|
CStudioHdr *pStudioHdr = GetModelPtr();
|
|
m_flGroundSpeed = GetSequenceGroundSpeed( pStudioHdr, GetSequence() ) * GetModelScale();
|
|
m_bSequenceLoops = ((GetSequenceFlags( pStudioHdr, GetSequence() ) & STUDIO_LOOPING) != 0);
|
|
// m_flAnimTime = gpGlobals->time;
|
|
m_flPlaybackRate = 1.0;
|
|
m_bSequenceFinished = false;
|
|
m_flLastEventCheck = 0;
|
|
|
|
m_nNewSequenceParity = ( m_nNewSequenceParity + 1 ) & EF_PARITY_MASK;
|
|
m_nResetEventsParity = ( m_nResetEventsParity + 1 ) & EF_PARITY_MASK;
|
|
|
|
// FIXME: why is this called here? Nothing should have changed to make this nessesary
|
|
SetEventIndexForSequence( pStudioHdr->pSeqdesc( GetSequence() ) );
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
|
|
bool C_BaseAnimating::IsSequenceLooping( CStudioHdr *pStudioHdr, int iSequence )
|
|
{
|
|
return (::GetSequenceFlags( pStudioHdr, iSequence ) & STUDIO_LOOPING) != 0;
|
|
}
|
|
|
|
float C_BaseAnimating::SequenceDuration( CStudioHdr *pStudioHdr, int iSequence )
|
|
{
|
|
if ( !pStudioHdr )
|
|
{
|
|
return 0.1f;
|
|
}
|
|
|
|
if (iSequence >= pStudioHdr->GetNumSeq() || iSequence < 0 )
|
|
{
|
|
DevWarning( 2, "C_BaseAnimating::SequenceDuration( %d ) out of range\n", iSequence );
|
|
return 0.1;
|
|
}
|
|
|
|
return Studio_Duration( pStudioHdr, iSequence, m_flPoseParameter );
|
|
|
|
}
|
|
|
|
int C_BaseAnimating::FindTransitionSequence( int iCurrentSequence, int iGoalSequence, int *piDir )
|
|
{
|
|
CStudioHdr *hdr = GetModelPtr();
|
|
if ( !hdr )
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if (piDir == NULL)
|
|
{
|
|
int iDir = 1;
|
|
int sequence = ::FindTransitionSequence( hdr, iCurrentSequence, iGoalSequence, &iDir );
|
|
if (iDir != 1)
|
|
return -1;
|
|
else
|
|
return sequence;
|
|
}
|
|
|
|
return ::FindTransitionSequence( hdr, iCurrentSequence, iGoalSequence, piDir );
|
|
|
|
}
|
|
|
|
void C_BaseAnimating::SetBodygroup( int iGroup, int iValue )
|
|
{
|
|
// SetBodygroup is not supported on pending dynamic models. Wait for it to load!
|
|
// XXX TODO we could buffer up the group and value if we really needed to. -henryg
|
|
Assert( GetModelPtr() );
|
|
::SetBodygroup( GetModelPtr( ), m_nBody, iGroup, iValue );
|
|
}
|
|
|
|
int C_BaseAnimating::GetBodygroup( int iGroup )
|
|
{
|
|
Assert( IsDynamicModelLoading() || GetModelPtr() );
|
|
return IsDynamicModelLoading() ? 0 : ::GetBodygroup( GetModelPtr( ), m_nBody, iGroup );
|
|
}
|
|
|
|
const char *C_BaseAnimating::GetBodygroupName( int iGroup )
|
|
{
|
|
Assert( IsDynamicModelLoading() || GetModelPtr() );
|
|
return IsDynamicModelLoading() ? "" : ::GetBodygroupName( GetModelPtr( ), iGroup );
|
|
}
|
|
|
|
int C_BaseAnimating::FindBodygroupByName( const char *name )
|
|
{
|
|
Assert( IsDynamicModelLoading() || GetModelPtr() );
|
|
return IsDynamicModelLoading() ? -1 : ::FindBodygroupByName( GetModelPtr( ), name );
|
|
}
|
|
|
|
int C_BaseAnimating::GetBodygroupCount( int iGroup )
|
|
{
|
|
Assert( IsDynamicModelLoading() || GetModelPtr() );
|
|
return IsDynamicModelLoading() ? 0 : ::GetBodygroupCount( GetModelPtr( ), iGroup );
|
|
}
|
|
|
|
int C_BaseAnimating::GetNumBodyGroups( void )
|
|
{
|
|
Assert( IsDynamicModelLoading() || GetModelPtr() );
|
|
return IsDynamicModelLoading() ? 0 : ::GetNumBodyGroups( GetModelPtr( ) );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : setnum -
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::SetHitboxSet( int setnum )
|
|
{
|
|
if ( IsDynamicModelLoading() )
|
|
return;
|
|
|
|
#ifdef _DEBUG
|
|
CStudioHdr *pStudioHdr = GetModelPtr();
|
|
if ( !pStudioHdr )
|
|
return;
|
|
|
|
if (setnum > pStudioHdr->numhitboxsets())
|
|
{
|
|
// Warn if an bogus hitbox set is being used....
|
|
static bool s_bWarned = false;
|
|
if (!s_bWarned)
|
|
{
|
|
Warning("Using bogus hitbox set in entity %s!\n", GetClassname() );
|
|
s_bWarned = true;
|
|
}
|
|
setnum = 0;
|
|
}
|
|
#endif
|
|
|
|
m_nHitboxSet = setnum;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *setname -
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::SetHitboxSetByName( const char *setname )
|
|
{
|
|
if ( IsDynamicModelLoading() )
|
|
return;
|
|
|
|
m_nHitboxSet = FindHitboxSetByName( GetModelPtr(), setname );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : int
|
|
//-----------------------------------------------------------------------------
|
|
int C_BaseAnimating::GetHitboxSet( void )
|
|
{
|
|
return m_nHitboxSet;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : char const
|
|
//-----------------------------------------------------------------------------
|
|
const char *C_BaseAnimating::GetHitboxSetName( void )
|
|
{
|
|
if ( IsDynamicModelLoading() )
|
|
return "";
|
|
|
|
return ::GetHitboxSetName( GetModelPtr(), m_nHitboxSet );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : int
|
|
//-----------------------------------------------------------------------------
|
|
int C_BaseAnimating::GetHitboxSetCount( void )
|
|
{
|
|
if ( IsDynamicModelLoading() )
|
|
return 0;
|
|
|
|
return ::GetHitboxSetCount( GetModelPtr() );
|
|
}
|
|
|
|
static Vector hullcolor[8] =
|
|
{
|
|
Vector( 1.0, 1.0, 1.0 ),
|
|
Vector( 1.0, 0.5, 0.5 ),
|
|
Vector( 0.5, 1.0, 0.5 ),
|
|
Vector( 1.0, 1.0, 0.5 ),
|
|
Vector( 0.5, 0.5, 1.0 ),
|
|
Vector( 1.0, 0.5, 1.0 ),
|
|
Vector( 0.5, 1.0, 1.0 ),
|
|
Vector( 1.0, 1.0, 1.0 )
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Draw the current hitboxes
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::DrawClientHitboxes( float duration /*= 0.0f*/, bool monocolor /*= false*/ )
|
|
{
|
|
CStudioHdr *pStudioHdr = GetModelPtr();
|
|
if ( !pStudioHdr )
|
|
return;
|
|
|
|
mstudiohitboxset_t *set =pStudioHdr->pHitboxSet( m_nHitboxSet );
|
|
if ( !set )
|
|
return;
|
|
|
|
Vector position;
|
|
QAngle angles;
|
|
|
|
int r = 255;
|
|
int g = 0;
|
|
int b = 0;
|
|
|
|
for ( int i = 0; i < set->numhitboxes; i++ )
|
|
{
|
|
mstudiobbox_t *pbox = set->pHitbox( i );
|
|
|
|
GetBonePosition( pbox->bone, position, angles );
|
|
|
|
if ( !monocolor )
|
|
{
|
|
int j = (pbox->group % 8);
|
|
r = ( int ) ( 255.0f * hullcolor[j][0] );
|
|
g = ( int ) ( 255.0f * hullcolor[j][1] );
|
|
b = ( int ) ( 255.0f * hullcolor[j][2] );
|
|
}
|
|
|
|
debugoverlay->AddBoxOverlay( position, pbox->bbmin, pbox->bbmax, angles, r, g, b, 0 ,duration );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : activity -
|
|
// Output : int C_BaseAnimating::SelectWeightedSequence
|
|
//-----------------------------------------------------------------------------
|
|
int C_BaseAnimating::SelectWeightedSequence ( int activity )
|
|
{
|
|
Assert( activity != ACT_INVALID );
|
|
|
|
return ::SelectWeightedSequence( GetModelPtr(), activity );
|
|
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
int C_BaseAnimating::LookupPoseParameter( CStudioHdr *pstudiohdr, const char *szName )
|
|
{
|
|
if ( !pstudiohdr )
|
|
return 0;
|
|
|
|
for (int i = 0; i < pstudiohdr->GetNumPoseParameters(); i++)
|
|
{
|
|
if (stricmp( pstudiohdr->pPoseParameter( i ).pszName(), szName ) == 0)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
// AssertMsg( 0, UTIL_VarArgs( "poseparameter %s couldn't be mapped!!!\n", szName ) );
|
|
return -1; // Error
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
float C_BaseAnimating::SetPoseParameter( CStudioHdr *pStudioHdr, const char *szName, float flValue )
|
|
{
|
|
return SetPoseParameter( pStudioHdr, LookupPoseParameter( pStudioHdr, szName ), flValue );
|
|
}
|
|
|
|
float C_BaseAnimating::SetPoseParameter( CStudioHdr *pStudioHdr, int iParameter, float flValue )
|
|
{
|
|
if ( !pStudioHdr )
|
|
{
|
|
Assert(!"C_BaseAnimating::SetPoseParameter: model missing");
|
|
return flValue;
|
|
}
|
|
|
|
if (iParameter >= 0)
|
|
{
|
|
float flNewValue;
|
|
flValue = Studio_SetPoseParameter( pStudioHdr, iParameter, flValue, flNewValue );
|
|
m_flPoseParameter[ iParameter ] = flNewValue;
|
|
}
|
|
|
|
return flValue;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *label -
|
|
// Output : int
|
|
//-----------------------------------------------------------------------------
|
|
int C_BaseAnimating::LookupSequence( const char *label )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
return ::LookupSequence( GetModelPtr(), label );
|
|
}
|
|
|
|
void C_BaseAnimating::Release()
|
|
{
|
|
ClearRagdoll();
|
|
BaseClass::Release();
|
|
}
|
|
|
|
void C_BaseAnimating::Clear( void )
|
|
{
|
|
InvalidateMdlCache();
|
|
Q_memset(&m_mouth, 0, sizeof(m_mouth));
|
|
m_flCycle = 0;
|
|
m_flOldCycle = 0;
|
|
m_bResetSequenceInfoOnLoad = false;
|
|
m_bDynamicModelPending = false;
|
|
m_AutoRefModelIndex.Clear();
|
|
BaseClass::Clear();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Clear current ragdoll
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::ClearRagdoll()
|
|
{
|
|
if ( m_pRagdoll )
|
|
{
|
|
// immediately mark the member ragdoll as being NULL,
|
|
// so that we have no reentrancy problems with the delete
|
|
// (such as the disappearance of the ragdoll physics waking up
|
|
// IVP which causes other objects to move and have a touch
|
|
// callback on the ragdoll entity, which was a crash on TF)
|
|
// That is to say: it is vital that the member be cleared out
|
|
// BEFORE the delete occurs.
|
|
CRagdoll * RESTRICT pDoomed = m_pRagdoll;
|
|
m_pRagdoll = NULL;
|
|
|
|
delete pDoomed;
|
|
|
|
// 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 );
|
|
|
|
// If we have ragdoll mins/maxs, we've just come out of ragdoll, so restore them
|
|
if ( m_vecPreRagdollMins != vec3_origin || m_vecPreRagdollMaxs != vec3_origin )
|
|
{
|
|
SetCollisionBounds( m_vecPreRagdollMins, m_vecPreRagdollMaxs );
|
|
}
|
|
|
|
#if defined( REPLAY_ENABLED )
|
|
// Delete entry from ragdoll recorder if Replay is enabled on server
|
|
ConVar* pReplayEnable = (ConVar*)cvar->FindVar( "replay_enable" );
|
|
if ( pReplayEnable && pReplayEnable->GetInt() && !engine->IsPlayingDemo() && !engine->IsPlayingTimeDemo() )
|
|
{
|
|
CReplayRagdollRecorder& RagdollRecorder = CReplayRagdollRecorder::Instance();
|
|
RagdollRecorder.StopRecordingRagdoll( this );
|
|
}
|
|
#endif
|
|
}
|
|
m_builtRagdoll = false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Looks up an activity by name.
|
|
// Input : label - Name of the activity, ie "ACT_IDLE".
|
|
// Output : Returns the activity ID or ACT_INVALID.
|
|
//-----------------------------------------------------------------------------
|
|
int C_BaseAnimating::LookupActivity( const char *label )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
return ::LookupActivity( GetModelPtr(), label );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//
|
|
// Input : iSequence -
|
|
//
|
|
// Output : char
|
|
//-----------------------------------------------------------------------------
|
|
const char *C_BaseAnimating::GetSequenceActivityName( int iSequence )
|
|
{
|
|
if( iSequence == -1 )
|
|
{
|
|
return "Not Found!";
|
|
}
|
|
|
|
if ( !GetModelPtr() )
|
|
return "No model!";
|
|
|
|
return ::GetSequenceActivityName( GetModelPtr(), iSequence );
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
float C_BaseAnimating::SetBoneController ( int iController, float flValue )
|
|
{
|
|
Assert( GetModelPtr() );
|
|
|
|
CStudioHdr *pmodel = GetModelPtr();
|
|
|
|
Assert(iController >= 0 && iController < NUM_BONECTRLS);
|
|
|
|
float controller = m_flEncodedController[iController];
|
|
float retVal = Studio_SetController( pmodel, iController, flValue, controller );
|
|
m_flEncodedController[iController] = controller;
|
|
return retVal;
|
|
}
|
|
|
|
|
|
void C_BaseAnimating::GetAimEntOrigin( IClientEntity *pAttachedTo, Vector *pAbsOrigin, QAngle *pAbsAngles )
|
|
{
|
|
CBaseEntity *pMoveParent;
|
|
if ( IsEffectActive( EF_BONEMERGE ) && IsEffectActive( EF_BONEMERGE_FASTCULL ) && (pMoveParent = GetMoveParent()) != NULL )
|
|
{
|
|
// Doing this saves a lot of CPU.
|
|
*pAbsOrigin = pMoveParent->WorldSpaceCenter();
|
|
*pAbsAngles = pMoveParent->GetRenderAngles();
|
|
}
|
|
else
|
|
{
|
|
if ( !m_pBoneMergeCache || !m_pBoneMergeCache->GetAimEntOrigin( pAbsOrigin, pAbsAngles ) )
|
|
BaseClass::GetAimEntOrigin( pAttachedTo, pAbsOrigin, pAbsAngles );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//
|
|
// Input : iSequence -
|
|
//
|
|
// Output : char
|
|
//-----------------------------------------------------------------------------
|
|
const char *C_BaseAnimating::GetSequenceName( int iSequence )
|
|
{
|
|
if( iSequence == -1 )
|
|
{
|
|
return "Not Found!";
|
|
}
|
|
|
|
if ( !GetModelPtr() )
|
|
return "No model!";
|
|
|
|
return ::GetSequenceName( GetModelPtr(), iSequence );
|
|
}
|
|
|
|
Activity C_BaseAnimating::GetSequenceActivity( int iSequence )
|
|
{
|
|
if( iSequence == -1 )
|
|
{
|
|
return ACT_INVALID;
|
|
}
|
|
|
|
if ( !GetModelPtr() )
|
|
return ACT_INVALID;
|
|
|
|
return (Activity)::GetSequenceActivity( GetModelPtr(), iSequence );
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// returns the sequence keyvalue text as a KeyValues pointer
|
|
//-----------------------------------------------------------------------------
|
|
KeyValues *C_BaseAnimating::GetSequenceKeyValues( int iSequence )
|
|
{
|
|
const char *szText = Studio_GetKeyValueText( GetModelPtr(), iSequence );
|
|
|
|
if (szText)
|
|
{
|
|
KeyValues *seqKeyValues = new KeyValues("");
|
|
if ( seqKeyValues->LoadFromBuffer( modelinfo->GetModelName( GetModel() ), szText ) )
|
|
{
|
|
return seqKeyValues;
|
|
}
|
|
seqKeyValues->deleteThis();
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Computes a box that surrounds all hitboxes
|
|
//-----------------------------------------------------------------------------
|
|
bool C_BaseAnimating::ComputeHitboxSurroundingBox( Vector *pVecWorldMins, Vector *pVecWorldMaxs )
|
|
{
|
|
// Note that this currently should not be called during position recomputation because of IK.
|
|
// The code below recomputes bones so as to get at the hitboxes,
|
|
// which causes IK to trigger, which causes raycasts against the other entities to occur,
|
|
// which is illegal to do while in the computeabsposition phase.
|
|
|
|
CStudioHdr *pStudioHdr = GetModelPtr();
|
|
if (!pStudioHdr)
|
|
return false;
|
|
|
|
mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( m_nHitboxSet );
|
|
if ( !set || !set->numhitboxes )
|
|
return false;
|
|
|
|
CBoneCache *pCache = GetBoneCache( pStudioHdr );
|
|
matrix3x4_t *hitboxbones[MAXSTUDIOBONES];
|
|
pCache->ReadCachedBonePointers( hitboxbones, pStudioHdr->numbones() );
|
|
|
|
// Compute a box in world space that surrounds this entity
|
|
pVecWorldMins->Init( FLT_MAX, FLT_MAX, FLT_MAX );
|
|
pVecWorldMaxs->Init( -FLT_MAX, -FLT_MAX, -FLT_MAX );
|
|
|
|
Vector vecBoxAbsMins, vecBoxAbsMaxs;
|
|
for ( int i = 0; i < set->numhitboxes; i++ )
|
|
{
|
|
mstudiobbox_t *pbox = set->pHitbox(i);
|
|
|
|
TransformAABB( *hitboxbones[pbox->bone], pbox->bbmin, pbox->bbmax, vecBoxAbsMins, vecBoxAbsMaxs );
|
|
VectorMin( *pVecWorldMins, vecBoxAbsMins, *pVecWorldMins );
|
|
VectorMax( *pVecWorldMaxs, vecBoxAbsMaxs, *pVecWorldMaxs );
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Computes a box that surrounds all hitboxes, in entity space
|
|
//-----------------------------------------------------------------------------
|
|
bool C_BaseAnimating::ComputeEntitySpaceHitboxSurroundingBox( Vector *pVecWorldMins, Vector *pVecWorldMaxs )
|
|
{
|
|
// Note that this currently should not be called during position recomputation because of IK.
|
|
// The code below recomputes bones so as to get at the hitboxes,
|
|
// which causes IK to trigger, which causes raycasts against the other entities to occur,
|
|
// which is illegal to do while in the computeabsposition phase.
|
|
|
|
CStudioHdr *pStudioHdr = GetModelPtr();
|
|
if (!pStudioHdr)
|
|
return false;
|
|
|
|
mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( m_nHitboxSet );
|
|
if ( !set || !set->numhitboxes )
|
|
return false;
|
|
|
|
CBoneCache *pCache = GetBoneCache( pStudioHdr );
|
|
matrix3x4_t *hitboxbones[MAXSTUDIOBONES];
|
|
pCache->ReadCachedBonePointers( hitboxbones, pStudioHdr->numbones() );
|
|
|
|
// Compute a box in world space that surrounds this entity
|
|
pVecWorldMins->Init( FLT_MAX, FLT_MAX, FLT_MAX );
|
|
pVecWorldMaxs->Init( -FLT_MAX, -FLT_MAX, -FLT_MAX );
|
|
|
|
matrix3x4_t worldToEntity, boneToEntity;
|
|
MatrixInvert( EntityToWorldTransform(), worldToEntity );
|
|
|
|
Vector vecBoxAbsMins, vecBoxAbsMaxs;
|
|
for ( int i = 0; i < set->numhitboxes; i++ )
|
|
{
|
|
mstudiobbox_t *pbox = set->pHitbox(i);
|
|
|
|
ConcatTransforms( worldToEntity, *hitboxbones[pbox->bone], boneToEntity );
|
|
TransformAABB( boneToEntity, pbox->bbmin, pbox->bbmax, vecBoxAbsMins, vecBoxAbsMaxs );
|
|
VectorMin( *pVecWorldMins, vecBoxAbsMins, *pVecWorldMins );
|
|
VectorMax( *pVecWorldMaxs, vecBoxAbsMaxs, *pVecWorldMaxs );
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : scale -
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::SetModelScale( float scale, float change_duration /*= 0.0f*/ )
|
|
{
|
|
if ( change_duration > 0.0f )
|
|
{
|
|
ModelScale *mvs = ( ModelScale * )CreateDataObject( MODELSCALE );
|
|
mvs->m_flModelScaleStart = m_flModelScale;
|
|
mvs->m_flModelScaleGoal = scale;
|
|
mvs->m_flModelScaleStartTime = gpGlobals->curtime;
|
|
mvs->m_flModelScaleFinishTime = mvs->m_flModelScaleStartTime + change_duration;
|
|
}
|
|
else
|
|
{
|
|
m_flModelScale = scale;
|
|
RefreshCollisionBounds();
|
|
|
|
if ( HasDataObjectType( MODELSCALE ) )
|
|
{
|
|
DestroyDataObject( MODELSCALE );
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::UpdateModelScale()
|
|
{
|
|
ModelScale *mvs = ( ModelScale * )GetDataObject( MODELSCALE );
|
|
if ( !mvs )
|
|
{
|
|
return;
|
|
}
|
|
|
|
float dt = mvs->m_flModelScaleFinishTime - mvs->m_flModelScaleStartTime;
|
|
Assert( dt > 0.0f );
|
|
|
|
float frac = ( gpGlobals->curtime - mvs->m_flModelScaleStartTime ) / dt;
|
|
frac = clamp( frac, 0.0f, 1.0f );
|
|
|
|
if ( gpGlobals->curtime >= mvs->m_flModelScaleFinishTime )
|
|
{
|
|
m_flModelScale = mvs->m_flModelScaleGoal;
|
|
DestroyDataObject( MODELSCALE );
|
|
}
|
|
else
|
|
{
|
|
m_flModelScale = Lerp( frac, mvs->m_flModelScaleStart, mvs->m_flModelScaleGoal );
|
|
}
|
|
|
|
RefreshCollisionBounds();
|
|
}
|
|
|
|
void C_BaseAnimating::RefreshCollisionBounds( void )
|
|
{
|
|
CollisionProp()->RefreshScaledCollisionBounds();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Clientside bone follower class. Used just to visualize them.
|
|
// Bone followers WON'T be sent to the client if VISUALIZE_FOLLOWERS is
|
|
// undefined in the server's physics_bone_followers.cpp
|
|
//-----------------------------------------------------------------------------
|
|
class C_BoneFollower : public C_BaseEntity
|
|
{
|
|
DECLARE_CLASS( C_BoneFollower, C_BaseEntity );
|
|
DECLARE_CLIENTCLASS();
|
|
public:
|
|
C_BoneFollower( void )
|
|
{
|
|
}
|
|
|
|
bool ShouldDraw( void );
|
|
int DrawModel( int flags );
|
|
bool TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace );
|
|
|
|
private:
|
|
int m_modelIndex;
|
|
int m_solidIndex;
|
|
};
|
|
|
|
IMPLEMENT_CLIENTCLASS_DT( C_BoneFollower, DT_BoneFollower, CBoneFollower )
|
|
RecvPropInt( RECVINFO( m_modelIndex ) ),
|
|
RecvPropInt( RECVINFO( m_solidIndex ) ),
|
|
END_RECV_TABLE()
|
|
|
|
void VCollideWireframe_ChangeCallback( IConVar *pConVar, char const *pOldString, float flOldValue )
|
|
{
|
|
for ( C_BaseEntity *pEntity = ClientEntityList().FirstBaseEntity(); pEntity; pEntity = ClientEntityList().NextBaseEntity(pEntity) )
|
|
{
|
|
pEntity->UpdateVisibility();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns whether object should render.
|
|
//-----------------------------------------------------------------------------
|
|
bool C_BoneFollower::ShouldDraw( void )
|
|
{
|
|
return ( vcollide_wireframe.GetBool() ); //MOTODO
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
int C_BoneFollower::DrawModel( int flags )
|
|
{
|
|
vcollide_t *pCollide = modelinfo->GetVCollide( m_modelIndex );
|
|
if ( pCollide )
|
|
{
|
|
static color32 debugColor = {0,255,255,0};
|
|
matrix3x4_t matrix;
|
|
AngleMatrix( GetAbsAngles(), GetAbsOrigin(), matrix );
|
|
engine->DebugDrawPhysCollide( pCollide->solids[m_solidIndex], NULL, matrix, debugColor );
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
bool C_BoneFollower::TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace )
|
|
{
|
|
vcollide_t *pCollide = modelinfo->GetVCollide( m_modelIndex );
|
|
Assert( pCollide && pCollide->solidCount > m_solidIndex );
|
|
|
|
physcollision->TraceBox( ray, pCollide->solids[m_solidIndex], GetAbsOrigin(), GetAbsAngles(), &trace );
|
|
|
|
if ( trace.fraction >= 1 )
|
|
return false;
|
|
|
|
// return owner as trace hit
|
|
trace.m_pEnt = GetOwnerEntity();
|
|
trace.hitgroup = 0;//m_hitGroup;
|
|
trace.physicsbone = 0;//m_physicsBone; // UNDONE: Get physics bone index & hitgroup
|
|
return trace.DidHit();
|
|
}
|
|
|
|
|
|
void C_BaseAnimating::DisableMuzzleFlash()
|
|
{
|
|
m_nOldMuzzleFlashParity = m_nMuzzleFlashParity;
|
|
}
|
|
|
|
|
|
void C_BaseAnimating::DoMuzzleFlash()
|
|
{
|
|
m_nMuzzleFlashParity = (m_nMuzzleFlashParity+1) & ((1 << EF_MUZZLEFLASH_BITS) - 1);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void DevMsgRT( char const* pMsg, ... )
|
|
{
|
|
if (gpGlobals->frametime != 0.0f)
|
|
{
|
|
va_list argptr;
|
|
va_start( argptr, pMsg );
|
|
//
|
|
{
|
|
static char string[1024];
|
|
Q_vsnprintf (string, sizeof( string ), pMsg, argptr);
|
|
DevMsg( 1, "%s", string );
|
|
}
|
|
// DevMsg( pMsg, argptr );
|
|
va_end( argptr );
|
|
}
|
|
}
|
|
|
|
|
|
void C_BaseAnimating::ForceClientSideAnimationOn()
|
|
{
|
|
m_bClientSideAnimation = true;
|
|
AddToClientSideAnimationList();
|
|
}
|
|
|
|
|
|
void C_BaseAnimating::AddToClientSideAnimationList()
|
|
{
|
|
// Already in list
|
|
if ( m_ClientSideAnimationListHandle != INVALID_CLIENTSIDEANIMATION_LIST_HANDLE )
|
|
return;
|
|
|
|
clientanimating_t list( this, 0 );
|
|
m_ClientSideAnimationListHandle = g_ClientSideAnimationList.AddToTail( list );
|
|
ClientSideAnimationChanged();
|
|
}
|
|
|
|
void C_BaseAnimating::RemoveFromClientSideAnimationList()
|
|
{
|
|
// Not in list yet
|
|
if ( INVALID_CLIENTSIDEANIMATION_LIST_HANDLE == m_ClientSideAnimationListHandle )
|
|
return;
|
|
|
|
unsigned int c = g_ClientSideAnimationList.Count();
|
|
|
|
Assert( m_ClientSideAnimationListHandle < c );
|
|
|
|
unsigned int last = c - 1;
|
|
|
|
if ( last == m_ClientSideAnimationListHandle )
|
|
{
|
|
// Just wipe the final entry
|
|
g_ClientSideAnimationList.FastRemove( last );
|
|
}
|
|
else
|
|
{
|
|
clientanimating_t lastEntry = g_ClientSideAnimationList[ last ];
|
|
// Remove the last entry
|
|
g_ClientSideAnimationList.FastRemove( last );
|
|
|
|
// And update it's handle to point to this slot.
|
|
lastEntry.pAnimating->m_ClientSideAnimationListHandle = m_ClientSideAnimationListHandle;
|
|
g_ClientSideAnimationList[ m_ClientSideAnimationListHandle ] = lastEntry;
|
|
}
|
|
|
|
// Invalidate our handle no matter what.
|
|
m_ClientSideAnimationListHandle = INVALID_CLIENTSIDEANIMATION_LIST_HANDLE;
|
|
}
|
|
|
|
|
|
// static method
|
|
void C_BaseAnimating::UpdateClientSideAnimations()
|
|
{
|
|
VPROF_BUDGET( "UpdateClientSideAnimations", VPROF_BUDGETGROUP_CLIENT_ANIMATION );
|
|
|
|
int c = g_ClientSideAnimationList.Count();
|
|
for ( int i = 0; i < c ; ++i )
|
|
{
|
|
clientanimating_t &anim = g_ClientSideAnimationList.Element(i);
|
|
if ( !(anim.flags & FCLIENTANIM_SEQUENCE_CYCLE) )
|
|
continue;
|
|
Assert( anim.pAnimating );
|
|
anim.pAnimating->UpdateClientSideAnimation();
|
|
}
|
|
}
|
|
|
|
CBoneList *C_BaseAnimating::RecordBones( CStudioHdr *hdr, matrix3x4_t *pBoneState )
|
|
{
|
|
if ( !ToolsEnabled() )
|
|
return NULL;
|
|
|
|
VPROF_BUDGET( "C_BaseAnimating::RecordBones", VPROF_BUDGETGROUP_TOOLS );
|
|
|
|
// Possible optimization: Instead of inverting everything while recording, record the pos/q stuff into a structure instead?
|
|
Assert( hdr );
|
|
|
|
// Setup our transform based on render angles and origin.
|
|
matrix3x4_t parentTransform;
|
|
AngleMatrix( GetRenderAngles(), GetRenderOrigin(), parentTransform );
|
|
|
|
CBoneList *boneList = CBoneList::Alloc();
|
|
Assert( boneList );
|
|
|
|
boneList->m_nBones = hdr->numbones();
|
|
|
|
for ( int i = 0; i < hdr->numbones(); i++ )
|
|
{
|
|
matrix3x4_t inverted;
|
|
matrix3x4_t output;
|
|
|
|
mstudiobone_t *bone = hdr->pBone( i );
|
|
|
|
// Only update bones referenced during setup
|
|
if ( !(bone->flags & BONE_USED_BY_ANYTHING ) )
|
|
{
|
|
boneList->m_quatRot[ i ].Init( 0.0f, 0.0f, 0.0f, 1.0f ); // Init by default sets all 0's, which is invalid
|
|
boneList->m_vecPos[ i ].Init();
|
|
continue;
|
|
}
|
|
|
|
if ( bone->parent == -1 )
|
|
{
|
|
// Decompose into parent space
|
|
MatrixInvert( parentTransform, inverted );
|
|
}
|
|
else
|
|
{
|
|
MatrixInvert( pBoneState[ bone->parent ], inverted );
|
|
}
|
|
|
|
ConcatTransforms( inverted, pBoneState[ i ], output );
|
|
|
|
MatrixAngles( output,
|
|
boneList->m_quatRot[ i ],
|
|
boneList->m_vecPos[ i ] );
|
|
}
|
|
|
|
return boneList;
|
|
}
|
|
|
|
void C_BaseAnimating::GetToolRecordingState( KeyValues *msg )
|
|
{
|
|
if ( !ToolsEnabled() )
|
|
return;
|
|
|
|
VPROF_BUDGET( "C_BaseAnimating::GetToolRecordingState", VPROF_BUDGETGROUP_TOOLS );
|
|
|
|
// Force the animation to drive bones
|
|
CStudioHdr *hdr = GetModelPtr();
|
|
matrix3x4_t *pBones = (matrix3x4_t*)_alloca( ( hdr ? hdr->numbones() : 1 ) * sizeof(matrix3x4_t) );
|
|
if ( hdr )
|
|
{
|
|
SetupBones( pBones, hdr->numbones(), BONE_USED_BY_ANYTHING, gpGlobals->curtime );
|
|
}
|
|
else
|
|
{
|
|
SetupBones( NULL, -1, BONE_USED_BY_ANYTHING, gpGlobals->curtime );
|
|
}
|
|
|
|
BaseClass::GetToolRecordingState( msg );
|
|
|
|
static BaseAnimatingRecordingState_t state;
|
|
state.m_nSkin = GetSkin();
|
|
state.m_nBody = m_nBody;
|
|
state.m_nSequence = m_nSequence;
|
|
state.m_pBoneList = NULL;
|
|
msg->SetPtr( "baseanimating", &state );
|
|
msg->SetInt( "viewmodel", IsViewModel() ? 1 : 0 );
|
|
|
|
if ( hdr )
|
|
{
|
|
state.m_pBoneList = RecordBones( hdr, pBones );
|
|
}
|
|
}
|
|
|
|
void C_BaseAnimating::CleanupToolRecordingState( KeyValues *msg )
|
|
{
|
|
if ( !ToolsEnabled() )
|
|
return;
|
|
|
|
BaseAnimatingRecordingState_t *pState = (BaseAnimatingRecordingState_t*)msg->GetPtr( "baseanimating" );
|
|
if ( pState && pState->m_pBoneList )
|
|
{
|
|
pState->m_pBoneList->Release();
|
|
}
|
|
|
|
BaseClass::CleanupToolRecordingState( msg );
|
|
}
|
|
|
|
LocalFlexController_t C_BaseAnimating::GetNumFlexControllers( void )
|
|
{
|
|
CStudioHdr *pstudiohdr = GetModelPtr( );
|
|
if (! pstudiohdr)
|
|
return LocalFlexController_t(0);
|
|
|
|
return pstudiohdr->numflexcontrollers();
|
|
}
|
|
|
|
const char *C_BaseAnimating::GetFlexDescFacs( int iFlexDesc )
|
|
{
|
|
CStudioHdr *pstudiohdr = GetModelPtr( );
|
|
if (! pstudiohdr)
|
|
return 0;
|
|
|
|
mstudioflexdesc_t *pflexdesc = pstudiohdr->pFlexdesc( iFlexDesc );
|
|
|
|
return pflexdesc->pszFACS( );
|
|
}
|
|
|
|
const char *C_BaseAnimating::GetFlexControllerName( LocalFlexController_t iFlexController )
|
|
{
|
|
CStudioHdr *pstudiohdr = GetModelPtr( );
|
|
if (! pstudiohdr)
|
|
return 0;
|
|
|
|
mstudioflexcontroller_t *pflexcontroller = pstudiohdr->pFlexcontroller( iFlexController );
|
|
|
|
return pflexcontroller->pszName( );
|
|
}
|
|
|
|
const char *C_BaseAnimating::GetFlexControllerType( LocalFlexController_t iFlexController )
|
|
{
|
|
CStudioHdr *pstudiohdr = GetModelPtr( );
|
|
if (! pstudiohdr)
|
|
return 0;
|
|
|
|
mstudioflexcontroller_t *pflexcontroller = pstudiohdr->pFlexcontroller( iFlexController );
|
|
|
|
return pflexcontroller->pszType( );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns the fade scale of the entity in question
|
|
// Output : unsigned char - 0 - 255 alpha value
|
|
//-----------------------------------------------------------------------------
|
|
unsigned char C_BaseAnimating::GetClientSideFade( void )
|
|
{
|
|
return UTIL_ComputeEntityFade( this, m_fadeMinDist, m_fadeMaxDist, m_flFadeScale );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Note that we've been transmitted a sequence
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::SetReceivedSequence( void )
|
|
{
|
|
m_bReceivedSequence = true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: See if we should force reset our sequence on a new model
|
|
//-----------------------------------------------------------------------------
|
|
bool C_BaseAnimating::ShouldResetSequenceOnNewModel( void )
|
|
{
|
|
return ( m_bReceivedSequence == false );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::UpdateBoneAttachments( void )
|
|
{
|
|
if ( !m_pAttachedTo )
|
|
return;
|
|
|
|
// Assert( IsFollowingEntity() );
|
|
// Assert( m_boneIndexAttached >= 0 );
|
|
|
|
C_BaseAnimating *follow = FindFollowedEntity();
|
|
if ( follow && (m_boneIndexAttached >= 0) )
|
|
{
|
|
matrix3x4_t boneToWorld, localSpace;
|
|
follow->GetCachedBoneMatrix( m_boneIndexAttached, boneToWorld );
|
|
AngleMatrix( m_boneAngles, m_bonePosition, localSpace );
|
|
ConcatTransforms( boneToWorld, localSpace, GetBoneForWrite( 0 ) );
|
|
|
|
Vector absOrigin;
|
|
MatrixGetColumn( GetBone( 0 ), 3, absOrigin );
|
|
SetAbsOrigin( absOrigin );
|
|
|
|
QAngle absAngle;
|
|
MatrixAngles( GetBone( 0 ), absAngle );
|
|
SetAbsAngles( absAngle);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::AttachEntityToBone( C_BaseAnimating* attachTarget, int boneIndexAttached, Vector bonePosition, QAngle boneAngles )
|
|
{
|
|
if ( !attachTarget )
|
|
return;
|
|
|
|
SetCollisionGroup( COLLISION_GROUP_DEBRIS );
|
|
|
|
FollowEntity( attachTarget );
|
|
SetOwnerEntity( attachTarget );
|
|
|
|
// Assert( boneIndexAttached >= 0 ); // We should be attaching to a bone.
|
|
|
|
if ( boneIndexAttached >= 0 )
|
|
{
|
|
m_boneIndexAttached = boneIndexAttached;
|
|
m_bonePosition = bonePosition;
|
|
m_boneAngles = boneAngles;
|
|
}
|
|
|
|
m_BoneAccessor.SetReadableBones( BONE_USED_BY_ANYTHING );
|
|
m_BoneAccessor.SetWritableBones( BONE_USED_BY_ANYTHING );
|
|
|
|
attachTarget->AddBoneAttachment( this );
|
|
|
|
NotifyBoneAttached( attachTarget );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::NotifyBoneAttached( C_BaseAnimating* attachTarget )
|
|
{
|
|
// If we're already attached to something, remove us from it.
|
|
if ( m_pAttachedTo )
|
|
{
|
|
m_pAttachedTo->RemoveBoneAttachment( this );
|
|
m_pAttachedTo = NULL;
|
|
}
|
|
|
|
// Remember the new attach target.
|
|
m_pAttachedTo = attachTarget;
|
|
|
|
// Special case: if we just attached to the local player and he is hidden, hide us as well.
|
|
C_BasePlayer *pPlayer = dynamic_cast<C_BasePlayer*>(attachTarget);
|
|
if ( pPlayer && pPlayer->IsLocalPlayer() )
|
|
{
|
|
if ( !C_BasePlayer::ShouldDrawLocalPlayer() )
|
|
{
|
|
AddEffects( EF_NODRAW );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RemoveEffects( EF_NODRAW );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::AddBoneAttachment( C_BaseAnimating* newBoneAttachment )
|
|
{
|
|
if ( !newBoneAttachment )
|
|
return;
|
|
|
|
m_BoneAttachments.AddToTail( newBoneAttachment );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::RemoveBoneAttachment( C_BaseAnimating* boneAttachment )
|
|
{
|
|
if ( !boneAttachment )
|
|
return;
|
|
|
|
m_BoneAttachments.FindAndRemove( boneAttachment );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
int C_BaseAnimating::GetNumBoneAttachments()
|
|
{
|
|
return m_BoneAttachments.Count();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
C_BaseAnimating* C_BaseAnimating::GetBoneAttachment( int i )
|
|
{
|
|
if ( m_BoneAttachments.IsValidIndex(i) )
|
|
{
|
|
return m_BoneAttachments[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::DestroyBoneAttachments()
|
|
{
|
|
while ( GetNumBoneAttachments() )
|
|
{
|
|
C_BaseAnimating *pAttachment = GetBoneAttachment(0);
|
|
if ( pAttachment )
|
|
{
|
|
pAttachment->Release();
|
|
}
|
|
else
|
|
{
|
|
m_BoneAttachments.Remove(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void C_BaseAnimating::MoveBoneAttachments( C_BaseAnimating* attachTarget )
|
|
{
|
|
if ( !attachTarget )
|
|
return;
|
|
|
|
// Move all of our bone attachments to this new object.
|
|
// Preserves the specific bone and attachment location information.
|
|
while ( GetNumBoneAttachments() )
|
|
{
|
|
C_BaseAnimating *pAttachment = GetBoneAttachment(0);
|
|
if ( pAttachment )
|
|
{
|
|
pAttachment->AttachEntityToBone( attachTarget );
|
|
}
|
|
else
|
|
{
|
|
m_BoneAttachments.Remove(0);
|
|
}
|
|
}
|
|
}
|