You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
6437 lines
177 KiB
6437 lines
177 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//===========================================================================// |
|
#include "cbase.h" |
|
#include "c_baseanimating.h" |
|
#include "c_sprite.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; |
|
|
|
BEGIN_RECV_TABLE_NOBASE( C_BaseAnimating, DT_ServerAnimationData ) |
|
RecvPropFloat(RECVINFO(m_flCycle)), |
|
END_RECV_TABLE() |
|
|
|
|
|
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)), |
|
|
|
RecvPropDataTable( "serveranimdata", 0, 0, &REFERENCE_RECV_TABLE( DT_ServerAnimationData ) ), |
|
|
|
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_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; |
|
} |
|
|
|
AddBaseAnimatingInterpolatedVars(); |
|
|
|
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; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: cleanup |
|
//----------------------------------------------------------------------------- |
|
C_BaseAnimating::~C_BaseAnimating() |
|
{ |
|
int i = g_PreviousBoneSetups.Find( this ); |
|
if ( i != -1 ) |
|
g_PreviousBoneSetups.FastRemove( i ); |
|
RemoveFromClientSideAnimationList(); |
|
|
|
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 ); |
|
|
|
UpdateRelevantInterpolatedVars(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: sets client side animation |
|
//----------------------------------------------------------------------------- |
|
void C_BaseAnimating::UseClientSideAnimation() |
|
{ |
|
m_bClientSideAnimation = true; |
|
} |
|
|
|
void C_BaseAnimating::UpdateRelevantInterpolatedVars() |
|
{ |
|
MDLCACHE_CRITICAL_SECTION(); |
|
// Remove any interpolated vars that need to be removed. |
|
if ( !GetPredictable() && !IsClientCreated() && GetModelPtr() && GetModelPtr()->SequencesAvailable() ) |
|
{ |
|
AddBaseAnimatingInterpolatedVars(); |
|
} |
|
else |
|
{ |
|
RemoveBaseAnimatingInterpolatedVars(); |
|
} |
|
} |
|
|
|
|
|
void C_BaseAnimating::AddBaseAnimatingInterpolatedVars() |
|
{ |
|
AddVar( m_flEncodedController, &m_iv_flEncodedController, LATCH_ANIMATION_VAR, true ); |
|
AddVar( m_flPoseParameter, &m_iv_flPoseParameter, LATCH_ANIMATION_VAR, true ); |
|
|
|
int flags = LATCH_ANIMATION_VAR; |
|
if ( m_bClientSideAnimation ) |
|
flags |= EXCLUDE_AUTO_INTERPOLATE; |
|
|
|
AddVar( &m_flCycle, &m_iv_flCycle, flags, true ); |
|
} |
|
|
|
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 ); |
|
} |
|
} |
|
|
|
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 = (MDLHandle_t)(int)(pStudioHdr->virtualModel)&0xffff; |
|
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 = (MDLHandle_t)(int)pStudioHdr->virtualModel&0xffff; |
|
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 = (MDLHandle_t)(int)pStudioHdr->virtualModel&0xffff; |
|
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 ); |
|
m_iv_flPoseParameter.SetLooping( Pose.loop != 0.0f, 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; |
|
m_iv_flEncodedController.SetLooping( loop, i ); |
|
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(); |
|
} |
|
|
|
UpdateRelevantInterpolatedVars(); |
|
|
|
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 ) |
|
{ |
|
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, gpGlobals->realtime, 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, 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, |
|
gpGlobals->curtime |
|
); |
|
|
|
|
|
// 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 = (gpGlobals->curtime - 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", gpGlobals->curtime, boneSetup.GetStudioHdr()->pSeqdesc( blend->m_nSequence ).pszLabel(), flCycle, (float)blend->m_flWeight ); |
|
} |
|
#endif |
|
|
|
boneSetup.AccumulatePose( pos, q, blend->m_nSequence, flCycle, blend->m_flWeight, gpGlobals->curtime, 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 ); |
|
|
|
MaintainSequenceTransitions( boneSetup, fCycle, pos, q ); |
|
|
|
AccumulateLayers( boneSetup, pos, q, currentTime ); |
|
|
|
CIKContext auto_ik; |
|
auto_ik.Init( hdr, GetRenderAngles(), GetRenderOrigin(), currentTime, gpGlobals->framecount, boneMask ); |
|
boneSetup.CalcAutoplaySequences( pos, q, currentTime, &auto_ik ); |
|
|
|
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 ); |
|
// Reset interpolation here since the client is controlling this rather than the server... |
|
m_iv_flPoseParameter.SetHistoryValuesForItem( index, raw ); |
|
} |
|
} |
|
|
|
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(); |
|
} |
|
|
|
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 ); |
|
|
|
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; |
|
|
|
if ( !m_bClientSideAnimation ) |
|
m_iv_flCycle.SetLooping( IsSequenceLooping( GetSequence() ) ); |
|
|
|
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(); |
|
} |
|
} |
|
|
|
// reset prev cycle if new sequence |
|
if (m_nNewSequenceParity != m_nPrevNewSequenceParity) |
|
{ |
|
// It's important not to call Reset() on a static prop, because if we call |
|
// Reset(), then the entity will stay in the interpolated entities list |
|
// forever, wasting CPU. |
|
MDLCACHE_CRITICAL_SECTION(); |
|
CStudioHdr *hdr = GetModelPtr(); |
|
if ( hdr && !( hdr->flags() & STUDIOHDR_FLAGS_STATIC_PROP ) ) |
|
{ |
|
m_iv_flCycle.Reset(); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// 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 = MIN( gpGlobals->curtime - m_flAnimTime, 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(); |
|
} |
|
} |
|
} |
|
|
|
|
|
//========================================================= |
|
// 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(); |
|
|
|
#if 0 |
|
// I didn't have a test case for this, but it seems like the right thing to do. Check multi-player! |
|
|
|
// Msg("%s : %s : %5.1f\n", GetClassname(), GetSequenceName( GetSequence() ), GetCycle() ); |
|
InvalidatePhysicsRecursive( ANIMATION_CHANGED ); |
|
#endif |
|
|
|
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 ) |
|
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 |
|
|
|
float curtime = gpGlobals->curtime; |
|
|
|
if (flInterval == 0.0f) |
|
{ |
|
flInterval = ( curtime - m_flAnimTime ); |
|
if (flInterval <= 0.001f) |
|
{ |
|
return 0.0f; |
|
} |
|
} |
|
|
|
if ( !m_flAnimTime ) |
|
{ |
|
flInterval = 0.0f; |
|
} |
|
|
|
float cyclerate = GetSequenceCycleRate( hdr, GetSequence() ); |
|
float addcycle = flInterval * cyclerate * m_flPlaybackRate; |
|
|
|
if( GetServerIntendedCycle() != -1.0f ) |
|
{ |
|
// The server would like us to ease in a correction so that we will animate the same on the client and server. |
|
// So we will actually advance the average of what we would have done and what the server wants. |
|
float serverCycle = GetServerIntendedCycle(); |
|
float serverAdvance = serverCycle - GetCycle(); |
|
bool adjustOkay = serverAdvance > 0.0f;// only want to go forward. backing up looks really jarring, even when slight |
|
if( serverAdvance < -0.8f ) |
|
{ |
|
// Oh wait, it was just a wraparound from .9 to .1. |
|
serverAdvance += 1; |
|
adjustOkay = true; |
|
} |
|
|
|
if( adjustOkay ) |
|
{ |
|
float originalAdvance = addcycle; |
|
addcycle = (serverAdvance + addcycle) / 2; |
|
|
|
const float MAX_CYCLE_ADJUSTMENT = 0.1f; |
|
addcycle = MIN( MAX_CYCLE_ADJUSTMENT, addcycle );// Don't do too big of a jump; it's too jarring as well. |
|
|
|
DevMsg( 2, "(%d): Cycle latch used to correct %.2f in to %.2f instead of %.2f.\n", |
|
entindex(), GetCycle(), GetCycle() + addcycle, GetCycle() + originalAdvance ); |
|
} |
|
|
|
SetServerIntendedCycle(-1.0f); // Only use a correction once, it isn't valid any time but right now. |
|
} |
|
|
|
float flNewCycle = GetCycle() + addcycle; |
|
m_flAnimTime = curtime; |
|
|
|
if ( bWatch ) |
|
{ |
|
Msg("%i CLIENT Time: %6.3f : (Interval %f) : cycle %f rate %f add %f\n", |
|
gpGlobals->tickcount, gpGlobals->curtime, flInterval, flNewCycle, cyclerate, addcycle ); |
|
} |
|
|
|
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; |
|
} |
|
|
|
SetCycle( flNewCycle ); |
|
|
|
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(); |
|
|
|
UpdateRelevantInterpolatedVars(); |
|
} |
|
|
|
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; |
|
|
|
UpdateRelevantInterpolatedVars(); |
|
} |
|
|
|
|
|
// 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); |
|
} |
|
} |
|
}
|
|
|