From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/game/client/c_baseanimating.cpp | 12858 +++++++++++++++---------------- 1 file changed, 6429 insertions(+), 6429 deletions(-) (limited to 'mp/src/game/client/c_baseanimating.cpp') diff --git a/mp/src/game/client/c_baseanimating.cpp b/mp/src/game/client/c_baseanimating.cpp index 1086c301..c71d2682 100644 --- a/mp/src/game/client/c_baseanimating.cpp +++ b/mp/src/game/client/c_baseanimating.cpp @@ -1,6429 +1,6429 @@ -//========= 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 -#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( 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 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; - 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_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( modelinfo->GetModel( nNewIndex ) == GetModel() ); - } - - 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( 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( 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 ); - 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;iflags() & 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() ); - -#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() ); - 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 ) & EF_PARITY_MASK; - m_nResetEventsParity = ( ++m_nResetEventsParity ) & 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(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); - } - } -} +//========= 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 +#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( 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 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; + 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_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( modelinfo->GetModel( nNewIndex ) == GetModel() ); + } + + 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( 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( 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 ); + 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;iflags() & 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() ); + +#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() ); + 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 ) & EF_PARITY_MASK; + m_nResetEventsParity = ( ++m_nResetEventsParity ) & 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(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); + } + } +} -- cgit v1.2.3