//===== Copyright © 1996-2005, 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 "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 "shaderapi/ishaderapi.h" #include "tools/bonelist.h" #include "toolframework/itoolframework.h" #include "datacache/idatacache.h" #include "gamestringpool.h" #include "engine/IVDebugOverlay.h" #include "jigglebones.h" #include "toolframework_client.h" #include "vstdlib/jobthread.h" #include "bonetoworldarray.h" #include "posedebugger.h" #include "tier0/ICommandLine.h" #include #include "prediction.h" #include "c_entityflame.h" #include "npcevent.h" #include "replay_ragdoll.h" #include "clientalphaproperty.h" #ifdef DEMOPOLISH_ENABLED #include "demo_polish/demo_polish.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", "" ); ConVar r_debug_sequencesets( "r_debug_sequencesets", "-2" ); ConVar r_jiggle_bones( "r_jiggle_bones", "1" ); ConVar RagdollImpactStrength( "z_ragdoll_impact_strength", "500" ); ConVar cl_disable_ragdolls( "cl_disable_ragdolls", "0", FCVAR_CHEAT ); ConVar cl_ejectbrass( "cl_ejectbrass", "1" ); // 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 ConVar sfm_record_hz( "sfm_record_hz", "30" ); static bool g_bInThreadedBoneSetup; mstudioevent_t *GetEventIndexForSequence( mstudioseqdesc_t &seqdesc ); C_EntityDissolve *DissolveEffect( C_BaseAnimating *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 ); ConVar enable_skeleton_draw( "enable_skeleton_draw", "0", FCVAR_CHEAT, "Render skeletons in wireframe" ); extern ConVar r_shadow_deferred; bool C_AnimationLayer::IsActive( void ) { return (m_nOrder != C_BaseAnimatingOverlay::MAX_OVERLAYS); } //----------------------------------------------------------------------------- // 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(); /* if (r_sequence_debug.GetInt() == pAnimating->entindex() ) { DevMsgRT( "%d : RecvProxy_Sequence( %d:%s )\n", pAnimating->entindex(), pAnimating->GetSequence(), pAnimating->GetSequenceName( pAnimating->GetSequence() ) ); Assert( 1 ); } */ } 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)), // 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 )), RecvPropBool( RECVINFO( m_bClientSideRagdoll )), RecvPropInt( RECVINFO( m_nNewSequenceParity )), RecvPropInt( RECVINFO( m_nResetEventsParity )), RecvPropInt( RECVINFO( m_nMuzzleFlashParity ) ), RecvPropEHandle(RECVINFO(m_hLightingOrigin)), RecvPropDataTable( "serveranimdata", 0, 0, &REFERENCE_RECV_TABLE( DT_ServerAnimationData ) ), RecvPropFloat( RECVINFO( m_flFrozen ) ), 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_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 < CAnimationLayer > ), // 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 ), 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 ), // TODO: FIX: This is dynamically-typed END_DATADESC() BEGIN_ENT_SCRIPTDESC( C_BaseAnimating, C_BaseEntity, "Animating models client-side" ) DEFINE_SCRIPTFUNC_NAMED( ScriptSetPoseParameter, "SetPoseParameter", "Set the specified pose parameter to the specified value" ) DEFINE_SCRIPTFUNC( IsSequenceFinished, "Ask whether the main sequence is done playing" ) END_SCRIPTDESC(); C_ClientRagdoll::C_ClientRagdoll( bool bRestoring , bool fullInit) { m_iCurrentFriction = 0; m_iFrictionAnimState = RAGDOLL_FRICTION_NONE; m_bReleaseRagdoll = false; m_bFadeOut = false; m_bFadingOut = false; m_bImportant = false; if(fullInit) { 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( false ); 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, char *pCustomImpactName ) { VPROF( "C_ClientRagdoll::ImpactTrace" ); IPhysicsObject *pPhysicsObject = VPhysicsGetObject(); if( !pPhysicsObject ) return; if ( !pPhysicsObject->IsCollisionEnabled() ) 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 *= RagdollImpactStrength.GetFloat(); // adjust impact strength // 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.minFriction; m_iMaxFriction = pRagdollT->animfriction.maxFriction; if ( m_iMinFriction != 0 || m_iMaxFriction != 0 ) { m_iFrictionAnimState = RAGDOLL_FRICTION_IN; m_flFrictionModTime = pRagdollT->animfriction.timeIn; 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.timeHold; 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.timeOut; 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 = GetRenderAlpha(); int iFadeSpeed = ( g_RagdollLVManager.IsLowViolence() ) ? g_ragdoll_lvfadespeed.GetInt() : g_ragdoll_fadespeed.GetInt(); iAlpha = MAX( iAlpha - ( iFadeSpeed * gpGlobals->frametime ), 0 ); SetRenderMode( kRenderTransAlpha ); SetRenderAlpha( 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 ) { 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; } //---------------------------------------------------------------------------- // Hooks into the fast path render system //---------------------------------------------------------------------------- IClientModelRenderable* C_ClientRagdoll::GetClientModelRenderable() { if ( !BaseClass::GetClientModelRenderable() ) return NULL; // NOTE: This is because of code in SetupWeights, which calls SetViewTarget. // The view target is a per-instance piece of state which is not yet // supported by the model fast path. Once it is, we can eliminate this // code and make it so ragdolls always use the fast path if ( m_iEyeAttachment > 0 ) return NULL; return this; } //----------------------------------------------------------------------------- // 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 ) { memset( pFlexWeights, 0, nFlexWeightCount * sizeof(float) ); if ( pFlexDelayedWeights ) { memset( pFlexDelayedWeights, 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 ) { UTIL_Remove( pChild ); } 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_iEjectBrassAttachment = -1; m_vecForce.Init(); m_nForceBone = -1; SetGlobalFadeScale( 1.0f ); m_ClientSideAnimationListHandle = INVALID_CLIENTSIDEANIMATION_LIST_HANDLE; m_bCanUseFastPath = false; m_bIsUsingRelativeLighting = false; m_bIsStaticProp = false; m_nPrevSequence = -1; m_nRestoreSequence = -1; m_pRagdoll = NULL; m_pClientsideRagdoll = NULL; m_builtRagdoll = false; 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_pNextForThreadedBoneSetup = NULL; 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_bBonePolishSetup = false; m_prevClientCycle = 0; m_prevClientAnimTime = 0; m_flOldModelScale = 0.0f; m_pJiggleBones = NULL; m_isJiggleBonesEnabled = true; AddToEntityList(ENTITY_LIST_SIMULATE); } //----------------------------------------------------------------------------- // Purpose: cleanup //----------------------------------------------------------------------------- C_BaseAnimating::~C_BaseAnimating() { Assert( !g_bInThreadedBoneSetup ); if ( m_iMostRecentBoneSetupRequest == g_iPreviousBoneCounter ) { int i = g_PreviousBoneSetups.Find( this ); Assert( i != -1 ); if ( i != -1 ) g_PreviousBoneSetups.FastRemove( i ); } else { Assert( g_PreviousBoneSetups.Find( this ) == -1 ); } RemoveFromClientSideAnimationList(); TermRopes(); delete m_pRagdollInfo; Assert(!m_pRagdoll); delete m_pIk; delete m_pBoneMergeCache; UnlockStudioHdr(); delete m_pStudioHdr; if ( m_pJiggleBones ) { delete m_pJiggleBones; m_pJiggleBones = NULL; } } int C_BaseAnimating::GetRenderFlags( void ) { int nRet = 0; if ( modelinfo->IsUsingFBTexture( GetModel(), GetSkin(), GetBody(), GetClientRenderable() ) ) nRet |= ERENDERFLAGS_NEEDS_POWER_OF_TWO_FB; return nRet; } //----------------------------------------------------------------------------- // 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() { // Remove any interpolated vars that need to be removed. MDLCACHE_CRITICAL_SECTION(); if ( !GetPredictable() && !IsClientCreated() && GetModelPtr() && GetModelPtr()->SequencesAvailable() && WantsInterpolatedVars() ) { AddBaseAnimatingInterpolatedVars(); } else { RemoveBaseAnimatingInterpolatedVars(); } } void C_BaseAnimating::AddBaseAnimatingInterpolatedVars() { AddVar( m_flEncodedController, &m_iv_flEncodedController, LATCH_ANIMATION_VAR, true ); int flags = LATCH_ANIMATION_VAR; if ( m_bClientSideAnimation ) flags |= EXCLUDE_AUTO_INTERPOLATE; AddVar( m_flPoseParameter, &m_iv_flPoseParameter, flags, true ); AddVar( &m_flCycle, &m_iv_flCycle, flags, true ); } void C_BaseAnimating::RemoveBaseAnimatingInterpolatedVars() { RemoveVar( m_flEncodedController, false ); RemoveVar( m_flPoseParameter, false ); RemoveVar( &m_flCycle, false ); } void C_BaseAnimating::LockStudioHdr() { AUTO_LOCK( m_StudioHdrInitLock ); const model_t *mdl = GetModel(); if (mdl) { m_hStudioHdr = modelinfo->GetCacheHandle( mdl ); if ( m_hStudioHdr != MDLHANDLE_INVALID ) { const studiohdr_t *pStudioHdr = mdlcache->LockStudioHdr( m_hStudioHdr ); CStudioHdr *pStudioHdrContainer = NULL; if ( !m_pStudioHdr ) { if ( pStudioHdr ) { pStudioHdrContainer = new CStudioHdr; pStudioHdrContainer->Init( pStudioHdr, mdlcache ); } else { m_hStudioHdr = MDLHANDLE_INVALID; } } else { pStudioHdrContainer = m_pStudioHdr; } Assert( ( pStudioHdr == NULL && pStudioHdrContainer == NULL ) || pStudioHdrContainer->GetRenderHdr() == pStudioHdr ); if ( pStudioHdrContainer && pStudioHdrContainer->GetVirtualModel() ) { MDLHandle_t hVirtualModel = (MDLHandle_t)(int)(pStudioHdrContainer->GetRenderHdr()->virtualModel)&0xffff; mdlcache->LockStudioHdr( hVirtualModel ); } m_pStudioHdr = pStudioHdrContainer; // must be last to ensure virtual model correctly set up } } } void C_BaseAnimating::UnlockStudioHdr() { if ( m_pStudioHdr ) { const model_t *mdl = GetModel(); if (mdl) { mdlcache->UnlockStudioHdr( m_hStudioHdr ); if ( m_pStudioHdr->GetVirtualModel() ) { MDLHandle_t hVirtualModel = (MDLHandle_t)(int)m_pStudioHdr->GetRenderHdr()->virtualModel&0xffff; mdlcache->UnlockStudioHdr( hVirtualModel ); } } } } CStudioHdr *C_BaseAnimating::OnNewModel() { BaseClass::OnNewModel(); m_bCanUseFastPath = false; if (m_pStudioHdr) { UnlockStudioHdr(); delete m_pStudioHdr; m_pStudioHdr = NULL; } // remove transition animations playback m_SequenceTransitioner.RemoveAll(); if (m_pJiggleBones) { delete m_pJiggleBones; m_pJiggleBones = NULL; } if ( !GetModel() ) return NULL; LockStudioHdr(); UpdateRelevantInterpolatedVars(); CStudioHdr *hdr = GetModelPtr(); if (hdr == NULL) return NULL; m_bIsStaticProp = ( hdr->flags() & STUDIOHDR_FLAGS_STATIC_PROP ) ? true : false; // Can we use the model fast path? m_bCanUseFastPath = !modelinfo->ModelHasMaterialProxy( GetModel() ); InvalidateBoneCache(); if ( m_pBoneMergeCache ) { delete m_pBoneMergeCache; m_pBoneMergeCache = NULL; // recreated in BuildTransformations } // 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( gpGlobals->curtime, 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( gpGlobals->curtime, 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() ) AddToEntityList( ENTITY_LIST_INTERPOLATE ); // 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 need to clear it out here. if ( ShouldResetSequenceOnNewModel() ) { SetSequence(0); } 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 ) { CStudioHdr *hdr = GetModelPtr(); bool bWrote = false; if ( hdr && iBone >= 0 && iBone < hdr->numbones() ) { const int boneMask = BONE_USED_BY_HITBOX; if ( hdr->boneFlags(iBone) & boneMask ) { if ( !IsBoneCacheValid() ) { SetupBones( NULL, -1, boneMask, gpGlobals->curtime ); } GetCachedBoneMatrix( iBone, pBoneToWorld ); bWrote = true; } } if ( !bWrote ) { MatrixCopy( EntityToWorldTransform(), pBoneToWorld ); } Assert( GetModelPtr() && iBone >= 0 && iBone < GetModelPtr()->numbones() ); } //----------------------------------------------------------------------------- // Purpose: Setup to initialize our model effects once the model's loaded //----------------------------------------------------------------------------- void C_BaseAnimating::InitModelEffects( void ) { m_bInitModelEffects = true; AddToEntityList(ENTITY_LIST_SIMULATE); 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() ) ) ) { ParseModelEffects( modelKeyValues ); } modelKeyValues->deleteThis(); } void C_BaseAnimating::ParseModelEffects( KeyValues *modelKeyValues ) { // 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 ); } } // 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", "" ); const char *pszAttachOffset = pSingleEffect->GetString( "attachment_offset", "" ); // 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 == -1 ) { 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; } } Vector vecOffset = vec3_origin; if ( pszAttachOffset ) { float flVec[3]; UTIL_StringToVector( flVec, pszAttachOffset ); vecOffset = Vector( flVec[0], flVec[1], flVec[2] ); } CUtlReference hModelEffect; // Spawn the particle effectw hModelEffect = ParticleProp()->Create( pszParticleEffect, (ParticleAttachment_t)iAttachType, iAttachment, vecOffset ); KeyValues *pkvAllControlPoints = pSingleEffect->FindKey("ControlPoints"); if ( pkvAllControlPoints ) { // Start grabbing the CPs and slotting them in for ( KeyValues *pSingleCP = pkvAllControlPoints->GetFirstSubKey(); pSingleCP; pSingleCP = pSingleCP->GetNextKey() ) { const char *pszControlPoint = pSingleCP->GetString( "cp_number", "" ); const char *pszAttachment = pSingleCP->GetString( "attachment_point", "" ); const char *pszAttachType = pSingleCP->GetString( "attachment_type", "" ); const char *pszAttachOffset = pSingleCP->GetString( "attachment_offset", "" ); // Convert control point int iControlPoint = atoi(pszControlPoint); // 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; } Vector vecOffset = vec3_origin; if ( pszAttachOffset ) { float flVec[3]; UTIL_StringToVector( flVec, pszAttachOffset ); vecOffset = Vector( flVec[0], flVec[1], flVec[2] ); } // Add the control point if we already have the effect if ( hModelEffect ) { if ( iAttachType == PATTACH_WORLDORIGIN ) ParticleProp()->AddControlPoint( hModelEffect, iControlPoint, NULL, (ParticleAttachment_t)iAttachType, NULL, vecOffset ); else ParticleProp()->AddControlPoint( hModelEffect, iControlPoint, this, (ParticleAttachment_t)iAttachType, pszAttachment, vecOffset ); } } } } } } void C_BaseAnimating::TermRopes() { FOR_EACH_LL( m_Ropes, i ) { UTIL_Remove( m_Ropes[i] ); } 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::GetPoseParameterRaw( 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 (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::EnableJiggleBones( void ) { m_isJiggleBonesEnabled = true; } void C_BaseAnimating::DisableJiggleBones( void ) { m_isJiggleBonesEnabled = false; // clear old data so any jiggle bones don't pop if they're enabled again if ( m_pJiggleBones ) { delete m_pJiggleBones; m_pJiggleBones = NULL; } } void C_BaseAnimating::ScriptSetPoseParameter( const char *szName, float fValue ) { CStudioHdr *pHdr = GetModelPtr(); if ( pHdr == NULL ) return; int iPoseParam = LookupPoseParameter( pHdr, szName ); SetPoseParameter( pHdr, iPoseParam, fValue ); } void C_BaseAnimating::GetCachedBoneMatrix( int boneIndex, matrix3x4_t &out ) { MatrixCopy( GetBone( boneIndex ), out ); } //----------------------------------------------------------------------------- // Purpose: Merge shared bones over from "followed" entity //----------------------------------------------------------------------------- void C_BaseAnimating::CalcBoneMerge( CStudioHdr *hdr, int boneMask, CBoneBitList &boneComputed ) { // 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, boneComputed ); } else { delete m_pBoneMergeCache; m_pBoneMergeCache = NULL; } } } //----------------------------------------------------------------------------- // 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", ( !g_bInThreadedBoneSetup ) ? VPROF_BUDGETGROUP_CLIENT_ANIMATION : "Client_Animation_Threaded" ); if ( !hdr ) return; matrix3x4a_t bonematrix; bool boneSimulated[MAXSTUDIOBONES]; // no bones have been simulated memset( boneSimulated, 0, sizeof(boneSimulated) ); mstudiobone_t *pbones = hdr->pBone( 0 ); bool bFixupSimulatedPositions = false; 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 we're playing back a demo, override the ragdoll bones with cached version if available - otherwise, simulate. #if defined( REPLAY_ENABLED ) 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 ); bFixupSimulatedPositions = !m_pRagdoll->GetRagdoll()->allowStretch; } // For EF_BONEMERGE entities, copy the bone matrices for any bones that have matching names. CalcBoneMerge( hdr, boneMask, boneComputed ); for (int i = 0; i < hdr->numbones(); i++) { // Only update bones reference by the bone mask. if ( !( hdr->boneFlags( i ) & boneMask ) ) continue; PREFETCH360( &GetBoneForWrite( i ), 0 ); // skip bones that are already setup if (boneComputed.IsBoneMarked( i )) { // dummy operation, just used to verify in debug that this should have happened GetBoneForWrite( i ); } else if ( boneSimulated[i] ) { ApplyBoneMatrixTransform( GetBoneForWrite( i ) ); if ( bFixupSimulatedPositions && pbones[i].parent != -1 ) { Vector boneOrigin; VectorTransform( pos[i], GetBone(pbones[i].parent), boneOrigin ); PositionMatrix( boneOrigin, GetBoneForWrite( i ) ); } continue; } else if ( CalcProceduralBone( hdr, i, m_BoneAccessor )) { continue; } else { // animate all non-simulated bones 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) && !r_jiggle_bones.GetBool() ) { if ( m_pJiggleBones ) { delete m_pJiggleBones; m_pJiggleBones = NULL; } } if ( (hdr->boneFlags( i ) & BONE_ALWAYS_PROCEDURAL) && (hdr->pBone( i )->proctype & STUDIO_PROC_JIGGLE) && r_jiggle_bones.GetBool() && m_isJiggleBonesEnabled ) { // // Physics-based "jiggle" bone // Bone is assumed to be along the Z axis // Pitch around X, yaw around Y // // compute desired bone orientation matrix3x4a_t goalMX; if (pbones[i].parent == -1) { ConcatTransforms( cameraTransform, bonematrix, goalMX ); } else { ConcatTransforms_Aligned( 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->curtime, jiggleInfo, goalMX, GetBoneForWrite( i ) ); } else if (hdr->boneParent(i) == -1) { ConcatTransforms( cameraTransform, bonematrix, GetBoneForWrite( i ) ); } else { ConcatTransforms_Aligned( 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 ) { float scale = GetModelScale(); if ( scale > 1.0f+FLT_EPSILON || scale < 1.0f-FLT_EPSILON ) { // The bone transform is in worldspace, so to scale this, we need to translate it back 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, !IsEffectActive(EF_NOINTERP) ); m_nPrevNewSequenceParity = m_nNewSequenceParity; // Update the transition sequence list. m_SequenceTransitioner.UpdateCurrent( boneSetup.GetStudioHdr(), GetSequence(), flCycle, GetPlaybackRate(), gpGlobals->curtime ); // process previous sequences for (int i = m_SequenceTransitioner.m_animationQueue.Count() - 2; i >= 0; i--) { CAnimationLayer *blend = &m_SequenceTransitioner.m_animationQueue[i]; float dt = (gpGlobals->curtime - blend->m_flLayerAnimtime); flCycle = blend->GetCycle() + dt * blend->GetPlaybackRate() * GetSequenceCycleRate( boneSetup.GetStudioHdr(), blend->GetSequence() ); flCycle = ClampCycle( flCycle, IsSequenceLooping( boneSetup.GetStudioHdr(), blend->GetSequence() ) ); #if 1 // _DEBUG if (r_sequence_debug.GetInt() == entindex()) { DevMsgRT( "%8.4f : %30s : %5.3f : %4.2f +\n", gpGlobals->curtime, boneSetup.GetStudioHdr()->pSeqdesc( blend->GetSequence() ).pszLabel(), flCycle, (float)blend->GetWeight() ); } #endif boneSetup.AccumulatePose( pos, q, blend->GetSequence(), flCycle, blend->GetWeight(), 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 } //----------------------------------------------------------------------------- // Purpose: Do the default sequence blending rules as done in HL1 //----------------------------------------------------------------------------- void C_BaseAnimating::StandardBlendingRules( CStudioHdr *hdr, Vector pos[], QuaternionAligned 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 ); } #ifdef DEMOPOLISH_ENABLED if ( IsDemoPolishPlaying() && IsPlayer() ) { float const flDemoPlaybackTime = DemoPolish_GetController().GetAdjustedPlaybackTime(); int const iSequenceOverride = DemoPolish_GetController().GetSequenceOverride( entindex(), flDemoPlaybackTime ); if ( iSequenceOverride >= 0 ) { // Override. SetSequence( iSequenceOverride ); } } #endif GetPoseParameters( hdr, poseparam ); // build root animation float fCycle = GetCycle(); #if 1 //_DEBUG if (r_sequence_debug.GetInt() == entindex()) { DevMsgRT( "%8.4f : %30s(%d) : %5.3f : %4.2f\n", currentTime, hdr->pSeqdesc( GetSequence() ).pszLabel(), GetSequence(), 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 ); } UnragdollBlend( hdr, pos, q, currentTime ); #ifdef STUDIO_ENABLE_PERF_COUNTERS #if _DEBUG /* if (r_sequence_debug.GetInt() == entindex() ) { 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; } // when hierarchy changes, these are no longer valid for comparison. munge the frame counter so they // get ignored void C_BaseAnimating::InvalidateAttachments() { int frameCount = -1; for ( int i = 0; i < m_Attachments.Count(); i++ ) { m_Attachments[i].m_nLastFramecount = frameCount; } } 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 ); PositionMatrix( vecWorldBonePos, 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: 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::IsViewModelOrAttachment() const { return false; } bool C_BaseAnimating::IsMenuModel() const { return false; } 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; do { if ( pEntity->IsNPC() || pEntity->IsPlayer() ) { return false; } } while ( ( pEntity = pEntity->GetMoveParent() ) != NULL ); 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, 0, PARTITION_CLIENT_IK_ATTACHMENT ); ( 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", ( IsX360() ) ? "1" : "0", 0, "Enable parallel processing of C_BaseAnimating::SetupBones()" ); //----------------------------------------------------------------------------- // Purpose: Do the default sequence blending rules as done in HL1 //----------------------------------------------------------------------------- #ifdef DEBUG_BONE_SETUP_THREADING CThreadLocalInt<> *pCount; #endif void C_BaseAnimating::SetupBonesOnBaseAnimating( C_BaseAnimating *&pBaseAnimating ) { C_BaseAnimating *pCurrent = pBaseAnimating; C_BaseAnimating *pNext; while ( pCurrent ) { pNext = pCurrent->m_pNextForThreadedBoneSetup; pCurrent->m_pNextForThreadedBoneSetup = NULL; pCurrent->SetupBones( NULL, -1, -1, gpGlobals->curtime ); pCurrent = pNext; } #ifdef DEBUG_BONE_SETUP_THREADING (*pCount)++; #endif } static void PreThreadedBoneSetup() { mdlcache->BeginCoarseLock(); mdlcache->BeginLock(); } static void PostThreadedBoneSetup() { mdlcache->EndLock(); mdlcache->EndCoarseLock(); #ifdef DEBUG_BONE_SETUP_THREADING Msg( " %x done, %d\n", ThreadGetCurrentId(), (int)(*pCount) ); (*pCount) = 0; #endif } static bool g_bDoThreadedBoneSetup; IThreadPool *g_pBoneSetupThreadPool; void C_BaseAnimating::InitBoneSetupThreadPool() { #ifdef DEBUG_BONE_SETUP_THREADING pCount = new CThreadLocalInt<>; #endif if ( IsX360() ) { #ifdef _X360 g_pBoneSetupThreadPool = g_pAlternateThreadPool; #endif } else { g_pBoneSetupThreadPool = g_pThreadPool; } } void C_BaseAnimating::ShutdownBoneSetupThreadPool() { } void C_BaseAnimating::MarkForThreadedBoneSetup() { if ( g_bDoThreadedBoneSetup && !g_bInThreadedBoneSetup && m_iMostRecentBoneSetupRequest != g_iPreviousBoneCounter ) { if ( !IsViewModel() ) { // This function is protected by m_BoneSetupLock (see SetupBones) if ( m_iMostRecentBoneSetupRequest != g_iPreviousBoneCounter ) { m_iMostRecentBoneSetupRequest = g_iPreviousBoneCounter; LOCAL_THREAD_LOCK(); Assert( g_PreviousBoneSetups.Find( this ) == -1 ); g_PreviousBoneSetups.AddToTail( this ); } } } } void C_BaseAnimating::ThreadedBoneSetup() { g_bDoThreadedBoneSetup = ( g_pBoneSetupThreadPool && g_pBoneSetupThreadPool->NumThreads() && cl_threaded_bone_setup.GetInt() ); if ( g_bDoThreadedBoneSetup ) { int nCount = g_PreviousBoneSetups.Count(); if ( nCount > 1 ) { VPROF_BUDGET( "C_BaseAnimating::ThreadedBoneSetup", "Client_Animation_Threaded" ); #ifdef DEBUG_BONE_SETUP_THREADING Msg( "{\n" ); #endif // This loop is here rather than the mark function so we don't have to worry about the list being threadsafe, or worry about entity destruction #ifdef _DEBUG CUtlVector test; test.AddVectorToTail( g_PreviousBoneSetups ); #endif for ( int i = g_PreviousBoneSetups.Count() - 1; i >= 0; i-- ) { C_BaseAnimating *pAnimating = g_PreviousBoneSetups[i]; C_BaseAnimating *pParent; if ( pAnimating->GetMoveParent() && ( pParent = pAnimating->GetMoveParent()->GetBaseAnimating() ) != NULL ) { Assert( pAnimating->m_pNextForThreadedBoneSetup == NULL ); C_BaseAnimating *pNextParent; while ( pParent->GetMoveParent() && ( pNextParent = pParent->GetMoveParent()->GetBaseAnimating() ) != NULL ) { pParent = pNextParent; } pAnimating->m_pNextForThreadedBoneSetup = pParent->m_pNextForThreadedBoneSetup; pParent->m_pNextForThreadedBoneSetup = pAnimating; g_PreviousBoneSetups.FastRemove( i ); if ( pParent->m_iMostRecentBoneSetupRequest != g_iPreviousBoneCounter ) { Assert( g_PreviousBoneSetups.Find( pParent ) == -1 ); pParent->m_iMostRecentBoneSetupRequest = g_iPreviousBoneCounter; g_PreviousBoneSetups.AddToTail( pParent ); } } } nCount = g_PreviousBoneSetups.Count(); g_bInThreadedBoneSetup = true; if ( cl_threaded_bone_setup.GetInt() == 1 ) { CParallelProcessor, 2 > processor; processor.m_ItemProcessor.Init( &SetupBonesOnBaseAnimating, &PreThreadedBoneSetup, &PostThreadedBoneSetup ); processor.Run( g_PreviousBoneSetups.Base(), nCount, 1, INT_MAX, g_pBoneSetupThreadPool ); } else { for ( int i = 0; i < nCount; i++ ) { SetupBonesOnBaseAnimating( g_PreviousBoneSetups[i] ); } } g_bInThreadedBoneSetup = false; #ifdef _DEBUG for ( int i = test.Count() - 1; i > 0; i-- ) { Assert( test[i]->m_pNextForThreadedBoneSetup == NULL ); } #endif #ifdef DEBUG_BONE_SETUP_THREADING Msg( "} \n" ); #endif } } g_iPreviousBoneCounter++; g_PreviousBoneSetups.RemoveAll(); } bool C_BaseAnimating::InThreadedBoneSetup() { return g_bInThreadedBoneSetup; } bool C_BaseAnimating::SetupBones( matrix3x4a_t *pBoneToWorldOut, int nMaxBones, int boneMask, float currentTime ) { VPROF_BUDGET( "C_BaseAnimating::SetupBones", ( !g_bInThreadedBoneSetup ) ? VPROF_BUDGETGROUP_CLIENT_ANIMATION : "Client_Animation_Threaded" ); if ( !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; } // Or lastly if demo polish recording #ifdef DEMOPOLISH_ENABLED if ( IsDemoPolishRecording() && IsPlayer() ) { boneMask |= BONE_USED_BY_ANYTHING; } #endif // DEMO_POLISH if ( g_bInThreadedBoneSetup ) { // boneMask |= ( BONE_USED_BY_ATTACHMENT | BONE_USED_BY_BONE_MERGE | BONE_USED_BY_HITBOX ); if ( !m_BoneSetupLock.TryLock() ) { // someone else is handling return false; } // else, we have the lock } else { m_BoneSetupLock.Lock(); } // If we're setting up LOD N, we have set up all lower LODs also // because lower LODs always use subsets of the bones of higher LODs. int nLOD = 0; int nMask = BONE_USED_BY_VERTEX_LOD0; for( ; nLOD < MAX_NUM_LODS; ++nLOD, nMask <<= 1 ) { if ( boneMask & nMask ) break; } for( ; nLOD < MAX_NUM_LODS; ++nLOD, nMask <<= 1 ) { boneMask |= nMask; } #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 // A bit of a hack, but this way when in prediction we use the "correct" gpGlobals->curtime -- rather than the // one that the player artificially advances if ( GetPredictable() && prediction->InPrediction() ) { currentTime = prediction->GetSavedTime(); } 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. // BUGBUG: Time can go backward due to prediction, catch that here until a better solution is found if ( LastBoneChangedTime() >= m_flLastBoneSetupTime || currentTime < 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 } MarkForThreadedBoneSetup(); // Keep track of everthing asked for over the entire frame // But not those things asked for during bone setup // if ( !g_bInThreadedBoneSetup ) { 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() ) { m_BoneSetupLock.Unlock(); return false; } // Setup our transform based on render angles and origin. ALIGN16 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() ); // label the entities if we're trying to figure out who is who if ( r_sequence_debug.GetInt() == -1) { Vector theMins, theMaxs; GetRenderBounds( theMins, theMaxs ); debugoverlay->AddTextOverlay( GetAbsOrigin() + (theMins + theMaxs) * 0.5f, 0, 0.0f, "%d:%s", entindex(), hdr->name() ); } if (hdr->flags() & STUDIOHDR_FLAGS_STATIC_PROP) { MatrixCopy( parentTransform, GetBoneForWrite( 0 ) ); } else { #ifdef DEBUG_BONE_SETUP_THREADING if ( !g_bInThreadedBoneSetup ) { Msg("!%x\n", this ); } #endif if ( !g_bInThreadedBoneSetup ) { 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 defined( INFESTED ) // 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; #endif Vector pos[MAXSTUDIOBONES]; QuaternionAligned q[MAXSTUDIOBONES]; 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() || IsEffectActive(EF_NOINTERP)) { 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; #ifdef DEMOPOLISH_ENABLED bool const bShouldPolish = IsDemoPolishEnabled() && IsPlayer(); if ( bShouldPolish && engine->IsPlayingDemo() ) { DemoPolish_GetController().MakeLocalAdjustments( entindex(), hdr, boneMask, pos, q, boneComputed ); } #endif // 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 ); } #ifdef DEMOPOLISH_ENABLED if ( m_bBonePolishSetup && bShouldPolish && engine->IsRecordingDemo() ) { DemoPolish_GetRecorder().RecordAnimData( entindex(), hdr, GetCycle(), parentTransform, pos, q, boneMask, m_BoneAccessor ); } #endif BuildTransformations( hdr, pos, q, parentTransform, bonesMaskNeedRecalc, boneComputed ); #ifdef DEMOPOLISH_ENABLED // Override bones? if ( bShouldPolish && engine->IsPlayingDemo() ) { DemoPolish_GetController().MakeGlobalAdjustments( entindex(), hdr, boneMask, m_BoneAccessor ); } #endif // Draw skeleton? if ( enable_skeleton_draw.GetBool() ) { DrawSkeleton( hdr, boneMask ); } 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() ) { Plat_FastMemcpy( 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() ); m_BoneSetupLock.Unlock(); return false; } } m_BoneSetupLock.Unlock(); 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 ( !ThreadInMainThread() ) { return true; } if ( IsViewModel() ) return g_BoneAcessBase.bAllowBoneAccessForViewModels; else return g_BoneAcessBase.bAllowBoneAccessForNormalModels; } // (static function) void C_BaseAnimating::PushAllowBoneAccess( bool bAllowForNormalModels, bool bAllowForViewModels, char const *tagPush ) { if ( !ThreadInMainThread() ) { return; } 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 ) { if ( !ThreadInMainThread() ) { return; } // 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++; } 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, const RenderableInstance_t &instance ) { VPROF_BUDGET( "C_BaseAnimating::DrawModel", VPROF_BUDGETGROUP_MODEL_RENDERING ); if ( !m_bReadyToDraw ) return 0; int drawn = 0; 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_DONOTMODIFYSTENCILSTATE ) { extraFlags |= STUDIO_DONOTMODIFYSTENCILSTATE; } if ( flags & STUDIO_SKIP_DECALS ) { extraFlags |= STUDIO_SKIP_DECALS; } // Necessary for lighting blending CreateModelInstance(); if ( !IsFollowingEntity() ) { drawn = InternalDrawModel( flags|extraFlags, instance ); } 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, instance ); // 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, instance ); } } } } // If we're visualizing our bboxes, draw them DrawBBoxVisualizations(); return drawn; } //----------------------------------------------------------------------------- // Purpose: Draw skeleton topology & coordinate systems //----------------------------------------------------------------------------- void C_BaseAnimating::DrawSkeleton( CStudioHdr const* pHdr, int iBoneMask ) const { if ( !pHdr ) return; Vector from, to; for ( int i = 0; i < pHdr->numbones(); ++i ) { if ( !(pHdr->boneFlags( i ) & iBoneMask) ) continue; debugoverlay->AddCoordFrameOverlay( m_BoneAccessor[ i ], 3.0f ); int const iParentIndex = pHdr->boneParent( i ); if ( iParentIndex < 0 ) continue; MatrixPosition( m_BoneAccessor[ i ], from ); MatrixPosition( m_BoneAccessor[ iParentIndex ], to ); debugoverlay->AddLineOverlay( from, to, 0, 255, 255, true, 0.0f ); } } //----------------------------------------------------------------------------- // Gets the hitbox-to-world transforms, returns false if there was a problem //----------------------------------------------------------------------------- bool C_BaseAnimating::HitboxToWorldTransforms( matrix3x4_t *pHitboxToWorld[MAXSTUDIOBONES] ) { if ( !IsBoneCacheValid() ) { 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; SetupBones( NULL, -1, BONE_USED_BY_HITBOX, gpGlobals->curtime ); } for ( int i = 0; i < m_CachedBoneData.Count(); i++ ) { // UNDONE: Some of these bones haven't been set up. Is it necessary to check each // one for membership in the hitbox set and NULL it if it isn't present? pHitboxToWorld[i] = &m_CachedBoneData[i]; } return true; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool C_BaseAnimating::OnPostInternalDrawModel( ClientModelRenderInfo_t *pInfo ) { return true; } //---------------------------------------------------------------------------- // Hooks into the fast path render system //---------------------------------------------------------------------------- static ConVar r_drawmodelstatsoverlay( "r_drawmodelstatsoverlay", "0", FCVAR_CHEAT ); IClientModelRenderable* C_BaseAnimating::GetClientModelRenderable() { // Cannot participate if it has a render clip plane if ( !m_bCanUseFastPath || m_bIsUsingRelativeLighting || !IsVisible() ) return NULL; if ( r_drawothermodels.GetInt() != 1 || r_drawmodelstatsoverlay.GetInt() != 0 || mat_wireframe.GetInt() != 0 ) return NULL; if ( IsFollowingEntity() && !FindFollowedEntity() ) return NULL; return this; } //---------------------------------------------------------------------------- // Hooks into the fast path render system //---------------------------------------------------------------------------- bool C_BaseAnimating::GetRenderData( void *pData, ModelDataCategory_t nCategory ) { switch ( nCategory ) { case MODEL_DATA_LIGHTING_MODEL: // Necessary for lighting blending CreateModelInstance(); *(RenderableLightingModel_t*)pData = LIGHTING_MODEL_STANDARD; return true; case MODEL_DATA_STENCIL: return ComputeStencilState( (ShaderStencilState_t*)pData ); default: return false; // return BaseClass::GetRenderData( pData, nCategory ); } } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool C_BaseAnimating::OnInternalDrawModel( ClientModelRenderInfo_t *pInfo ) { 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 ); } } } } } //---------------------------------------------------------------------------- // Computes stencil settings //---------------------------------------------------------------------------- bool C_BaseAnimating::ComputeStencilState( ShaderStencilState_t *pStencilState ) { #if defined( _X360 ) if ( !r_shadow_deferred.GetBool() ) { // Early out if we don't care about deferred shadow masks return false; } uint32 mask = 0x0; uint32 nRef = mask; mask |= 1 << 2; // Stencil for masking deferred shadows uses 0x4 bool bCastsShadows = ( ShadowCastType() != SHADOWS_NONE ); nRef |= bCastsShadows << 2; pStencilState->m_bEnable = true; pStencilState->m_nTestMask = 0xFFFFFFFF; pStencilState->m_nWriteMask = mask; pStencilState->m_nReferenceValue = nRef; pStencilState->m_CompareFunc = SHADER_STENCILFUNC_ALWAYS; pStencilState->m_PassOp = SHADER_STENCILOP_SET_TO_REFERENCE; pStencilState->m_FailOp = SHADER_STENCILOP_KEEP; pStencilState->m_ZFailOp = SHADER_STENCILOP_KEEP; // Deferred shadow rendering: // set or clear hi-stencil depending on shadow cast type pStencilState->m_bHiStencilEnable = false; pStencilState->m_bHiStencilWriteEnable = true; pStencilState->m_HiStencilCompareFunc = SHADER_HI_STENCILFUNC_NOTEQUAL; pStencilState->m_nHiStencilReferenceValue = 0; /* // The old hi-stencil state, that unmasked too many tiles for things receiving shadows pStencilState->m_HiStencilCompareFunc = bCastsShadows ? SHADER_HI_STENCILFUNC_NOTEQUAL : SHADER_HI_STENCILFUNC_EQUAL; pStencilState->m_nHiStencilReferenceValue = bCastsShadows ? 0 : 1; */ return true; #else return false; #endif } //----------------------------------------------------------------------------- // Purpose: Draws the object // Input : flags - //----------------------------------------------------------------------------- int C_BaseAnimating::InternalDrawModel( int flags, const RenderableInstance_t &instance ) { 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, instance ); } // Make sure hdr is valid for drawing if ( !GetModelPtr() ) return 0; bool bUsingStencil = false; CMatRenderContextPtr pRenderContext( materials ); if ( !( flags & STUDIO_DONOTMODIFYSTENCILSTATE ) && !( flags & STUDIO_SHADOWDEPTHTEXTURE ) && ( flags & STUDIO_RENDER ) ) { ShaderStencilState_t state; bUsingStencil = ComputeStencilState( &state ); if ( bUsingStencil ) { pRenderContext->SetStencilState( state ); } } 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 = m_nBody; pInfo->hitboxset = m_nHitboxSet; bool bMarkAsDrawn = false; if ( OnInternalDrawModel( pInfo ) ) { Assert( !pInfo->pModelToWorld); if ( !pInfo->pModelToWorld ) { pInfo->pModelToWorld = &pInfo->modelToWorld; // Turns the origin + angles into a matrix AngleMatrix( pInfo->angles, pInfo->origin, pInfo->modelToWorld ); } // Suppress unlocking CMatRenderDataReference rd( pRenderContext ); DrawModelState_t state; matrix3x4_t *pBoneToWorld; bMarkAsDrawn = modelrender->DrawModelSetup( *pInfo, &state, &pBoneToWorld ); // Scale the base transform if we don't have a bone hierarchy if ( GetModelScale() > 1.0f+FLT_EPSILON || GetModelScale() < 1.0f-FLT_EPSILON ) { 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 ); } if ( bUsingStencil ) { ShaderStencilState_t state; state.m_bEnable = false; #if defined( _X360 ) // Deferred shadow rendering: Disable Hi-Stencil state.m_bHiStencilEnable = false; state.m_bHiStencilWriteEnable = false; #endif pRenderContext->SetStencilState( state ); } 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 ) { bool watch = false;//IsPlayer(); // Q_strstr( hdr->name, "rifle" ) ? true : false; //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 = !IsVisibleToAnyPlayer() && !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; mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( GetSequence() ); 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_OldSystem() < EVENT_CLIENT ) //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_OldSystem() < EVENT_CLIENT ) //Adrian - Support the old event system continue; if ( (pevent[i].cycle > m_flPrevEventCycle && pevent[i].cycle <= flEventCycle) ) { if ( watch ) { Msg( "%i (seq: %d/%s) FE %i Normal cycle %f, prev %f ev %f (time %.3f) (options %s)\n", gpGlobals->tickcount, GetSequence(), GetSequenceActivityName( GetSequence() ), pevent[i].Event(), pevent[i].cycle, m_flPrevEventCycle, flEventCycle, gpGlobals->curtime, pevent[ i ].pszOptions() ); } 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.runStepLeft); } else { params.m_pSoundName = physprops->GetString(psurf->sounds.runStepRight); } CPASAttenuationFilter filter( pEnt, params.m_pSoundName ); params.m_bWarnOnDirectWaveReference = true; params.m_flVolume = flVolume; pEnt->EmitSound( filter, pEnt->entindex(), params ); } } } //----------------------------------------------------------------------------- // Purpose: Creates a particle effect and ejects a single shell. If // the effect is already active, it just restarts it to eject another shell. //----------------------------------------------------------------------------- void C_BaseAnimating::EjectParticleBrass( const char *pEffectName, const int iAttachment ) { if ( cl_ejectbrass.GetBool() == false ) return; // TODO: Can we change the attachment for an active particle system? if ( !m_ejectBrassEffect || m_iEjectBrassAttachment != iAttachment ) { m_iEjectBrassAttachment = iAttachment; m_ejectBrassEffect = ParticleProp()->Create( pEffectName, PATTACH_POINT_FOLLOW, iAttachment ); } else { m_ejectBrassEffect->Restart(); } } //----------------------------------------------------------------------------- // 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; int iAttachmentCP1 = -1; int iAttachTypeCP1 = PATTACH_ABSORIGIN_FOLLOW; char token[256]; char szParticleEffect[256]; // Get the particle effect name const char *p = options; p = nexttoken(token, p, ' '); if ( token ) { Q_strncpy( szParticleEffect, token, 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 == -1 ) { 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 CNewParticleEffect *pEffect = ParticleProp()->Create( szParticleEffect, (ParticleAttachment_t)iAttachType, iAttachment ); // Get the attachment type for CP1 p = nexttoken(token, p, ' '); if ( !p ) return; if ( token ) { iAttachTypeCP1 = GetAttachTypeFromString( token ); if ( iAttachTypeCP1 == -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 ) { iAttachmentCP1 = atoi(token); // See if we can find any attachment points matching the name if ( token[0] != '0' && iAttachmentCP1 == 0 ) { iAttachmentCP1 = LookupAttachment( token ); if ( iAttachmentCP1 == -1 ) { 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; } } ParticleProp()->AddControlPoint( pEffect, 1, this, (ParticleAttachment_t)iAttachTypeCP1, token ); } } break; case AE_CL_STOP_PARTICLE_EFFECT: { char token[256]; char szParticleEffect[256]; // Get the particle effect name const char *p = options; p = nexttoken(token, p, ' '); if ( token ) { Q_strncpy( szParticleEffect, token, sizeof(szParticleEffect) ); } // Get the attachment point index p = nexttoken(token, p, ' '); bool bStopInstantly = ( token && !Q_stricmp( token, "instantly" ) ); ParticleProp()->StopParticlesNamed( szParticleEffect, bStopInstantly ); } break; case AE_CL_ADD_PARTICLE_EFFECT_CP: { int iControlPoint = 1; int iAttachment = -1; int iAttachType = PATTACH_ABSORIGIN_FOLLOW; int iEffectIndex = -1; char token[256]; char szParticleEffect[256]; // Get the particle effect name const char *p = options; p = nexttoken(token, p, ' '); if ( token ) { Q_strncpy( szParticleEffect, token, sizeof(szParticleEffect) ); } // Get the control point number p = nexttoken(token, p, ' '); if ( token ) { iControlPoint = atoi( token ); } // 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 == -1 ) { 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; } } } iEffectIndex = ParticleProp()->FindEffect( szParticleEffect ); if ( iEffectIndex == -1 ) { Warning("Failed to find specified particle effect. Trying to add CP to '%s' on attachment named '%s'\n", szParticleEffect, token ); return; } ParticleProp()->AddControlPoint( iEffectIndex, iControlPoint, this, (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: { 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 ); } break; case CL_EVENT_FOOTSTEP_RIGHT: { 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 ); } 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 ) { DevWarning( "Unhandled eject brass animevent\n" ); } 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 ) { if ( isdigit( *token ) ) { iAttachment = atoi(token); } else { iAttachment = LookupAttachment( 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; float flOldCycle = GetCycle(); int nChangeFlags = 0; if ( !m_bClientSideAnimation ) m_iv_flCycle.SetLooping( IsSequenceLooping( GetSequence() ) ); int bNoMoreChanges; int retVal = BaseInterpolatePart1( flCurrentTime, oldOrigin, oldAngles, bNoMoreChanges ); if ( retVal == INTERPOLATE_STOP ) { if ( bNoMoreChanges ) RemoveFromEntityList(ENTITY_LIST_INTERPOLATE); return true; } // Did cycle change? if( GetCycle() != flOldCycle ) nChangeFlags |= ANIMATION_CHANGED; if ( bNoMoreChanges ) RemoveFromEntityList(ENTITY_LIST_INTERPOLATE); BaseInterpolatePart2( oldOrigin, oldAngles, nChangeFlags ); return true; } //----------------------------------------------------------------------------- // returns true if we're currently being ragdolled //----------------------------------------------------------------------------- bool C_BaseAnimating::IsRagdoll() const { return m_pRagdoll && m_bClientSideRagdoll; } //----------------------------------------------------------------------------- // 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 ); Vector vecBloat( 5.0f, 5.0f, 5.0f ); theMins -= vecBloat; theMaxs += vecBloat; } 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 ) { #ifdef DEMOPOLISH_ENABLED if ( DemoPolish_ShouldReplaceRoot( entindex() ) ) { return DemoPolish_GetController().GetRenderOrigin( entindex() ); } else #endif if ( IsRagdoll() ) { return m_pRagdoll->GetRagdollOrigin(); } return BaseClass::GetRenderOrigin(); } const QAngle& C_BaseAnimating::GetRenderAngles( void ) { #ifdef DEMOPOLISH_ENABLED if ( DemoPolish_ShouldReplaceRoot( entindex() ) ) { return DemoPolish_GetController().GetRenderAngles( entindex() ); } else #endif if ( IsRagdoll() ) { return vec3_angle; } 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( BOUNDS_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 ) { m_flOldCycle = GetCycle(); m_nOldSequence = GetSequence(); m_flOldModelScale = GetModelScale(); int i; for ( i=0;iflags() & STUDIOHDR_FLAGS_STATIC_PROP ) ) { m_iv_flCycle.Reset( gpGlobals->curtime ); } } } //----------------------------------------------------------------------------- // Purpose: // Input : bnewentity - //----------------------------------------------------------------------------- void C_BaseAnimating::OnPreDataChanged( DataUpdateType_t updateType ) { BaseClass::OnPreDataChanged( updateType ); m_nPrevBody = GetBody(); m_nPrevSkin = GetSkin(); m_bLastClientSideFrameReset = m_bClientSideFrameReset; } void C_BaseAnimating::ForceSetupBonesAtTime( matrix3x4a_t *pBonesOut, float flTime ) { // blow the cached prev bones InvalidateBoneCache(); // reset root position to flTime Interpolate( flTime ); if ( m_bClientSideAnimation ) { float saveCycle = GetCycle(); float oldCycle = m_prevClientCycle; if ( oldCycle > saveCycle ) { oldCycle -= 1.0f; } float cycleInterp = RemapVal( flTime, m_prevClientAnimTime, m_flAnimTime, oldCycle, saveCycle ); cycleInterp = clamp( cycleInterp, 0.0f, 1.0f ); SetCycle( cycleInterp ); // Setup bone state at the given time SetupBones( pBonesOut, MAXSTUDIOBONES, BONE_USED_BY_ANYTHING, flTime ); SetCycle( saveCycle ); } else { // Setup bone state at the given time SetupBones( pBonesOut, MAXSTUDIOBONES, BONE_USED_BY_ANYTHING, flTime ); } } void C_BaseAnimating::GetRagdollInitBoneArrays( matrix3x4a_t *pDeltaBones0, matrix3x4a_t *pDeltaBones1, matrix3x4a_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 { Plat_FastMemcpy( pCurrentBones, m_CachedBoneData.Base(), sizeof( matrix3x4a_t ) * m_CachedBoneData.Count() ); } } C_ClientRagdoll *C_BaseAnimating::CreateClientRagdoll( bool bRestoring ) { DevMsg( "Creating ragdoll at tick %d\n", gpGlobals->tickcount ); return new C_ClientRagdoll( bRestoring ); } 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 = CreateClientRagdoll( false ); if ( pRagdoll == NULL ) return NULL; TermRopes(); const model_t *model = GetModel(); const char *pModelName = modelinfo->GetModelName( model ); if ( pRagdoll->InitializeAsClientEntity( pModelName, false ) == 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_bClientSideRagdoll = true; pRagdoll->SetRenderMode( GetRenderMode() ); pRagdoll->SetRenderColor( GetRenderColor().r, GetRenderColor().g, GetRenderColor().b ); pRagdoll->SetRenderAlpha( GetRenderAlpha() ); pRagdoll->SetGlobalFadeScale( GetGlobalFadeScale() ); pRagdoll->SetBody( GetBody() ); pRagdoll->SetSkin( GetSkin() ); pRagdoll->m_vecForce = m_vecForce; pRagdoll->m_nForceBone = m_nForceBone; pRagdoll->SetNextClientThink( CLIENT_THINK_ALWAYS ); pRagdoll->SetModelName( AllocPooledString(pModelName) ); pRagdoll->CopySequenceTransitions(this); pRagdoll->SetModelScale( this->GetModelScale() ); return pRagdoll; } void C_BaseAnimating::CopySequenceTransitions( C_BaseAnimating *pCopyFrom ) { m_SequenceTransitioner.m_animationQueue.RemoveAll(); int count = pCopyFrom->m_SequenceTransitioner.m_animationQueue.Count(); m_SequenceTransitioner.m_animationQueue.EnsureCount(count); for ( int i = 0; i < count; i++ ) { m_SequenceTransitioner.m_animationQueue[i] = pCopyFrom->m_SequenceTransitioner.m_animationQueue[i]; } } C_BaseAnimating *C_BaseAnimating::BecomeRagdollOnClient() { MoveToLastReceivedPosition( true ); GetAbsOrigin(); m_pClientsideRagdoll = CreateRagdollCopy(); if ( !m_pClientsideRagdoll ) return NULL; matrix3x4a_t boneDelta0[MAXSTUDIOBONES]; matrix3x4a_t boneDelta1[MAXSTUDIOBONES]; matrix3x4a_t currentBones[MAXSTUDIOBONES]; const float boneDt = 0.1f; GetRagdollInitBoneArrays( boneDelta0, boneDelta1, currentBones, boneDt ); m_pClientsideRagdoll->InitAsClientRagdoll( boneDelta0, boneDelta1, currentBones, boneDt ); return m_pClientsideRagdoll; } bool C_BaseAnimating::InitAsClientRagdoll( const matrix3x4_t *pDeltaBones0, const matrix3x4_t *pDeltaBones1, const matrix3x4_t *pCurrentBonePosition, float boneDt ) { 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 ); // 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 ( 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) { // Now that the data has come down from the server, double-check that the clientside animation variables are handled properly UpdateRelevantInterpolatedVars(); 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 ( m_nPrevBody != GetBody() || m_nPrevSkin != GetSkin() ) { OnTranslucencyTypeChanged(); } 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 ( GetSequence() != m_nPrevSequence ) { OnNewSequence(); } // If there's a significant change, make sure the shadow updates if ( modelchanged || (GetSequence() != m_nPrevSequence)) { InvalidatePhysicsRecursive( BOUNDS_CHANGED | SEQUENCE_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 ) { SetCycle( 0 ); } } // build a ragdoll if necessary if ( m_bClientSideRagdoll && !m_builtRagdoll ) { if ( !cl_disable_ragdolls.GetBool() ) { if ( !BecomeRagdollOnClient() ) { AddEffects( EF_NODRAW ); } } } //HACKHACK!!! if ( m_bClientSideRagdoll && m_builtRagdoll == true ) { if ( m_pRagdoll == NULL ) AddEffects( EF_NODRAW ); } if ( m_pRagdoll && !m_bClientSideRagdoll || !m_bClientSideRagdoll && m_builtRagdoll ) { 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 ( IsEffectActive( EF_NOINTERP ) ) { if ( m_pRagdollInfo && m_pRagdollInfo->m_bActive ) { Msg( "delete ragdoll due to nointerp\n" ); // Remove ragdoll info delete m_pRagdollInfo; m_pRagdollInfo = NULL; } AddToEntityList(ENTITY_LIST_SIMULATE); } m_bIsUsingRelativeLighting = ( m_hLightingOrigin.Get() != NULL ); } //----------------------------------------------------------------------------- // 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, !IsEffectActive(EF_NOINTERP) ); } 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 ); } } bool C_BaseAnimating::Simulate() { bool bRet = !m_bIsStaticProp; // static prop defaults to false if ( m_bInitModelEffects ) { DelayedInitModelEffects(); } if ( gpGlobals->frametime != 0.0f ) { CStudioHdr *pStudio = GetModelPtr(); if ( pStudio && pStudio->SequencesAvailable() ) { if ( pStudio->GetRenderHdr()->flags & STUDIOHDR_FLAGS_NO_ANIM_EVENTS ) { bRet = false; } else { DoAnimationEvents( pStudio ); } } } if ( BaseClass::Simulate() ) { bRet = true; } // Server says don't interpolate this frame, so set previous info to new info. if ( IsEffectActive(EF_NOINTERP) ) { ResetLatched(); } if ( GetSequence() != -1 && m_pRagdoll && ( !m_bClientSideRagdoll ) ) { ClearRagdoll(); } return bRet; } 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 ); matrix3x4_t *hitboxbones[MAXSTUDIOBONES]; HitboxToWorldTransforms( hitboxbones ); 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 = pBone->GetSurfaceProp(); 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 ) { Assert( IsFinite( flCycle ) ); m_flCycle = flCycle; InvalidatePhysicsRecursive( ANIMATION_CHANGED ); /* if (r_sequence_debug.GetInt() == entindex() ) { DevMsgRT("%d : SetCycle %s:%.3f\n", entindex(), GetSequenceName( GetSequence() ), flCycle ); } */ } } //----------------------------------------------------------------------------- // Reset any global fields that are dependant on the sequence //----------------------------------------------------------------------------- void C_BaseAnimating::OnNewSequence( void ) { CStudioHdr *pStudioHdr = GetModelPtr(); // Assert( pStudioHdr ); if ( pStudioHdr ) { m_bSequenceLoops = ((GetSequenceFlags( pStudioHdr, GetSequence() ) & STUDIO_LOOPING) != 0); m_flGroundSpeed = GetSequenceGroundSpeed( pStudioHdr, GetSequence() ); // FIXME: why is this called here? Nothing should have changed to make this nessesary SetEventIndexForSequence( pStudioHdr->pSeqdesc( GetSequence() ) ); } } //----------------------------------------------------------------------------- // 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() ); } */ if (r_debug_sequencesets.GetInt() == entindex()) { Msg("%s : %s : SetSequence\n", GetClassname(), GetSequenceName( GetSequence() )); } m_nSequence = nSequence; InvalidatePhysicsRecursive( BOUNDS_CHANGED | SEQUENCE_CHANGED ); if ( m_bClientSideAnimation ) { ClientSideAnimationChanged(); } OnNewSequence(); /* if (r_sequence_debug.GetInt() == entindex() ) { DevMsgRT("%d : SetSequence %s\n", entindex(), GetSequenceName( GetSequence() ) ); } */ } } //----------------------------------------------------------------------------- // Extracts the bounding box //----------------------------------------------------------------------------- void C_BaseAnimating::ExtractBbox( int nSequence, Vector &mins, Vector &maxs ) { CStudioHdr *pStudioHdr = GetModelPtr(); Assert( pStudioHdr ); ::ExtractBbox( pStudioHdr, nSequence, mins, maxs ); } //========================================================= // 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; bool watch = false; //Q_strstr( hdr->name(), "grip" ) ? true : false; //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; } //anim.prevanimtime = m_flAnimTime; float cycleAdvance = flInterval * GetSequenceCycleRate( hdr, GetSequence() ) * GetPlaybackRate(); 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 : %1.3f\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 ); } float C_BaseAnimating::GetSequenceLinearMotionAndDuration( int iSequence, Vector *pVec ) { return ::GetSequenceLinearMotionAndDuration( GetModelPtr(), iSequence, m_flPoseParameter, pVec ); } //----------------------------------------------------------------------------- // Purpose: // Output : //----------------------------------------------------------------------------- bool C_BaseAnimating::GetSequenceMovement( int nSequence, float fromCycle, float toCycle, Vector &deltaPosition, QAngle &deltaAngles ) { CStudioHdr *pstudiohdr = GetModelPtr( ); if (! pstudiohdr) return false; return Studio_SeqMovement( pstudiohdr, nSequence, fromCycle, toCycle, m_flPoseParameter, deltaPosition, deltaAngles ); } //----------------------------------------------------------------------------- // Purpose: // // Input : *pVec - //----------------------------------------------------------------------------- void C_BaseAnimating::GetBlendedLinearVelocity( Vector *pVec ) { Vector vecDist; float flDuration = GetSequenceLinearMotionAndDuration( GetSequence(), &vecDist ); VectorScale( vecDist, 1.0 / flDuration, *pVec ); Vector tmp; for (int i = m_SequenceTransitioner.m_animationQueue.Count() - 2; i >= 0; i--) { CAnimationLayer *blend = &m_SequenceTransitioner.m_animationQueue[i]; float flWeight = blend->GetFadeout( gpGlobals->curtime ); if ( flWeight == 0.0f ) continue; flDuration = GetSequenceLinearMotionAndDuration( blend->GetSequence(), &vecDist ); VectorScale( vecDist, 1.0 / flDuration, tmp ); *pVec = Lerp( flWeight, *pVec, tmp ); } } //----------------------------------------------------------------------------- // Purpose: convert local movement into pose parameters that take account of blending // Input: vecLocalVelocity - local velocity in right-hand-rule coordinates // iMoveX, iMoveY - pose parameter indexes for movement // Output : //----------------------------------------------------------------------------- #define MOVEMENT_ERROR_LIMIT 1.0 void C_BaseAnimating::SetMovementPoseParams( const Vector &vecLocalVelocity, int iMoveX, int iMoveY, int iXSign, int iYSign ) { CStudioHdr *pStudioHdr = GetModelPtr( ); if (! pStudioHdr) return; Vector2D vecCurrentPose( 0.0f, 0.0f ); // set the pose parameters to the correct direction, but not value if ( vecLocalVelocity.x != 0.0f && fabs( vecLocalVelocity.x ) > fabs( vecLocalVelocity.y ) ) { vecCurrentPose.x = ((vecLocalVelocity.x < 0.0) ? -iXSign : iXSign); vecCurrentPose.y = iYSign * (vecLocalVelocity.y / fabs( vecLocalVelocity.x )); } else if (vecLocalVelocity.y != 0.0f) { vecCurrentPose.x = iXSign * (vecLocalVelocity.x / fabs( vecLocalVelocity.y )); vecCurrentPose.y = ((vecLocalVelocity.y < 0.0) ? -iYSign : iYSign); } if (vecCurrentPose.x == 0.0f && vecCurrentPose.y == 0.0f) { SetPoseParameter( pStudioHdr, iMoveX, vecCurrentPose.x ); SetPoseParameter( pStudioHdr, iMoveY, vecCurrentPose.y ); return; } // refine pose parameters to be more accurate int i = 0; float dx, dy; Vector vecAnimVelocity; // Set the initial 9-way blend movement pose parameters. SetPoseParameter( pStudioHdr, iMoveX, vecCurrentPose.x ); SetPoseParameter( pStudioHdr, iMoveY, vecCurrentPose.y ); /* if ( r_sequence_debug.GetInt() == entindex() ) { DevMsgRT("%d (%d) : %.2f %.2f : %.2f %.2f : %.3f %.3f\n", entindex(), -1, vecLocalVelocity.x, vecLocalVelocity.y, vecAnimVelocity.x, vecAnimVelocity.y, vecCurrentPose.x, vecCurrentPose.y ); } */ bool retry = true; do { GetBlendedLinearVelocity( &vecAnimVelocity ); // adjust X pose parameter based on movement error if (fabs( vecAnimVelocity.x ) > 0.001) { vecCurrentPose.x *= vecLocalVelocity.x / vecAnimVelocity.x; } else { // too slow, set to zero so it can optimized out during bone setup vecCurrentPose.x = 0; } SetPoseParameter( pStudioHdr, iMoveX, vecCurrentPose.x ); // adjust Y pose parameter based on movement error if (fabs( vecAnimVelocity.y ) > 0.001) { vecCurrentPose.y *= vecLocalVelocity.y / vecAnimVelocity.y; } else { // too slow, set to zero so it can optimized out during bone setup vecCurrentPose.y = 0; } SetPoseParameter( pStudioHdr, iMoveY, vecCurrentPose.y ); /* if ( r_sequence_debug.GetInt() == entindex() ) { DevMsgRT("%d (%d) : %.2f %.2f : %.2f %.2f : %.3f %.3f\n", entindex(), i, vecLocalVelocity.x, vecLocalVelocity.y, vecAnimVelocity.x, vecAnimVelocity.y, vecCurrentPose.x, vecCurrentPose.y ); } */ dx = vecLocalVelocity.x - vecAnimVelocity.x; dy = vecLocalVelocity.y - vecAnimVelocity.y; retry = (vecCurrentPose.x < 1.0 && vecCurrentPose.x > -1.0) && (dx < -MOVEMENT_ERROR_LIMIT || dx > MOVEMENT_ERROR_LIMIT); retry = retry || ((vecCurrentPose.y < 1.0 && vecCurrentPose.y > -1.0) && (dy < -MOVEMENT_ERROR_LIMIT || dy > MOVEMENT_ERROR_LIMIT)); } while (i++ < 5 && retry); } //----------------------------------------------------------------------------- // Purpose: // Input : flInterval - // Output : float //----------------------------------------------------------------------------- float C_BaseAnimating::FrameAdvance( float flInterval ) { CStudioHdr *hdr = GetModelPtr(); if ( !hdr ) return 0.0f; bool bWatch = false; // Q_strstr( hdr->name, "commando" ) ? true : false; 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 * GetPlaybackRate(); m_prevClientCycle = GetCycle(); m_prevClientAnimTime = m_flAnimTime; 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 (flNewCycle >= 1.0f) { ReachedEndOfSequence(); } if ( IsSequenceLooping( hdr, GetSequence() ) ) { flNewCycle -= (int)(flNewCycle); } else { flNewCycle = (flNewCycle < 0.0f) ? 0.0f : 1.0f; } m_bSequenceFinished = true; } SetCycle( flNewCycle ); InvalidatePhysicsRecursive( ANIMATION_CHANGED ); m_flGroundSpeed = GetSequenceGroundSpeed( hdr, GetSequence() ); return flInterval; } // Stubs for weapon prediction void C_BaseAnimating::ResetSequenceInfo( void ) { if (GetSequence() == -1) { SetSequence( 0 ); } /* if (r_sequence_debug.GetInt() == entindex() ) { DevMsgRT("%d : client reset %s\n", entindex(), GetSequenceName( GetSequence() ) ); } */ m_flPlaybackRate = 1.0; m_bSequenceFinished = false; m_flLastEventCheck = 0; if ( !IsPlayer() ) { m_nNewSequenceParity = ( ++m_nNewSequenceParity ) & EF_PARITY_MASK; } m_nResetEventsParity = ( ++m_nResetEventsParity ) & EF_PARITY_MASK; } //========================================================= //========================================================= 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::SetSkin( int iSkin ) { if ( m_nSkin != iSkin ) { m_nSkin = iSkin; OnTranslucencyTypeChanged(); } } void C_BaseAnimating::SetBody( int iBody ) { if ( m_nBody != iBody ) { m_nBody = iBody; OnTranslucencyTypeChanged(); } } void C_BaseAnimating::SetBodygroup( int iGroup, int iValue ) { Assert( GetModelPtr() ); int nOldBody = m_nBody; ::SetBodygroup( GetModelPtr( ), m_nBody, iGroup, iValue ); if ( nOldBody != m_nBody ) { OnTranslucencyTypeChanged(); } } int C_BaseAnimating::GetBodygroup( int iGroup ) { Assert( GetModelPtr() ); return ::GetBodygroup( GetModelPtr( ), m_nBody, iGroup ); } const char *C_BaseAnimating::GetBodygroupName( int iGroup ) { Assert( GetModelPtr() ); return ::GetBodygroupName( GetModelPtr( ), iGroup ); } int C_BaseAnimating::FindBodygroupByName( const char *name ) { Assert( GetModelPtr() ); return ::FindBodygroupByName( GetModelPtr( ), name ); } int C_BaseAnimating::GetBodygroupCount( int iGroup ) { Assert( GetModelPtr() ); return ::GetBodygroupCount( GetModelPtr( ), iGroup ); } int C_BaseAnimating::GetNumBodyGroups( void ) { Assert( GetModelPtr() ); return ::GetNumBodyGroups( GetModelPtr( ) ); } //----------------------------------------------------------------------------- // Purpose: // Input : setnum - //----------------------------------------------------------------------------- void C_BaseAnimating::SetHitboxSet( int setnum ) { #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 ) { 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 ) { return ::GetHitboxSetName( GetModelPtr(), m_nHitboxSet ); } //----------------------------------------------------------------------------- // Purpose: // Output : int //----------------------------------------------------------------------------- int C_BaseAnimating::GetHitboxSetCount( void ) { 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, -1 ); } int C_BaseAnimating::SelectWeightedSequenceFromModifiers( Activity activity, CUtlSymbol *pActivityModifiers, int iModifierCount ) { Assert( activity != ACT_INVALID ); Assert( GetModelPtr() ); return GetModelPtr()->SelectWeightedSequenceFromModifiers( activity, pActivityModifiers, iModifierCount ); } //========================================================= //========================================================= int C_BaseAnimating::LookupPoseParameter( CStudioHdr *pstudiohdr, const char *szName ) { if ( !pstudiohdr ) return -1; 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; } Assert( IsFinite( flValue ) ); if ( iParameter >= 0 ) { float flNewValue; flValue = Studio_SetPoseParameter( pStudioHdr, iParameter, flValue, flNewValue ); m_flPoseParameter[ iParameter ] = flNewValue; } return flValue; } float C_BaseAnimating::GetPoseParameter( int iParameter ) { CStudioHdr *pStudioHdr = GetModelPtr(); if ( pStudioHdr == NULL ) return 0.0f; if ( !pStudioHdr ) { Assert(!"C_BaseAnimating::SetPoseParameter: model missing"); return 0.0f; } if ( iParameter >= 0 ) { return Studio_GetPoseParameter( pStudioHdr, iParameter, m_flPoseParameter[ iParameter ] ); } return 0.0f; } //----------------------------------------------------------------------------- // 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 ) { Q_memset(&m_mouth, 0, sizeof(m_mouth)); 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->GetRenderOrigin(); *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 ); } //----------------------------------------------------------------------------- // 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; matrix3x4_t *hitboxbones[MAXSTUDIOBONES]; HitboxToWorldTransforms( hitboxbones ); // 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; matrix3x4_t *hitboxbones[MAXSTUDIOBONES]; HitboxToWorldTransforms( hitboxbones ); // 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 ) { if ( m_flModelScale != scale ) { m_flModelScale = scale; InvalidatePhysicsRecursive( BOUNDS_CHANGED ); } } //----------------------------------------------------------------------------- // 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, const RenderableInstance_t &instance ); 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, const RenderableInstance_t &instance ) { 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 ); if ( !pCollide ) { DevWarning("Failed to get collision model (%d, %d), %s (%s)\n", m_modelIndex, m_solidIndex, modelinfo->GetModelName(modelinfo->GetModel(m_modelIndex)), IsDormant() ? "dormant" : "active" ); return false; } 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 (!engine->Con_IsVisible()) { va_list argptr; va_start( argptr, pMsg ); // { static char string[1024]; Q_vsnprintf (string, sizeof( string ), pMsg, argptr); DevMsg( 1, "%s", string ); } // DevMsg( pMsg, argptr ); va_end( argptr ); } } void C_BaseAnimating::ForceClientSideAnimationOn() { m_bClientSideAnimation = true; AddToClientSideAnimationList(); } void C_BaseAnimating::AddToClientSideAnimationList() { // Already in list if ( m_ClientSideAnimationListHandle != INVALID_CLIENTSIDEANIMATION_LIST_HANDLE ) return; clientanimating_t list( this, 0 ); m_ClientSideAnimationListHandle = g_ClientSideAnimationList.AddToTail( list ); ClientSideAnimationChanged(); } void C_BaseAnimating::RemoveFromClientSideAnimationList() { // Not in list yet if ( INVALID_CLIENTSIDEANIMATION_LIST_HANDLE == m_ClientSideAnimationListHandle ) return; unsigned int c = g_ClientSideAnimationList.Count(); Assert( m_ClientSideAnimationListHandle < c ); unsigned int last = c - 1; if ( last == m_ClientSideAnimationListHandle ) { // Just wipe the final entry g_ClientSideAnimationList.FastRemove( last ); } else { clientanimating_t lastEntry = g_ClientSideAnimationList[ last ]; // Remove the last entry g_ClientSideAnimationList.FastRemove( last ); // And update it's handle to point to this slot. lastEntry.pAnimating->m_ClientSideAnimationListHandle = m_ClientSideAnimationListHandle; g_ClientSideAnimationList[ m_ClientSideAnimationListHandle ] = lastEntry; } // Invalidate our handle no matter what. m_ClientSideAnimationListHandle = INVALID_CLIENTSIDEANIMATION_LIST_HANDLE; } // static method void C_BaseAnimating::UpdateClientSideAnimations() { VPROF_BUDGET( "UpdateClientSideAnimations", VPROF_BUDGETGROUP_CLIENT_ANIMATION ); int c = g_ClientSideAnimationList.Count(); for ( int i = 0; i < c ; ++i ) { clientanimating_t &anim = g_ClientSideAnimationList.Element(i); if ( !(anim.flags & FCLIENTANIM_SEQUENCE_CYCLE) ) continue; Assert( anim.pAnimating ); anim.pAnimating->UpdateClientSideAnimation(); } } CBoneList *C_BaseAnimating::RecordBones( CStudioHdr *hdr, matrix3x4_t *pBoneState ) { if ( !ToolsEnabled() ) return NULL; VPROF_BUDGET( "C_BaseAnimating::RecordBones", VPROF_BUDGETGROUP_TOOLS ); // Possible optimization: Instead of inverting everything while recording, record the pos/q stuff into a structure instead? Assert( hdr ); // Setup our transform based on render angles and origin. matrix3x4_t parentTransform; AngleMatrix( GetRenderAngles(), GetRenderOrigin(), parentTransform ); Assert( !m_bBoneListInUse ); CBoneList *boneList = m_bBoneListInUse ? CBoneList::Alloc() : &m_recordingBoneList; m_bBoneListInUse = true; 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(); matrix3x4a_t *pBones = (matrix3x4a_t*)stackalloc( ( 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 = GetBody(); state.m_nSequence = m_nSequence; state.m_pBoneList = hdr ? RecordBones( hdr, pBones ) : NULL; msg->SetPtr( "baseanimating", &state ); msg->SetBool( "viewmodel", IsViewModelOrAttachment() ); if ( IsViewModel() ) { C_BaseViewModel *pViewModel = assert_cast< C_BaseViewModel* >( this ); C_BaseCombatWeapon *pWeapon = pViewModel->GetOwningWeapon(); if ( pWeapon ) { pWeapon->GetToolViewModelState( msg ); } } } void C_BaseAnimating::CleanupToolRecordingState( KeyValues *msg ) { if ( !ToolsEnabled() ) return; BaseAnimatingRecordingState_t *pState = (BaseAnimatingRecordingState_t*)msg->GetPtr( "baseanimating" ); if ( pState && pState->m_pBoneList ) { if ( pState->m_pBoneList != &m_recordingBoneList ) { pState->m_pBoneList->Release(); } else { Assert( m_bBoneListInUse ); m_bBoneListInUse = false; } } 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: 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 ); } bool C_BaseAnimating::m_bBoneListInUse = false; CBoneList C_BaseAnimating::m_recordingBoneList;