//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include "cbase.h" #include "dod_playeranimstate.h" #include "base_playeranimstate.h" #include "tier0/vprof.h" #include "animation.h" #include "studio.h" #include "apparent_velocity_helper.h" #include "utldict.h" #include "weapon_dodbase.h" #include "dod_shareddefs.h" #ifdef CLIENT_DLL #include "c_dod_player.h" #include "engine/ivdebugoverlay.h" #include "filesystem.h" ConVar anim_showmainactivity( "anim_showmainactivity", "0", FCVAR_CHEAT, "Show the idle, walk, run, and/or sprint activities." ); #else #include "dod_player.h" #endif ConVar anim_showstate( "anim_showstate", "-1", FCVAR_CHEAT | FCVAR_REPLICATED, "Show the (client) animation state for the specified entity (-1 for none)." ); ConVar anim_showstatelog( "anim_showstatelog", "0", FCVAR_CHEAT | FCVAR_REPLICATED, "1 to output anim_showstate to Msg(). 2 to store in AnimState.log. 3 for both." ); ConVar dod_bodyheightoffset( "dod_bodyheightoffset", "4", FCVAR_CHEAT | FCVAR_REPLICATED, "Deploy height offset." ); #define ANIMPART_STAND "stand" #define ANIMPART_PRONE "prone" #define ANIMPART_CROUCH "crouch" #define ANIMPART_SPRINT "sprint" #define ANIMPART_SANDBAG "sandbag" #define ANIMPART_BIPOD "bipod" // When moving this fast, he plays run anim. #define ARBITRARY_RUN_SPEED 300.0f #define DOD_BODYYAW_RATE 720.0f #define DOD_WALK_SPEED 60.0f #define DOD_RUN_SPEED 120.0f #define DOD_SPRINT_SPEED 260.0f class CDODPlayerAnimState : public CBasePlayerAnimState, public IDODPlayerAnimState { public: DECLARE_CLASS( CDODPlayerAnimState, CBasePlayerAnimState ); friend IDODPlayerAnimState* CreatePlayerAnimState( CDODPlayer *pPlayer ); CDODPlayerAnimState(); virtual void ShowDebugInfo( void ); // This is called by both the client and the server in the same way to trigger events for // players firing, jumping, throwing grenades, etc. virtual void DoAnimationEvent( PlayerAnimEvent_t event, int nData ); virtual void ClearAnimationState(); virtual Activity CalcMainActivity(); virtual void Update( float eyeYaw, float eyePitch ); virtual void DebugShowAnimState( int iStartLine ); virtual int CalcAimLayerSequence( float *flCyle, float *flAimSequenceWeight, bool bForceIdle ) { return 0; } virtual float GetCurrentMaxGroundSpeed(); virtual void ComputeSequences( CStudioHdr *pStudioHdr ); virtual void ClearAnimationLayers(); virtual void RestartMainSequence(); virtual float CalcMovementPlaybackRate( bool *bIsMoving ); Activity TranslateActivity( Activity actDesired ); void CancelGestures( void ); protected: // Pose paramters. bool SetupPoseParameters( CStudioHdr *pStudioHdr ); virtual void ComputePoseParam_MoveYaw( CStudioHdr *pStudioHdr ); virtual void ComputePoseParam_AimPitch( CStudioHdr *pStudioHdr ); virtual void ComputePoseParam_AimYaw( CStudioHdr *pStudioHdr ); void ComputePoseParam_BodyHeight( CStudioHdr *pStudioHdr ); virtual void EstimateYaw( void ); void ConvergeYawAngles( float flGoalYaw, float flYawRate, float flDeltaTime, float &flCurrentYaw ); void ComputeFireSequence(); void ComputeDeployedSequence(); void ComputeGestureSequence( CStudioHdr *pStudioHdr ); void RestartGesture( int iGestureType, Activity act, bool bAutoKill = true ); void UpdateLayerSequenceGeneric( CStudioHdr *pStudioHdr, int iLayer, bool &bEnabled, float &flCurCycle, int &iSequence, bool bWaitAtEnd, float flWeight = 1.0 ); void DebugShowAnimStateForPlayer( bool bIsServer ); void DebugShowEyeYaw( void ); // Client specific. #ifdef CLIENT_DLL // Debug. void DebugShowActivity( Activity activity ); #endif private: void InitDOD( CDODPlayer *pPlayer ); bool HandleJumping( Activity *idealActivity ); bool HandleProne( Activity *idealActivity ); bool HandleProneDown( CDODPlayer *pPlayer, Activity *idealActivity ); bool HandleProneUp( CDODPlayer *pPlayer, Activity *idealActivity ); bool HandleDucked( Activity *idealActivity ); bool IsGettingDown( CDODPlayer *pPlayer ); bool IsGettingUp( CDODPlayer *pPlayer ); CDODPlayer* GetOuterDOD() const; bool IsPlayingGesture( int type ) { return ( m_bPlayingGesture && m_iGestureType == type ); } private: // Current state variables. bool m_bJumping; // Set on a jump event. float m_flJumpStartTime; bool m_bFirstJumpFrame; // These control the prone state _achine. bool m_bGettingDown; bool m_bGettingUp; bool m_bWasGoingProne; bool m_bWasGettingUp; // The single Gesture layer bool m_bPlayingGesture; bool m_bAutokillGesture; int m_iGestureSequence; float m_flGestureCycle; int m_iGestureType; enum { GESTURE_NONE = -1, GESTURE_ATTACK1 = 0, GESTURE_ATTACK2, GESTURE_RELOAD, GESTURE_HAND_SIGNAL, GESTURE_FIDGET, GESTURE_PLANT, GESTURE_DEFUSE, }; // Pose parameters. bool m_bPoseParameterInit; float m_flEstimateYaw; float m_flEstimateVelocity; float m_flLastAimPitch; float m_flLastAimYaw; float m_flLastBodyHeight; float m_flLastAimTurnTime; Vector2D m_vecLastMoveYaw; int m_iMoveX; int m_iMoveY; int m_iAimYaw; int m_iAimPitch; int m_iBodyHeight; float m_flFireIdleTime; // Time that we drop our gun bool m_bLastDeployState; // true = last was deployed, false = last was not deployed DODWeaponID m_iLastWeaponID; // remember the weapon we were last using // Our DOD player pointer. CDODPlayer *m_pOuterDOD; }; IDODPlayerAnimState* CreatePlayerAnimState( CDODPlayer *pPlayer ) { CDODPlayerAnimState *pState = new CDODPlayerAnimState; pState->InitDOD( pPlayer ); return pState; } // -------------------------------------------------------------------------------- // // CDODPlayerAnimState implementation. // -------------------------------------------------------------------------------- // CDODPlayerAnimState::CDODPlayerAnimState() { m_bGettingDown = false; m_bGettingUp = false; m_bWasGoingProne = false; m_bWasGettingUp = false; m_pOuterDOD = NULL; m_bPoseParameterInit = false; m_flEstimateYaw = 0.0f; m_flLastAimPitch = 0.0f; m_flLastAimYaw = 0.0f; m_flLastBodyHeight = 0.0f; m_flLastAimTurnTime = 0.0f; m_vecLastMoveYaw.Init(); m_iMoveX = -1; m_iMoveY = -1; m_iAimYaw = -1; m_iAimPitch = -1; m_iBodyHeight = -1; } void CDODPlayerAnimState::InitDOD( CDODPlayer *pPlayer ) { m_pOuterDOD = pPlayer; CModAnimConfig config; config.m_flMaxBodyYawDegrees = 45; config.m_LegAnimType = LEGANIM_GOLDSRC; config.m_bUseAimSequences = false; BaseClass::Init( pPlayer, config ); } void CDODPlayerAnimState::ClearAnimationState() { m_bJumping = false; m_flFireIdleTime = 0; m_bLastDeployState = false; m_iLastWeaponID = WEAPON_NONE; CancelGestures(); BaseClass::ClearAnimationState(); } void CDODPlayerAnimState::DoAnimationEvent( PlayerAnimEvent_t event, int nData ) { if ( event == PLAYERANIMEVENT_FIRE_GUN ) { RestartGesture( GESTURE_ATTACK1, ACT_RANGE_ATTACK1, false ); if( GetOuterDOD()->m_Shared.IsBazookaDeployed() ) { m_flFireIdleTime = gpGlobals->curtime + 0.1; // don't hold this pose after firing } else { // hold last frame of fire pose for 2 seconds ( if we are moving ) m_flFireIdleTime = gpGlobals->curtime + 2; } } if ( event == PLAYERANIMEVENT_SECONDARY_ATTACK ) { CancelGestures(); RestartGesture( GESTURE_ATTACK2, ACT_RANGE_ATTACK2 ); } else if ( event == PLAYERANIMEVENT_RELOAD ) { CancelGestures(); RestartGesture( GESTURE_RELOAD, ACT_RELOAD ); } else if ( event == PLAYERANIMEVENT_THROW_GRENADE ) { CancelGestures(); RestartGesture( GESTURE_ATTACK1, ACT_RANGE_ATTACK1 ); } else if ( event == PLAYERANIMEVENT_ROLL_GRENADE ) { CancelGestures(); RestartGesture( GESTURE_ATTACK2, ACT_RANGE_ATTACK2 ); } else if ( event == PLAYERANIMEVENT_JUMP ) { // Play the jump animation. m_bJumping = true; m_bFirstJumpFrame = true; RestartMainSequence(); m_flJumpStartTime = gpGlobals->curtime; } else if ( event == PLAYERANIMEVENT_HANDSIGNAL ) { CDODPlayer *pPlayer = GetOuterDOD(); if ( pPlayer && !( pPlayer->m_Shared.IsBazookaDeployed() || pPlayer->m_Shared.IsProne() || pPlayer->m_Shared.IsProneDeployed() || pPlayer->m_Shared.IsSniperZoomed() || pPlayer->m_Shared.IsSandbagDeployed() ) ) { CancelGestures(); RestartGesture( GESTURE_HAND_SIGNAL, ACT_DOD_HS_IDLE ); } } else if ( event == PLAYERANIMEVENT_PLANT_TNT ) { CancelGestures(); RestartGesture( GESTURE_PLANT, ACT_DOD_PLANT_TNT ); } else if ( event == PLAYERANIMEVENT_DEFUSE_TNT ) { CancelGestures(); RestartGesture( GESTURE_DEFUSE, ACT_DOD_DEFUSE_TNT ); } } void CDODPlayerAnimState::ShowDebugInfo( void ) { if ( anim_showstate.GetInt() == m_pOuter->entindex() ) { DebugShowAnimStateForPlayer( m_pOuter->IsServer() ); } } void CDODPlayerAnimState::RestartMainSequence() { CancelGestures(); BaseClass::RestartMainSequence(); } bool CDODPlayerAnimState::HandleJumping( Activity *idealActivity ) { if ( m_bJumping ) { if ( m_bFirstJumpFrame ) { m_bFirstJumpFrame = false; RestartMainSequence(); // Reset the animation. } // Don't check if he's on the ground for a sec.. sometimes the client still has the // on-ground flag set right when the message comes in. if ( gpGlobals->curtime - m_flJumpStartTime > 0.2f ) { if ( m_pOuter->GetFlags() & FL_ONGROUND ) { m_bJumping = false; RestartMainSequence(); } } } if ( m_bJumping ) { *idealActivity = ACT_HOP; return true; } else { return false; } } //----------------------------------------------------------------------------- // Purpose: Handle the prone up animation. //----------------------------------------------------------------------------- bool CDODPlayerAnimState::HandleProneDown( CDODPlayer *pPlayer, Activity *idealActivity ) { if ( ( pPlayer->GetCycle() > 0.99f ) || ( pPlayer->m_Shared.IsProne() ) ) { *idealActivity = ACT_PRONE_IDLE; if ( GetOuterXYSpeed() > MOVING_MINIMUM_SPEED ) { *idealActivity = ACT_PRONE_FORWARD; } RestartMainSequence(); m_bGettingDown = false; } else { *idealActivity = ACT_GET_DOWN_STAND; if ( pPlayer->GetFlags() & FL_DUCKING ) { *idealActivity = ACT_GET_DOWN_CROUCH; } } return true; } //----------------------------------------------------------------------------- // Purpose: Handle the prone up animation. //----------------------------------------------------------------------------- bool CDODPlayerAnimState::HandleProneUp( CDODPlayer *pPlayer, Activity *idealActivity ) { if ( ( m_pOuter->GetCycle() > 0.99f ) || ( !pPlayer->m_Shared.IsGettingUpFromProne() ) ) { m_bGettingUp = false; RestartMainSequence(); return false; } *idealActivity = ACT_GET_UP_STAND; if ( pPlayer->GetFlags() & FL_DUCKING ) { *idealActivity = ACT_GET_UP_CROUCH; } return true; } //----------------------------------------------------------------------------- // Purpose: Handle the prone animations. //----------------------------------------------------------------------------- bool CDODPlayerAnimState::HandleProne( Activity *idealActivity ) { // Get the player. CDODPlayer *pPlayer = GetOuterDOD(); if ( !pPlayer ) return false; // Find the leading edge on going prone. bool bChange = pPlayer->m_Shared.IsGoingProne() && !m_bWasGoingProne; m_bWasGoingProne = pPlayer->m_Shared.IsGoingProne(); if ( bChange ) { m_bGettingDown = true; RestartMainSequence(); } // Find the leading edge on getting up. bChange = pPlayer->m_Shared.IsGettingUpFromProne() && !m_bWasGettingUp; m_bWasGettingUp = pPlayer->m_Shared.IsGettingUpFromProne(); if ( bChange ) { m_bGettingUp = true; RestartMainSequence(); } // Handle the transitions. if ( m_bGettingDown ) { return HandleProneDown( pPlayer, idealActivity ); } else if ( m_bGettingUp ) { return HandleProneUp( pPlayer, idealActivity ); } // Handle the prone state. if ( pPlayer->m_Shared.IsProne() ) { *idealActivity = ACT_PRONE_IDLE; if ( GetOuterXYSpeed() > MOVING_MINIMUM_SPEED ) { *idealActivity = ACT_PRONE_FORWARD; } return true; } return false; } bool CDODPlayerAnimState::HandleDucked( Activity *idealActivity ) { if ( m_pOuter->GetFlags() & FL_DUCKING ) { if ( GetOuterXYSpeed() > MOVING_MINIMUM_SPEED ) *idealActivity = ACT_RUN_CROUCH; else *idealActivity = ACT_CROUCHIDLE; return true; } else { return false; } } Activity CDODPlayerAnimState::CalcMainActivity() { Activity idealActivity = ACT_IDLE; float flSpeed = GetOuterXYSpeed(); if ( HandleJumping( &idealActivity ) || HandleProne( &idealActivity ) || HandleDucked( &idealActivity ) ) { // intentionally blank } else { if ( flSpeed > MOVING_MINIMUM_SPEED ) { if( flSpeed >= DOD_SPRINT_SPEED ) { idealActivity = ACT_SPRINT; // If we sprint, cancel the fire idle time CancelGestures(); } else if( flSpeed >= DOD_WALK_SPEED ) idealActivity = ACT_RUN; else idealActivity = ACT_WALK; } } // Shouldn't be here but we need to ship - bazooka deployed reload/running check. if ( IsPlayingGesture( GESTURE_RELOAD ) ) { if ( flSpeed >= DOD_RUN_SPEED && m_pOuterDOD->m_Shared.IsBazookaOnlyDeployed() ) { CancelGestures(); } } ShowDebugInfo(); // Client specific. #ifdef CLIENT_DLL if ( anim_showmainactivity.GetBool() ) { DebugShowActivity( idealActivity ); } #endif return idealActivity; } void CDODPlayerAnimState::CancelGestures( void ) { m_bPlayingGesture = false; m_iGestureType = GESTURE_NONE; #ifdef CLIENT_DLL m_iGestureSequence = -1; #else m_pOuter->RemoveAllGestures(); #endif } void CDODPlayerAnimState::RestartGesture( int iGestureType, Activity act, bool bAutoKill /* = true */ ) { Activity idealActivity = TranslateActivity( act ); m_bPlayingGesture = true; m_iGestureType = iGestureType; #ifdef CLIENT_DLL m_iGestureSequence = m_pOuter->SelectWeightedSequence( idealActivity ); if( m_iGestureSequence == -1 ) { m_bPlayingGesture = false; } m_flGestureCycle = 0.0f; m_bAutokillGesture = bAutoKill; #else m_pOuterDOD->RestartGesture( idealActivity, true, bAutoKill ); #endif } Activity CDODPlayerAnimState::TranslateActivity( Activity actDesired ) { Activity idealActivity = actDesired; if ( m_pOuterDOD->m_Shared.IsSandbagDeployed() ) { switch( idealActivity ) { case ACT_IDLE: idealActivity = ACT_DOD_DEPLOYED; break; case ACT_RANGE_ATTACK1: idealActivity = ACT_DOD_PRIMARYATTACK_DEPLOYED; break; case ACT_RELOAD: idealActivity = ACT_DOD_RELOAD_DEPLOYED; break; default: break; } } else if ( m_pOuterDOD->m_Shared.IsProneDeployed() ) { switch( idealActivity ) { case ACT_PRONE_IDLE: idealActivity = ACT_DOD_PRONE_DEPLOYED; break; case ACT_RANGE_ATTACK1: idealActivity = ACT_DOD_PRIMARYATTACK_PRONE_DEPLOYED; break; case ACT_RELOAD: idealActivity = ACT_DOD_RELOAD_PRONE_DEPLOYED; break; default: break; } } else if ( m_pOuterDOD->m_Shared.IsSniperZoomed() || m_pOuterDOD->m_Shared.IsBazookaDeployed() ) { switch( idealActivity ) { case ACT_IDLE: idealActivity = ACT_DOD_IDLE_ZOOMED; break; case ACT_WALK: idealActivity = ACT_DOD_WALK_ZOOMED; break; case ACT_CROUCHIDLE: idealActivity = ACT_DOD_CROUCH_ZOOMED; break; case ACT_RUN_CROUCH: idealActivity = ACT_DOD_CROUCHWALK_ZOOMED; break; case ACT_PRONE_IDLE: idealActivity = ACT_DOD_PRONE_ZOOMED; break; case ACT_PRONE_FORWARD: idealActivity = ACT_DOD_PRONE_FORWARD_ZOOMED; break; case ACT_RANGE_ATTACK1: if ( m_pOuterDOD->m_Shared.IsSniperZoomed() ) { if( m_pOuterDOD->m_Shared.IsProne() ) idealActivity = ACT_DOD_PRIMARYATTACK_PRONE; } break; case ACT_RELOAD: if ( m_pOuterDOD->m_Shared.IsBazookaDeployed() ) { if( m_pOuterDOD->m_Shared.IsProne() ) { idealActivity = ACT_DOD_RELOAD_PRONE_DEPLOYED; } else { idealActivity = ACT_DOD_RELOAD_DEPLOYED; } } break; default: break; } } else if ( m_pOuterDOD->m_Shared.IsProne() ) { // translate prone shooting, reload, handsignal switch( idealActivity ) { case ACT_RANGE_ATTACK1: idealActivity = ACT_DOD_PRIMARYATTACK_PRONE; break; case ACT_RANGE_ATTACK2: idealActivity = ACT_DOD_SECONDARYATTACK_PRONE; break; case ACT_RELOAD: idealActivity = ACT_DOD_RELOAD_PRONE; break; default: break; } } else if ( m_pOuter->GetFlags() & FL_DUCKING ) { switch( idealActivity ) { case ACT_RANGE_ATTACK1: idealActivity = ACT_DOD_PRIMARYATTACK_CROUCH; break; case ACT_RANGE_ATTACK2: idealActivity = ACT_DOD_SECONDARYATTACK_CROUCH; break; case ACT_DOD_HS_IDLE: idealActivity = ACT_DOD_HS_CROUCH; break; } } // Are our guns at fire or rest? if ( m_flFireIdleTime > gpGlobals->curtime ) { switch( idealActivity ) { case ACT_IDLE: idealActivity = ACT_DOD_STAND_AIM; break; case ACT_CROUCHIDLE: idealActivity = ACT_DOD_CROUCH_AIM; break; case ACT_RUN_CROUCH: idealActivity = ACT_DOD_CROUCHWALK_AIM; break; case ACT_WALK: idealActivity = ACT_DOD_WALK_AIM; break; case ACT_RUN: idealActivity = ACT_DOD_RUN_AIM; break; default: break; } } else { switch( idealActivity ) { case ACT_IDLE: idealActivity = ACT_DOD_STAND_IDLE; break; case ACT_CROUCHIDLE: idealActivity = ACT_DOD_CROUCH_IDLE; break; case ACT_RUN_CROUCH: idealActivity = ACT_DOD_CROUCHWALK_IDLE; break; case ACT_WALK: idealActivity = ACT_DOD_WALK_IDLE; break; case ACT_RUN: idealActivity = ACT_DOD_RUN_IDLE; break; default: break; } } return m_pOuterDOD->TranslateActivity( idealActivity ); } CDODPlayer* CDODPlayerAnimState::GetOuterDOD() const { return m_pOuterDOD; } float CDODPlayerAnimState::GetCurrentMaxGroundSpeed() { return PLAYER_SPEED_SPRINT; } float CDODPlayerAnimState::CalcMovementPlaybackRate( bool *bIsMoving ) { if( ( GetCurrentMainSequenceActivity() == ACT_GET_UP_STAND ) || ( GetCurrentMainSequenceActivity() == ACT_GET_DOWN_STAND ) || ( GetCurrentMainSequenceActivity() == ACT_GET_UP_CROUCH ) || ( GetCurrentMainSequenceActivity() == ACT_GET_DOWN_CROUCH ) ) { // We don't want to change the playback speed of these, even if we move. *bIsMoving = false; return 1.0; } // it would be a good idea to ramp up from 0.5 to 1.0 as they go from stop to moveing, it looks more natural. *bIsMoving = true; return 1.0; } void CDODPlayerAnimState::DebugShowAnimState( int iStartLine ) { #ifdef CLIENT_DLL engine->Con_NPrintf( iStartLine++, "getting down: %s\n", m_bGettingDown ? "yes" : "no" ); engine->Con_NPrintf( iStartLine++, "getting up: %s\n", m_bGettingUp ? "yes" : "no" ); #endif BaseClass::DebugShowAnimState( iStartLine ); } void CDODPlayerAnimState::ComputeSequences( CStudioHdr *pStudioHdr ) { // Reset some things if we're changed weapons // do this before ComputeSequences CWeaponDODBase *pWeapon = GetOuterDOD()->m_Shared.GetActiveDODWeapon(); if ( pWeapon ) { if( pWeapon->GetWeaponID() != m_iLastWeaponID ) { CancelGestures(); m_iLastWeaponID = pWeapon->GetWeaponID(); m_flFireIdleTime = 0; } } BaseClass::ComputeSequences( pStudioHdr ); if( !m_bGettingDown && !m_bGettingUp ) { ComputeFireSequence(); #ifdef CLIENT_DLL ComputeGestureSequence( pStudioHdr ); // get the weapon's swap criteria ( reload? attack? deployed? deployed reload? ) // and determine whether we should use alt model or not CWeaponDODBase *pWeapon = GetOuterDOD()->m_Shared.GetActiveDODWeapon(); if ( pWeapon ) { int iCurrentState = ALTWPN_CRITERIA_NONE; if( m_bPlayingGesture && m_iGestureType == GESTURE_ATTACK1 ) iCurrentState |= ALTWPN_CRITERIA_FIRING; else if( m_bPlayingGesture && m_iGestureType == GESTURE_RELOAD ) iCurrentState |= ALTWPN_CRITERIA_RELOADING; if( m_pOuterDOD->m_Shared.IsProne() ) iCurrentState |= ALTWPN_CRITERIA_PRONE; // always use default model while proning or hand signal if( !IsPlayingGesture( GESTURE_HAND_SIGNAL ) && !IsPlayingGesture( GESTURE_FIDGET ) && !m_bGettingDown && !m_bGettingUp ) { pWeapon->CheckForAltWeapon( iCurrentState ); } else { pWeapon->SetUseAltModel( false ); } } #endif } } #define GESTURE_LAYER AIMSEQUENCE_LAYER #define NUM_LAYERS_WANTED (GESTURE_LAYER + 1) void CDODPlayerAnimState::ClearAnimationLayers() { if ( !m_pOuter ) return; m_pOuter->SetNumAnimOverlays( NUM_LAYERS_WANTED ); for ( int i=0; i < m_pOuter->GetNumAnimOverlays(); i++ ) { m_pOuter->GetAnimOverlay( i )->SetOrder( CBaseAnimatingOverlay::MAX_OVERLAYS ); } } void CDODPlayerAnimState::ComputeFireSequence( void ) { // Hold the shoot pose for a time after firing, unless we stand still if( m_flFireIdleTime < gpGlobals->curtime && IsPlayingGesture( GESTURE_ATTACK1 ) && GetOuterXYSpeed() > MOVING_MINIMUM_SPEED ) { CancelGestures(); } if( GetOuterDOD()->m_Shared.IsInMGDeploy() != m_bLastDeployState ) { CancelGestures(); m_bLastDeployState = GetOuterDOD()->m_Shared.IsInMGDeploy(); } } void CDODPlayerAnimState::ComputeGestureSequence( CStudioHdr *pStudioHdr ) { UpdateLayerSequenceGeneric( pStudioHdr, GESTURE_LAYER, m_bPlayingGesture, m_flGestureCycle, m_iGestureSequence, !m_bAutokillGesture ); } void CDODPlayerAnimState::UpdateLayerSequenceGeneric( CStudioHdr *pStudioHdr, int iLayer, bool &bEnabled, float &flCurCycle, int &iSequence, bool bWaitAtEnd, float flWeight /* = 1.0 */ ) { if ( !bEnabled ) return; if( flCurCycle > 1.0 ) flCurCycle = 1.0; // Increment the fire sequence's cycle. flCurCycle += m_pOuter->GetSequenceCycleRate( pStudioHdr, iSequence ) * gpGlobals->frametime; if ( flCurCycle > 1 ) { if ( bWaitAtEnd ) { flCurCycle = 1; } else { // Not firing anymore. bEnabled = false; iSequence = 0; return; } } CAnimationLayer *pLayer = m_pOuter->GetAnimOverlay( iLayer ); pLayer->m_flCycle = flCurCycle; pLayer->m_nSequence = iSequence; pLayer->m_flPlaybackRate = 1.0; pLayer->m_flWeight = flWeight; pLayer->m_nOrder = iLayer; } extern ConVar mp_facefronttime; extern ConVar mp_feetyawrate; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDODPlayerAnimState::Update( float eyeYaw, float eyePitch ) { // Profile the animation update. VPROF( "CDODPlayerAnimState::Update" ); // Get the studio header for the player. CStudioHdr *pStudioHdr = GetOuterDOD()->GetModelPtr(); if ( !pStudioHdr ) return; // Check to see if we should be updating the animation state - dead, ragdolled? if ( !ShouldUpdateAnimState() ) { ClearAnimationState(); return; } // Store the eye angles. m_flEyeYaw = AngleNormalize( eyeYaw ); m_flEyePitch = AngleNormalize( eyePitch ); // Clear animation overlays because we're about to completely reconstruct them. ClearAnimationLayers(); // Compute the player sequences. ComputeSequences( pStudioHdr ); if ( SetupPoseParameters( pStudioHdr ) ) { // Pose parameter - what direction are the player's legs running in. ComputePoseParam_MoveYaw( pStudioHdr ); // Pose parameter - Torso aiming (up/down). ComputePoseParam_AimPitch( pStudioHdr ); // Pose parameter - Torso aiming (rotation). ComputePoseParam_AimYaw( pStudioHdr ); // Pose parameter - Body Height (torso elevation). ComputePoseParam_BodyHeight( pStudioHdr ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CDODPlayerAnimState::SetupPoseParameters( CStudioHdr *pStudioHdr ) { // Check to see if this has already been done. if ( m_bPoseParameterInit ) return true; // Save off the pose parameter indices. if ( !pStudioHdr ) return false; // Look for the movement blenders. m_iMoveX = GetOuterDOD()->LookupPoseParameter( pStudioHdr, "move_x" ); m_iMoveY = GetOuterDOD()->LookupPoseParameter( pStudioHdr, "move_y" ); if ( ( m_iMoveX < 0 ) || ( m_iMoveY < 0 ) ) return false; // Look for the aim pitch blender. m_iAimPitch = GetOuterDOD()->LookupPoseParameter( pStudioHdr, "body_pitch" ); if ( m_iAimPitch < 0 ) return false; // Look for aim yaw blender. m_iAimYaw = GetOuterDOD()->LookupPoseParameter( pStudioHdr, "body_yaw" ); if ( m_iAimYaw < 0 ) return false; // Look for the body height blender. m_iBodyHeight = GetOuterDOD()->LookupPoseParameter( pStudioHdr, "body_height" ); if ( m_iBodyHeight < 0 ) return false; m_bPoseParameterInit = true; return true; } #define DOD_MOVEMENT_ERROR_LIMIT 1.0 //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDODPlayerAnimState::ComputePoseParam_MoveYaw( CStudioHdr *pStudioHdr ) { // Check to see if we are deployed or prone. if( GetOuterDOD()->m_Shared.IsInMGDeploy() || GetOuterDOD()->m_Shared.IsProne() ) { // Set the 9-way blend movement pose parameters. Vector2D vecCurrentMoveYaw( 0.0f, 0.0f ); GetOuter()->SetPoseParameter( pStudioHdr, m_iMoveX, vecCurrentMoveYaw.x ); GetOuter()->SetPoseParameter( pStudioHdr, m_iMoveY, vecCurrentMoveYaw.y ); m_vecLastMoveYaw = vecCurrentMoveYaw; #if 0 // Rotate the entire body instantly. m_flGoalFeetYaw = AngleNormalize( m_flEyeYaw ); m_flCurrentFeetYaw = m_flGoalFeetYaw; m_flLastTurnTime = gpGlobals->curtime; // Rotate entire body into position. m_angRender[YAW] = m_flCurrentFeetYaw; m_angRender[PITCH] = m_angRender[ROLL] = 0; SetOuterBodyYaw( m_flCurrentFeetYaw ); g_flLastBodyYaw = m_flCurrentFeetYaw; #endif } else { // Get the estimated movement yaw. EstimateYaw(); // Get the view yaw. float flAngle = AngleNormalize( m_flEyeYaw ); // rotate movement into local reference frame float flYaw = flAngle - m_flEstimateYaw; flYaw = AngleNormalize( -flYaw ); // Get the current speed the character is running. Vector vecEstVelocity; vecEstVelocity.x = cos( DEG2RAD( flYaw ) ) * m_flEstimateVelocity; vecEstVelocity.y = sin( DEG2RAD( flYaw ) ) * m_flEstimateVelocity; Vector2D vecCurrentMoveYaw( 0.0f, 0.0f ); // set the pose parameters to the correct direction, but not value if ( vecEstVelocity.x != 0.0f && fabs( vecEstVelocity.x ) > fabs( vecEstVelocity.y ) ) { vecCurrentMoveYaw.x = (vecEstVelocity.x < 0.0) ? -1.0 : 1.0; vecCurrentMoveYaw.y = vecEstVelocity.y / fabs( vecEstVelocity.x ); } else if (vecEstVelocity.y != 0.0f) { vecCurrentMoveYaw.y = (vecEstVelocity.y < 0.0) ? -1.0 : 1.0; vecCurrentMoveYaw.x = vecEstVelocity.x / fabs( vecEstVelocity.y ); } #ifndef CLIENT_DLL GetOuter()->SetPoseParameter( pStudioHdr, m_iMoveX, vecCurrentMoveYaw.x ); GetOuter()->SetPoseParameter( pStudioHdr, m_iMoveY, vecCurrentMoveYaw.y ); #else // refine pose parameters to be more accurate int i = 0; float dx, dy; Vector vecAnimVelocity; /* if ( m_pOuter->entindex() == 2 ) { GetOuter()->SetPoseParameter( pStudioHdr, m_iMoveX, vecCurrentMoveYaw.x ); GetOuter()->SetPoseParameter( pStudioHdr, m_iMoveY, vecCurrentMoveYaw.y ); GetOuterDOD()->GetBlendedLinearVelocity( &vecAnimVelocity ); DevMsgRT("(%.2f) %.3f : (%.2f) %.3f\n", vecAnimVelocity.x, vecCurrentMoveYaw.x, vecAnimVelocity.y, vecCurrentMoveYaw.y ); } */ bool retry = true; do { // Set the 9-way blend movement pose parameters. GetOuter()->SetPoseParameter( pStudioHdr, m_iMoveX, vecCurrentMoveYaw.x ); GetOuter()->SetPoseParameter( pStudioHdr, m_iMoveY, vecCurrentMoveYaw.y ); GetOuterDOD()->GetBlendedLinearVelocity( &vecAnimVelocity ); // adjust X pose parameter based on movement error if (fabs( vecAnimVelocity.x ) > 0.001) { vecCurrentMoveYaw.x *= vecEstVelocity.x / vecAnimVelocity.x; } else { vecCurrentMoveYaw.x = 0; GetOuter()->SetPoseParameter( pStudioHdr, m_iMoveX, vecCurrentMoveYaw.x ); } // adjust Y pose parameter based on movement error if (fabs( vecAnimVelocity.y ) > 0.001) { vecCurrentMoveYaw.y *= vecEstVelocity.y / vecAnimVelocity.y; } else { vecCurrentMoveYaw.y = 0; GetOuter()->SetPoseParameter( pStudioHdr, m_iMoveY, vecCurrentMoveYaw.y ); } dx = vecEstVelocity.x - vecAnimVelocity.x; dy = vecEstVelocity.y - vecAnimVelocity.y; retry = (vecCurrentMoveYaw.x < 1.0 && vecCurrentMoveYaw.x > -1.0) && (dx < -DOD_MOVEMENT_ERROR_LIMIT || dx > DOD_MOVEMENT_ERROR_LIMIT); retry = retry || ((vecCurrentMoveYaw.y < 1.0 && vecCurrentMoveYaw.y > -1.0) && (dy < -DOD_MOVEMENT_ERROR_LIMIT || dy > DOD_MOVEMENT_ERROR_LIMIT)); } while (i++ < 5 && retry); /* if ( m_pOuter->entindex() == 2 ) { DevMsgRT("%d(%.2f : %.2f) %.3f : (%.2f : %.2f) %.3f\n", i, vecEstVelocity.x, vecAnimVelocity.x, vecCurrentMoveYaw.x, vecEstVelocity.y, vecAnimVelocity.y, vecCurrentMoveYaw.y ); } */ #endif m_vecLastMoveYaw = vecCurrentMoveYaw; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDODPlayerAnimState::EstimateYaw( void ) { // Get the frame time. float flDeltaTime = gpGlobals->frametime; if ( flDeltaTime == 0.0f ) { // FIXME: why does this short circuit? m_flEstimateVelocity = 0.0; m_flEstimateYaw = 0.0; return; } // Get the player's velocity and angles. Vector vecEstVelocity; GetOuterAbsVelocity( vecEstVelocity ); QAngle angles = GetOuterDOD()->GetLocalAngles(); // If we are not moving, sync up the feet and eyes slowly. if ( vecEstVelocity.x == 0.0f && vecEstVelocity.y == 0.0f ) { float flYawDelta = angles[YAW] - m_flEstimateYaw; flYawDelta = AngleNormalize( flYawDelta ); if ( flDeltaTime < 0.25f ) { flYawDelta *= ( flDeltaTime * 4.0f ); } else { flYawDelta *= flDeltaTime; } m_flEstimateVelocity = 0.0; m_flEstimateYaw += flYawDelta; AngleNormalize( m_flEstimateYaw ); } else { m_flEstimateVelocity = vecEstVelocity.Length2D(); m_flEstimateYaw = ( atan2( vecEstVelocity.y, vecEstVelocity.x ) * 180.0f / M_PI ); m_flEstimateYaw = clamp( m_flEstimateYaw, -180.0f, 180.0f ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDODPlayerAnimState::ComputePoseParam_AimPitch( CStudioHdr *pStudioHdr ) { // Get the view pitch. float flAimPitch = m_flEyePitch; // Lock pitch at 0 if a reload gesture is playing #ifdef CLIENT_DLL if ( IsPlayingGesture( GESTURE_RELOAD ) ) flAimPitch = 0; #else Activity idealActivity = TranslateActivity( ACT_RELOAD ); if ( m_pOuter->IsPlayingGesture( idealActivity ) ) flAimPitch = 0; #endif // Set the aim pitch pose parameter and save. GetOuter()->SetPoseParameter( pStudioHdr, m_iAimPitch, -flAimPitch ); m_flLastAimPitch = flAimPitch; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDODPlayerAnimState::ComputePoseParam_AimYaw( CStudioHdr *pStudioHdr ) { // Get the movement velocity. Vector vecVelocity; GetOuterAbsVelocity( vecVelocity ); // Check to see if we are moving. bool bMoving = ( vecVelocity.Length() > 1.0f ) ? true : false; // Check our prone and deployed state. bool bDeployed = GetOuterDOD()->m_Shared.IsSandbagDeployed() || GetOuterDOD()->m_Shared.IsProneDeployed(); bool bProne = GetOuterDOD()->m_Shared.IsProne(); // If we are moving or are prone and undeployed. if ( bMoving || ( bProne && !bDeployed ) ) { // The feet match the eye direction when moving - the move yaw takes care of the rest. m_flGoalFeetYaw = m_flEyeYaw; } // Else if we are not moving. else { // Initialize the feet. if ( m_flLastAimTurnTime <= 0.0f ) { m_flGoalFeetYaw = m_flEyeYaw; m_flCurrentFeetYaw = m_flEyeYaw; m_flLastAimTurnTime = gpGlobals->curtime; } // Make sure the feet yaw isn't too far out of sync with the eye yaw. // TODO: Do something better here! else { float flYawDelta = AngleNormalize( m_flGoalFeetYaw - m_flEyeYaw ); if ( bDeployed ) { if ( fabs( flYawDelta ) > 20.0f ) { float flSide = ( flYawDelta > 0.0f ) ? -1.0f : 1.0f; m_flGoalFeetYaw += ( 20.0f * flSide ); } } else { if ( fabs( flYawDelta ) > m_AnimConfig.m_flMaxBodyYawDegrees ) { float flSide = ( flYawDelta > 0.0f ) ? -1.0f : 1.0f; m_flGoalFeetYaw += ( m_AnimConfig.m_flMaxBodyYawDegrees * flSide ); } } } } // Fix up the feet yaw. m_flGoalFeetYaw = AngleNormalize( m_flGoalFeetYaw ); if ( m_flGoalFeetYaw != m_flCurrentFeetYaw ) { ConvergeYawAngles( m_flGoalFeetYaw, DOD_BODYYAW_RATE, gpGlobals->frametime, m_flCurrentFeetYaw ); m_flLastAimTurnTime = gpGlobals->curtime; } // Rotate the body into position. m_angRender[YAW] = m_flCurrentFeetYaw; // Find the aim(torso) yaw base on the eye and feet yaws. float flAimYaw = m_flEyeYaw - m_flCurrentFeetYaw; flAimYaw = AngleNormalize( flAimYaw ); // Set the aim yaw and save. GetOuter()->SetPoseParameter( pStudioHdr, m_iAimYaw, -flAimYaw ); m_flLastAimYaw = flAimYaw; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDODPlayerAnimState::ConvergeYawAngles( float flGoalYaw, float flYawRate, float flDeltaTime, float &flCurrentYaw ) { #define FADE_TURN_DEGREES 60.0f // Find the yaw delta. float flDeltaYaw = flGoalYaw - flCurrentYaw; float flDeltaYawAbs = fabs( flDeltaYaw ); flDeltaYaw = AngleNormalize( flDeltaYaw ); // Always do at least a bit of the turn (1%). float flScale = 1.0f; flScale = flDeltaYawAbs / FADE_TURN_DEGREES; flScale = clamp( flScale, 0.01f, 1.0f ); float flYaw = flYawRate * flDeltaTime * flScale; if ( flDeltaYawAbs < flYaw ) { flCurrentYaw = flGoalYaw; } else { float flSide = ( flDeltaYaw < 0.0f ) ? -1.0f : 1.0f; flCurrentYaw += ( flYaw * flSide ); } flCurrentYaw = AngleNormalize( flCurrentYaw ); #undef FADE_TURN_DEGREES } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDODPlayerAnimState::ComputePoseParam_BodyHeight( CStudioHdr *pStudioHdr ) { if( m_pOuterDOD->m_Shared.IsSandbagDeployed() ) { // float flHeight = m_pOuterDOD->m_Shared.GetDeployedHeight() - 4.0f; float flHeight = m_pOuterDOD->m_Shared.GetDeployedHeight() - dod_bodyheightoffset.GetFloat(); GetOuter()->SetPoseParameter( pStudioHdr, m_iBodyHeight, flHeight ); m_flLastBodyHeight = flHeight; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void Anim_StateLog( const char *pMsg, ... ) { // Format the string. char str[4096]; va_list marker; va_start( marker, pMsg ); Q_vsnprintf( str, sizeof( str ), pMsg, marker ); va_end( marker ); // Log it? if ( anim_showstatelog.GetInt() == 1 || anim_showstatelog.GetInt() == 3 ) { Msg( "%s", str ); } if ( anim_showstatelog.GetInt() > 1 ) { // static FileHandle_t hFile = filesystem->Open( "AnimState.log", "wt" ); // filesystem->FPrintf( hFile, "%s", str ); // filesystem->Flush( hFile ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void Anim_StatePrintf( int iLine, const char *pMsg, ... ) { // Format the string. char str[4096]; va_list marker; va_start( marker, pMsg ); Q_vsnprintf( str, sizeof( str ), pMsg, marker ); va_end( marker ); // Show it with Con_NPrintf. engine->Con_NPrintf( iLine, "%s", str ); // Log it. Anim_StateLog( "%s\n", str ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDODPlayerAnimState::DebugShowAnimStateForPlayer( bool bIsServer ) { // Get the player's velocity. Vector vecVelocity; GetOuterAbsVelocity( vecVelocity ); // Start animation state logging. int iLine = 5; if ( bIsServer ) { iLine = 12; } // Anim_StateLog( "-------------%s: frame %d -----------------\n", bIsServer ? "Server" : "Client", gpGlobals->framecount ); Anim_StatePrintf( iLine++, "-------------%s: frame %d -----------------\n", bIsServer ? "Server" : "Client", gpGlobals->framecount ); // Write out the main sequence and its data. Anim_StatePrintf( iLine++, "Main: %s, Cycle: %.2f\n", GetSequenceName( GetOuter()->GetModelPtr(), GetOuter()->GetSequence() ), GetOuter()->GetCycle() ); // Write out the layers and their data. for ( int iAnim = 0; iAnim < GetOuter()->GetNumAnimOverlays(); ++iAnim ) { CAnimationLayer *pLayer = GetOuter()->GetAnimOverlay( iAnim ); if ( pLayer && ( pLayer->m_nOrder != CBaseAnimatingOverlay::MAX_OVERLAYS ) ) { Anim_StatePrintf( iLine++, "Layer %s: Weight: %.2f, Cycle: %.2f", GetSequenceName( GetOuter()->GetModelPtr(), pLayer->m_nSequence ), (float)pLayer->m_flWeight, (float)pLayer->m_flCycle ); } } // Write out the speed data. Anim_StatePrintf( iLine++, "Time: %.2f, Speed: %.2f, MaxSpeed: %.2f", gpGlobals->curtime, vecVelocity.Length2D(), GetCurrentMaxGroundSpeed() ); // Write out the 9-way blend data. Anim_StatePrintf( iLine++, "EntityYaw: %.2f, AimYaw: %.2f, AimPitch: %.2f, MoveX: %.2f, MoveY: %.2f Body: %.2f", m_angRender[YAW], m_flLastAimYaw, m_flLastAimPitch, m_vecLastMoveYaw.x, m_vecLastMoveYaw.y, m_flLastBodyHeight ); // Anim_StateLog( "--------------------------------------------\n\n" ); Anim_StatePrintf( iLine++, "--------------------------------------------\n\n" ); DebugShowEyeYaw(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDODPlayerAnimState::DebugShowEyeYaw( void ) { #ifdef _NDEBUG float flBaseSize = 10; float flHeight = 80; Vector vecPos = GetOuter()->GetAbsOrigin() + Vector( 0.0f, 0.0f, 3.0f ); QAngle angles( 0.0f, 0.0f, 0.0f ); angles[YAW] = m_flEyeYaw; Vector vecForward, vecRight, vecUp; AngleVectors( angles, &vecForward, &vecRight, &vecUp ); // Draw a red triangle on the ground for the eye yaw. debugoverlay->AddTriangleOverlay( ( vecPos + vecRight * flBaseSize / 2.0f ), ( vecPos - vecRight * flBaseSize / 2.0f ), ( vecPos + vecForward * flHeight, 255, 0, 0, 255, false, 0.01f ); #endif } // Client specific debug functions. #ifdef CLIENT_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDODPlayerAnimState::DebugShowActivity( Activity activity ) { #ifdef _DEBUG const char *pszActivity = "other"; switch( activity ) { case ACT_IDLE: { pszActivity = "idle"; break; } case ACT_SPRINT: { pszActivity = "sprint"; break; } case ACT_WALK: { pszActivity = "walk"; break; } case ACT_RUN: { pszActivity = "run"; break; } } Msg( "Activity: %s\n", pszActivity ); #endif } #endif