aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/shared/base_playeranimstate.cpp
diff options
context:
space:
mode:
authorJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
committerJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
commit39ed87570bdb2f86969d4be821c94b722dc71179 (patch)
treeabc53757f75f40c80278e87650ea92808274aa59 /mp/src/game/shared/base_playeranimstate.cpp
downloadsource-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz
source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/game/shared/base_playeranimstate.cpp')
-rw-r--r--mp/src/game/shared/base_playeranimstate.cpp1079
1 files changed, 1079 insertions, 0 deletions
diff --git a/mp/src/game/shared/base_playeranimstate.cpp b/mp/src/game/shared/base_playeranimstate.cpp
new file mode 100644
index 00000000..411d764f
--- /dev/null
+++ b/mp/src/game/shared/base_playeranimstate.cpp
@@ -0,0 +1,1079 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "base_playeranimstate.h"
+#include "tier0/vprof.h"
+#include "animation.h"
+#include "studio.h"
+#include "apparent_velocity_helper.h"
+#include "utldict.h"
+#include "filesystem.h"
+
+
+#ifdef CLIENT_DLL
+ #include "c_baseplayer.h"
+ #include "engine/ivdebugoverlay.h"
+
+ ConVar cl_showanimstate( "cl_showanimstate", "-1", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Show the (client) animation state for the specified entity (-1 for none)." );
+ ConVar showanimstate_log( "cl_showanimstate_log", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "1 to output cl_showanimstate to Msg(). 2 to store in AnimStateClient.log. 3 for both." );
+#else
+ #include "player.h"
+ ConVar sv_showanimstate( "sv_showanimstate", "-1", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Show the (server) animation state for the specified entity (-1 for none)." );
+ ConVar showanimstate_log( "sv_showanimstate_log", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "1 to output sv_showanimstate to Msg(). 2 to store in AnimStateServer.log. 3 for both." );
+#endif
+
+
+// Below this many degrees, slow down turning rate linearly
+#define FADE_TURN_DEGREES 45.0f
+
+// After this, need to start turning feet
+#define MAX_TORSO_ANGLE 70.0f
+
+// Below this amount, don't play a turning animation/perform IK
+#define MIN_TURN_ANGLE_REQUIRING_TURN_ANIMATION 15.0f
+
+
+ConVar mp_feetyawrate(
+ "mp_feetyawrate",
+ "720",
+ FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY,
+ "How many degrees per second that we can turn our feet or upper body." );
+
+ConVar mp_facefronttime(
+ "mp_facefronttime",
+ "3",
+ FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY,
+ "After this amount of time of standing in place but aiming to one side, go ahead and move feet to face upper body." );
+
+ConVar mp_ik( "mp_ik", "1", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Use IK on in-place turns." );
+
+// Pose parameters stored for debugging.
+float g_flLastBodyPitch, g_flLastBodyYaw, m_flLastMoveYaw;
+
+
+// ------------------------------------------------------------------------------------------------ //
+// CBasePlayerAnimState implementation.
+// ------------------------------------------------------------------------------------------------ //
+
+CBasePlayerAnimState::CBasePlayerAnimState()
+{
+ m_flEyeYaw = 0.0f;
+ m_flEyePitch = 0.0f;
+ m_bCurrentFeetYawInitialized = false;
+ m_flCurrentTorsoYaw = 0.0f;
+ m_flCurrentTorsoYaw = TURN_NONE;
+ m_flMaxGroundSpeed = 0.0f;
+ m_flStoredCycle = 0.0f;
+
+ m_flGaitYaw = 0.0f;
+ m_flGoalFeetYaw = 0.0f;
+ m_flCurrentFeetYaw = 0.0f;
+ m_flLastYaw = 0.0f;
+ m_flLastTurnTime = 0.0f;
+ m_angRender.Init();
+ m_vLastMovePose.Init();
+ m_iCurrent8WayIdleSequence = -1;
+ m_iCurrent8WayCrouchIdleSequence = -1;
+
+ m_pOuter = NULL;
+ m_eCurrentMainSequenceActivity = ACT_IDLE;
+ m_flLastAnimationStateClearTime = 0.0f;
+}
+
+
+CBasePlayerAnimState::~CBasePlayerAnimState()
+{
+}
+
+
+void CBasePlayerAnimState::Init( CBaseAnimatingOverlay *pPlayer, const CModAnimConfig &config )
+{
+ m_pOuter = pPlayer;
+ m_AnimConfig = config;
+ ClearAnimationState();
+}
+
+
+void CBasePlayerAnimState::Release()
+{
+ delete this;
+}
+
+
+void CBasePlayerAnimState::ClearAnimationState()
+{
+ ClearAnimationLayers();
+ m_bCurrentFeetYawInitialized = false;
+ m_flLastAnimationStateClearTime = gpGlobals->curtime;
+}
+
+
+float CBasePlayerAnimState::TimeSinceLastAnimationStateClear() const
+{
+ return gpGlobals->curtime - m_flLastAnimationStateClearTime;
+}
+
+
+void CBasePlayerAnimState::Update( float eyeYaw, float eyePitch )
+{
+ VPROF( "CBasePlayerAnimState::Update" );
+
+ // Clear animation overlays because we're about to completely reconstruct them.
+ ClearAnimationLayers();
+
+ // Some mods don't want to update the player's animation state if they're dead and ragdolled.
+ if ( !ShouldUpdateAnimState() )
+ {
+ ClearAnimationState();
+ return;
+ }
+
+
+ CStudioHdr *pStudioHdr = GetOuter()->GetModelPtr();
+ // Store these. All the calculations are based on them.
+ m_flEyeYaw = AngleNormalize( eyeYaw );
+ m_flEyePitch = AngleNormalize( eyePitch );
+
+ // Compute sequences for all the layers.
+ ComputeSequences( pStudioHdr );
+
+
+ // Compute all the pose params.
+ ComputePoseParam_BodyPitch( pStudioHdr ); // Look up/down.
+ ComputePoseParam_BodyYaw(); // Torso rotation.
+ ComputePoseParam_MoveYaw( pStudioHdr ); // What direction his legs are running in.
+
+
+ ComputePlaybackRate();
+
+
+#ifdef CLIENT_DLL
+ if ( cl_showanimstate.GetInt() == m_pOuter->entindex() )
+ {
+ DebugShowAnimStateFull( 5 );
+ }
+ else if ( cl_showanimstate.GetInt() == -2 )
+ {
+ C_BasePlayer *targetPlayer = C_BasePlayer::GetLocalPlayer();
+
+ if( targetPlayer && ( targetPlayer->GetObserverMode() == OBS_MODE_IN_EYE || targetPlayer->GetObserverMode() == OBS_MODE_CHASE ) )
+ {
+ C_BaseEntity *target = targetPlayer->GetObserverTarget();
+
+ if( target && target->IsPlayer() )
+ {
+ targetPlayer = ToBasePlayer( target );
+ }
+ }
+
+ if ( m_pOuter == targetPlayer )
+ {
+ DebugShowAnimStateFull( 6 );
+ }
+ }
+#else
+ if ( sv_showanimstate.GetInt() == m_pOuter->entindex() )
+ {
+ DebugShowAnimState( 20 );
+ }
+#endif
+}
+
+
+bool CBasePlayerAnimState::ShouldUpdateAnimState()
+{
+ // By default, don't update their animation state when they're dead because they're
+ // either a ragdoll or they're not drawn.
+ return GetOuter()->IsAlive();
+}
+
+
+bool CBasePlayerAnimState::ShouldChangeSequences( void ) const
+{
+ return true;
+}
+
+
+void CBasePlayerAnimState::SetOuterPoseParameter( int iParam, float flValue )
+{
+ // Make sure to set all the history values too, otherwise the server can overwrite them.
+ GetOuter()->SetPoseParameter( iParam, flValue );
+}
+
+
+void CBasePlayerAnimState::ClearAnimationLayers()
+{
+ VPROF( "CBasePlayerAnimState::ClearAnimationLayers" );
+ if ( !m_pOuter )
+ return;
+
+ m_pOuter->SetNumAnimOverlays( AIMSEQUENCE_LAYER+NUM_AIMSEQUENCE_LAYERS );
+ for ( int i=0; i < m_pOuter->GetNumAnimOverlays(); i++ )
+ {
+ m_pOuter->GetAnimOverlay( i )->SetOrder( CBaseAnimatingOverlay::MAX_OVERLAYS );
+#ifndef CLIENT_DLL
+ m_pOuter->GetAnimOverlay( i )->m_fFlags = 0;
+#endif
+ }
+}
+
+
+void CBasePlayerAnimState::RestartMainSequence()
+{
+ CBaseAnimatingOverlay *pPlayer = GetOuter();
+
+ pPlayer->m_flAnimTime = gpGlobals->curtime;
+ pPlayer->SetCycle( 0 );
+}
+
+
+void CBasePlayerAnimState::ComputeSequences( CStudioHdr *pStudioHdr )
+{
+ VPROF( "CBasePlayerAnimState::ComputeSequences" );
+
+ ComputeMainSequence(); // Lower body (walk/run/idle).
+ UpdateInterpolators(); // The groundspeed interpolator uses the main sequence info.
+
+ if ( m_AnimConfig.m_bUseAimSequences )
+ {
+ ComputeAimSequence(); // Upper body, based on weapon type.
+ }
+}
+
+void CBasePlayerAnimState::ResetGroundSpeed( void )
+{
+ m_flMaxGroundSpeed = GetCurrentMaxGroundSpeed();
+}
+
+void CBasePlayerAnimState::ComputeMainSequence()
+{
+ VPROF( "CBasePlayerAnimState::ComputeMainSequence" );
+
+ CBaseAnimatingOverlay *pPlayer = GetOuter();
+
+ // Have our class or the mod-specific class determine what the current activity is.
+ Activity idealActivity = CalcMainActivity();
+
+#ifdef CLIENT_DLL
+ Activity oldActivity = m_eCurrentMainSequenceActivity;
+#endif
+
+ // Store our current activity so the aim and fire layers know what to do.
+ m_eCurrentMainSequenceActivity = idealActivity;
+
+ // Export to our outer class..
+ int animDesired = SelectWeightedSequence( TranslateActivity(idealActivity) );
+
+#if !defined( HL1_CLIENT_DLL ) && !defined ( HL1_DLL )
+ if ( pPlayer->GetSequenceActivity( pPlayer->GetSequence() ) == pPlayer->GetSequenceActivity( animDesired ) )
+ return;
+#endif
+
+ if ( animDesired < 0 )
+ animDesired = 0;
+
+ pPlayer->ResetSequence( animDesired );
+
+#ifdef CLIENT_DLL
+ // If we went from idle to walk, reset the interpolation history.
+ // Kind of hacky putting this here.. it might belong outside the base class.
+ if ( (oldActivity == ACT_CROUCHIDLE || oldActivity == ACT_IDLE) &&
+ (idealActivity == ACT_WALK || idealActivity == ACT_RUN_CROUCH) )
+ {
+ ResetGroundSpeed();
+ }
+#endif
+}
+
+
+
+
+
+void CBasePlayerAnimState::UpdateAimSequenceLayers(
+ float flCycle,
+ int iFirstLayer,
+ bool bForceIdle,
+ CSequenceTransitioner *pTransitioner,
+ float flWeightScale
+ )
+{
+ float flAimSequenceWeight = 1;
+ int iAimSequence = CalcAimLayerSequence( &flCycle, &flAimSequenceWeight, bForceIdle );
+ if ( iAimSequence == -1 )
+ iAimSequence = 0;
+
+ // Feed the current state of the animation parameters to the sequence transitioner.
+ // It will hand back either 1 or 2 animations in the queue to set, depending on whether
+ // it's transitioning or not. We just dump those into the animation layers.
+ pTransitioner->CheckForSequenceChange(
+ m_pOuter->GetModelPtr(),
+ iAimSequence,
+ false, // don't force transitions on the same anim
+ true // yes, interpolate when transitioning
+ );
+
+ pTransitioner->UpdateCurrent(
+ m_pOuter->GetModelPtr(),
+ iAimSequence,
+ flCycle,
+ GetOuter()->GetPlaybackRate(),
+ gpGlobals->curtime
+ );
+
+ CAnimationLayer *pDest0 = m_pOuter->GetAnimOverlay( iFirstLayer );
+ CAnimationLayer *pDest1 = m_pOuter->GetAnimOverlay( iFirstLayer+1 );
+
+ if ( pTransitioner->m_animationQueue.Count() == 1 )
+ {
+ // If only 1 animation, then blend it in fully.
+ CAnimationLayer *pSource0 = &pTransitioner->m_animationQueue[0];
+ *pDest0 = *pSource0;
+
+ pDest0->m_flWeight = 1;
+ pDest1->m_flWeight = 0;
+ pDest0->m_nOrder = iFirstLayer;
+
+#ifndef CLIENT_DLL
+ pDest0->m_fFlags |= ANIM_LAYER_ACTIVE;
+#endif
+ }
+ else if ( pTransitioner->m_animationQueue.Count() >= 2 )
+ {
+ // The first one should be fading out. Fade in the new one inversely.
+ CAnimationLayer *pSource0 = &pTransitioner->m_animationQueue[0];
+ CAnimationLayer *pSource1 = &pTransitioner->m_animationQueue[1];
+
+ *pDest0 = *pSource0;
+ *pDest1 = *pSource1;
+ Assert( pDest0->m_flWeight >= 0.0f && pDest0->m_flWeight <= 1.0f );
+ pDest1->m_flWeight = 1 - pDest0->m_flWeight; // This layer just mirrors the other layer's weight (one fades in while the other fades out).
+
+ pDest0->m_nOrder = iFirstLayer;
+ pDest1->m_nOrder = iFirstLayer+1;
+
+#ifndef CLIENT_DLL
+ pDest0->m_fFlags |= ANIM_LAYER_ACTIVE;
+ pDest1->m_fFlags |= ANIM_LAYER_ACTIVE;
+#endif
+ }
+
+ pDest0->m_flWeight *= flWeightScale * flAimSequenceWeight;
+ pDest0->m_flWeight = clamp( (float)pDest0->m_flWeight, 0.0f, 1.0f );
+
+ pDest1->m_flWeight *= flWeightScale * flAimSequenceWeight;
+ pDest1->m_flWeight = clamp( (float)pDest1->m_flWeight, 0.0f, 1.0f );
+
+ pDest0->m_flCycle = pDest1->m_flCycle = flCycle;
+}
+
+
+void CBasePlayerAnimState::OptimizeLayerWeights( int iFirstLayer, int nLayers )
+{
+ int i;
+
+ // Find the total weight of the blended layers, not including the idle layer (iFirstLayer)
+ float totalWeight = 0.0f;
+ for ( i=1; i < nLayers; i++ )
+ {
+ CAnimationLayer *pLayer = m_pOuter->GetAnimOverlay( iFirstLayer+i );
+ if ( pLayer->IsActive() && pLayer->m_flWeight > 0.0f )
+ {
+ totalWeight += pLayer->m_flWeight;
+ }
+ }
+
+ // Set the idle layer's weight to be 1 minus the sum of other layer weights
+ CAnimationLayer *pLayer = m_pOuter->GetAnimOverlay( iFirstLayer );
+ if ( pLayer->IsActive() && pLayer->m_flWeight > 0.0f )
+ {
+ pLayer->m_flWeight = 1.0f - totalWeight;
+ pLayer->m_flWeight = MAX( (float)pLayer->m_flWeight, 0.0f);
+ }
+
+ // This part is just an optimization. Since we have the walk/run animations weighted on top of
+ // the idle animations, all this does is disable the idle animations if the walk/runs are at
+ // full weighting, which is whenever a guy is at full speed.
+ //
+ // So it saves us blending a couple animation layers whenever a guy is walking or running full speed.
+ int iLastOne = -1;
+ for ( i=0; i < nLayers; i++ )
+ {
+ CAnimationLayer *pLayer = m_pOuter->GetAnimOverlay( iFirstLayer+i );
+ if ( pLayer->IsActive() && pLayer->m_flWeight > 0.99 )
+ iLastOne = i;
+ }
+
+ if ( iLastOne != -1 )
+ {
+ for ( int i=iLastOne-1; i >= 0; i-- )
+ {
+ CAnimationLayer *pLayer = m_pOuter->GetAnimOverlay( iFirstLayer+i );
+#ifdef CLIENT_DLL
+ pLayer->m_nOrder = CBaseAnimatingOverlay::MAX_OVERLAYS;
+#else
+ pLayer->m_nOrder.Set( CBaseAnimatingOverlay::MAX_OVERLAYS );
+ pLayer->m_fFlags = 0;
+#endif
+ }
+ }
+}
+
+bool CBasePlayerAnimState::ShouldBlendAimSequenceToIdle()
+{
+ Activity act = GetCurrentMainSequenceActivity();
+
+ return (act == ACT_RUN || act == ACT_WALK || act == ACT_RUNTOIDLE || act == ACT_RUN_CROUCH);
+}
+
+void CBasePlayerAnimState::ComputeAimSequence()
+{
+ VPROF( "CBasePlayerAnimState::ComputeAimSequence" );
+
+ // Synchronize the lower and upper body cycles.
+ float flCycle = m_pOuter->GetCycle();
+
+ // Figure out the new cycle time.
+ UpdateAimSequenceLayers( flCycle, AIMSEQUENCE_LAYER, true, &m_IdleSequenceTransitioner, 1 );
+
+ if ( ShouldBlendAimSequenceToIdle() )
+ {
+ // What we do here is blend between the idle upper body animation (like where he's got the dual elites
+ // held out in front of him but he's not moving) and his walk/run/crouchrun upper body animation,
+ // weighting it based on how fast he's moving. That way, when he's moving slowly, his upper
+ // body doesn't jiggle all around.
+ bool bIsMoving;
+ float flPlaybackRate = CalcMovementPlaybackRate( &bIsMoving );
+ if ( bIsMoving )
+ UpdateAimSequenceLayers( flCycle, AIMSEQUENCE_LAYER+2, false, &m_SequenceTransitioner, flPlaybackRate );
+ }
+
+ OptimizeLayerWeights( AIMSEQUENCE_LAYER, NUM_AIMSEQUENCE_LAYERS );
+}
+
+
+int CBasePlayerAnimState::CalcSequenceIndex( const char *pBaseName, ... )
+{
+ char szFullName[512];
+ va_list marker;
+ va_start( marker, pBaseName );
+ Q_vsnprintf( szFullName, sizeof( szFullName ), pBaseName, marker );
+ va_end( marker );
+ int iSequence = GetOuter()->LookupSequence( szFullName );
+
+ // Show warnings if we can't find anything here.
+ if ( iSequence == -1 )
+ {
+ static CUtlDict<int,int> dict;
+ if ( dict.Find( szFullName ) == -1 )
+ {
+ dict.Insert( szFullName, 0 );
+ Warning( "CalcSequenceIndex: can't find '%s'.\n", szFullName );
+ }
+
+ iSequence = 0;
+ }
+
+ return iSequence;
+}
+
+
+
+void CBasePlayerAnimState::UpdateInterpolators()
+{
+ VPROF( "CBasePlayerAnimState::UpdateInterpolators" );
+
+ // First, figure out their current max speed based on their current activity.
+ float flCurMaxSpeed = GetCurrentMaxGroundSpeed();
+ m_flMaxGroundSpeed = flCurMaxSpeed;
+}
+
+
+float CBasePlayerAnimState::GetInterpolatedGroundSpeed()
+{
+ return m_flMaxGroundSpeed;
+}
+
+
+float CBasePlayerAnimState::CalcMovementPlaybackRate( bool *bIsMoving )
+{
+ // Determine ideal playback rate
+ Vector vel;
+ GetOuterAbsVelocity( vel );
+
+ float speed = vel.Length2D();
+ bool isMoving = ( speed > MOVING_MINIMUM_SPEED );
+
+ *bIsMoving = false;
+ float flReturnValue = 1;
+
+ if ( isMoving && CanThePlayerMove() )
+ {
+ float flGroundSpeed = GetInterpolatedGroundSpeed();
+ if ( flGroundSpeed < 0.001f )
+ {
+ flReturnValue = 0.01;
+ }
+ else
+ {
+ // Note this gets set back to 1.0 if sequence changes due to ResetSequenceInfo below
+ flReturnValue = speed / flGroundSpeed;
+ flReturnValue = clamp( flReturnValue, 0.01f, 10.f ); // don't go nuts here.
+ }
+ *bIsMoving = true;
+ }
+
+ return flReturnValue;
+}
+
+
+bool CBasePlayerAnimState::CanThePlayerMove()
+{
+ return true;
+}
+
+
+void CBasePlayerAnimState::ComputePlaybackRate()
+{
+ VPROF( "CBasePlayerAnimState::ComputePlaybackRate" );
+ if ( m_AnimConfig.m_LegAnimType != LEGANIM_9WAY && m_AnimConfig.m_LegAnimType != LEGANIM_8WAY )
+ {
+ // When using a 9-way blend, playback rate is always 1 and we just scale the pose params
+ // to speed up or slow down the animation.
+ bool bIsMoving;
+ float flRate = CalcMovementPlaybackRate( &bIsMoving );
+ if ( bIsMoving )
+ GetOuter()->SetPlaybackRate( flRate );
+ else
+ GetOuter()->SetPlaybackRate( 1 );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : CBasePlayer
+//-----------------------------------------------------------------------------
+CBaseAnimatingOverlay *CBasePlayerAnimState::GetOuter() const
+{
+ return m_pOuter;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : dt -
+//-----------------------------------------------------------------------------
+void CBasePlayerAnimState::EstimateYaw()
+{
+ Vector est_velocity;
+ GetOuterAbsVelocity( est_velocity );
+
+ float flLength = est_velocity.Length2D();
+ if ( flLength > MOVING_MINIMUM_SPEED )
+ {
+ m_flGaitYaw = atan2( est_velocity[1], est_velocity[0] );
+ m_flGaitYaw = RAD2DEG( m_flGaitYaw );
+ m_flGaitYaw = AngleNormalize( m_flGaitYaw );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Override for backpeddling
+// Input : dt -
+//-----------------------------------------------------------------------------
+void CBasePlayerAnimState::ComputePoseParam_MoveYaw( CStudioHdr *pStudioHdr )
+{
+ VPROF( "CBasePlayerAnimState::ComputePoseParam_MoveYaw" );
+
+ //Matt: Goldsrc style animations need to not rotate the model
+ if ( m_AnimConfig.m_LegAnimType == LEGANIM_GOLDSRC )
+ {
+#ifndef CLIENT_DLL
+ //Adrian: Make the model's angle match the legs so the hitboxes match on both sides.
+ GetOuter()->SetLocalAngles( QAngle( 0, m_flCurrentFeetYaw, 0 ) );
+#endif
+ }
+
+ // If using goldsrc-style animations where he's moving in the direction that his feet are facing,
+ // we don't use move yaw.
+ if ( m_AnimConfig.m_LegAnimType != LEGANIM_9WAY && m_AnimConfig.m_LegAnimType != LEGANIM_8WAY )
+ return;
+
+ // view direction relative to movement
+ float flYaw;
+
+ EstimateYaw();
+
+ float ang = m_flEyeYaw;
+ if ( ang > 180.0f )
+ {
+ ang -= 360.0f;
+ }
+ else if ( ang < -180.0f )
+ {
+ ang += 360.0f;
+ }
+
+ // calc side to side turning
+ flYaw = ang - m_flGaitYaw;
+ // Invert for mapping into 8way blend
+ flYaw = -flYaw;
+ flYaw = flYaw - (int)(flYaw / 360) * 360;
+
+ if (flYaw < -180)
+ {
+ flYaw = flYaw + 360;
+ }
+ else if (flYaw > 180)
+ {
+ flYaw = flYaw - 360;
+ }
+
+
+ if ( m_AnimConfig.m_LegAnimType == LEGANIM_9WAY )
+ {
+#ifndef CLIENT_DLL
+ //Adrian: Make the model's angle match the legs so the hitboxes match on both sides.
+ GetOuter()->SetLocalAngles( QAngle( 0, m_flCurrentFeetYaw, 0 ) );
+#endif
+
+ int iMoveX = GetOuter()->LookupPoseParameter( pStudioHdr, "move_x" );
+ int iMoveY = GetOuter()->LookupPoseParameter( pStudioHdr, "move_y" );
+ if ( iMoveX < 0 || iMoveY < 0 )
+ return;
+
+ bool bIsMoving;
+ float flPlaybackRate = CalcMovementPlaybackRate( &bIsMoving );
+
+ // Setup the 9-way blend parameters based on our speed and direction.
+ Vector2D vCurMovePose( 0, 0 );
+
+ if ( bIsMoving )
+ {
+ vCurMovePose.x = cos( DEG2RAD( flYaw ) ) * flPlaybackRate;
+ vCurMovePose.y = -sin( DEG2RAD( flYaw ) ) * flPlaybackRate;
+ }
+
+ GetOuter()->SetPoseParameter( pStudioHdr, iMoveX, vCurMovePose.x );
+ GetOuter()->SetPoseParameter( pStudioHdr, iMoveY, vCurMovePose.y );
+
+ m_vLastMovePose = vCurMovePose;
+ }
+ else
+ {
+ int iMoveYaw = GetOuter()->LookupPoseParameter( pStudioHdr, "move_yaw" );
+ if ( iMoveYaw >= 0 )
+ {
+ GetOuter()->SetPoseParameter( pStudioHdr, iMoveYaw, flYaw );
+ m_flLastMoveYaw = flYaw;
+
+ // Now blend in his idle animation.
+ // This makes the 8-way blend act like a 9-way blend by blending to
+ // an idle sequence as he slows down.
+#if defined(CLIENT_DLL)
+ bool bIsMoving;
+ CAnimationLayer *pLayer = m_pOuter->GetAnimOverlay( MAIN_IDLE_SEQUENCE_LAYER );
+
+ pLayer->m_flWeight = 1 - CalcMovementPlaybackRate( &bIsMoving );
+ if ( !bIsMoving )
+ {
+ pLayer->m_flWeight = 1;
+ }
+
+ if ( ShouldChangeSequences() )
+ {
+ // Whenever this layer stops blending, we can choose a new idle sequence to blend to, so he
+ // doesn't always use the same idle.
+ if ( pLayer->m_flWeight < 0.02f || m_iCurrent8WayIdleSequence == -1 )
+ {
+ m_iCurrent8WayIdleSequence = m_pOuter->SelectWeightedSequence( ACT_IDLE );
+ m_iCurrent8WayCrouchIdleSequence = m_pOuter->SelectWeightedSequence( ACT_CROUCHIDLE );
+ }
+
+ if ( m_eCurrentMainSequenceActivity == ACT_CROUCHIDLE || m_eCurrentMainSequenceActivity == ACT_RUN_CROUCH )
+ pLayer->m_nSequence = m_iCurrent8WayCrouchIdleSequence;
+ else
+ pLayer->m_nSequence = m_iCurrent8WayIdleSequence;
+ }
+
+ pLayer->m_flPlaybackRate = 1;
+ pLayer->m_flCycle += m_pOuter->GetSequenceCycleRate( pStudioHdr, pLayer->m_nSequence ) * gpGlobals->frametime;
+ pLayer->m_flCycle = fmod( pLayer->m_flCycle, 1 );
+ pLayer->m_nOrder = MAIN_IDLE_SEQUENCE_LAYER;
+#endif
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBasePlayerAnimState::ComputePoseParam_BodyPitch( CStudioHdr *pStudioHdr )
+{
+ VPROF( "CBasePlayerAnimState::ComputePoseParam_BodyPitch" );
+
+ // Get pitch from v_angle
+ float flPitch = m_flEyePitch;
+ if ( flPitch > 180.0f )
+ {
+ flPitch -= 360.0f;
+ }
+ flPitch = clamp( flPitch, -90.f, 90.f );
+
+ // See if we have a blender for pitch
+ int pitch = GetOuter()->LookupPoseParameter( pStudioHdr, "body_pitch" );
+ if ( pitch < 0 )
+ return;
+
+ GetOuter()->SetPoseParameter( pStudioHdr, pitch, flPitch );
+ g_flLastBodyPitch = flPitch;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : goal -
+// maxrate -
+// dt -
+// current -
+// Output : int
+//-----------------------------------------------------------------------------
+int CBasePlayerAnimState::ConvergeAngles( float goal,float maxrate, float maxgap, float dt, float& current )
+{
+ int direction = TURN_NONE;
+
+ float anglediff = goal - current;
+ anglediff = AngleNormalize( anglediff );
+
+ float anglediffabs = fabs( anglediff );
+
+ float scale = 1.0f;
+ if ( anglediffabs <= FADE_TURN_DEGREES )
+ {
+ scale = anglediffabs / FADE_TURN_DEGREES;
+ // Always do at least a bit of the turn ( 1% )
+ scale = clamp( scale, 0.01f, 1.0f );
+ }
+
+ float maxmove = maxrate * dt * scale;
+
+ if ( anglediffabs > maxgap )
+ {
+ // gap is too big, jump
+ maxmove = (anglediffabs - maxgap);
+ }
+
+ if ( anglediffabs < maxmove )
+ {
+ // we are close enought, just set the final value
+ current = goal;
+ }
+ else
+ {
+ // adjust value up or down
+ if ( anglediff > 0 )
+ {
+ current += maxmove;
+ direction = TURN_LEFT;
+ }
+ else
+ {
+ current -= maxmove;
+ direction = TURN_RIGHT;
+ }
+ }
+
+ current = AngleNormalize( current );
+
+ return direction;
+}
+
+void CBasePlayerAnimState::ComputePoseParam_BodyYaw()
+{
+ VPROF( "CBasePlayerAnimState::ComputePoseParam_BodyYaw" );
+
+ // Find out which way he's running (m_flEyeYaw is the way he's looking).
+ Vector vel;
+ GetOuterAbsVelocity( vel );
+ bool bIsMoving = vel.Length2D() > MOVING_MINIMUM_SPEED;
+
+ // If we just initialized this guy (maybe he just came into the PVS), then immediately
+ // set his feet in the right direction, otherwise they'll spin around from 0 to the
+ // right direction every time someone switches spectator targets.
+ if ( !m_bCurrentFeetYawInitialized )
+ {
+ m_bCurrentFeetYawInitialized = true;
+ m_flGoalFeetYaw = m_flCurrentFeetYaw = m_flEyeYaw;
+ m_flLastTurnTime = 0.0f;
+ }
+ else if ( bIsMoving )
+ {
+ // player is moving, feet yaw = aiming yaw
+ if ( m_AnimConfig.m_LegAnimType == LEGANIM_9WAY || m_AnimConfig.m_LegAnimType == LEGANIM_8WAY )
+ {
+ // His feet point in the direction his eyes are, but they can run in any direction.
+ m_flGoalFeetYaw = m_flEyeYaw;
+ }
+ else
+ {
+ m_flGoalFeetYaw = RAD2DEG( atan2( vel.y, vel.x ) );
+
+ // If he's running backwards, flip his feet backwards.
+ Vector vEyeYaw( cos( DEG2RAD( m_flEyeYaw ) ), sin( DEG2RAD( m_flEyeYaw ) ), 0 );
+ Vector vFeetYaw( cos( DEG2RAD( m_flGoalFeetYaw ) ), sin( DEG2RAD( m_flGoalFeetYaw ) ), 0 );
+ if ( vEyeYaw.Dot( vFeetYaw ) < -0.01 )
+ {
+ m_flGoalFeetYaw += 180;
+ }
+ }
+
+ }
+ else if ( (gpGlobals->curtime - m_flLastTurnTime) > mp_facefronttime.GetFloat() )
+ {
+ // player didn't move & turn for quite some time
+ m_flGoalFeetYaw = m_flEyeYaw;
+ }
+ else
+ {
+ // If he's rotated his view further than the model can turn, make him face forward.
+ float flDiff = AngleNormalize( m_flGoalFeetYaw - m_flEyeYaw );
+
+ if ( fabs(flDiff) > m_AnimConfig.m_flMaxBodyYawDegrees )
+ {
+ if ( flDiff > 0 )
+ m_flGoalFeetYaw -= m_AnimConfig.m_flMaxBodyYawDegrees;
+ else
+ m_flGoalFeetYaw += m_AnimConfig.m_flMaxBodyYawDegrees;
+ }
+ }
+
+ m_flGoalFeetYaw = AngleNormalize( m_flGoalFeetYaw );
+
+ if ( m_flCurrentFeetYaw != m_flGoalFeetYaw )
+ {
+ ConvergeAngles( m_flGoalFeetYaw, mp_feetyawrate.GetFloat(), m_AnimConfig.m_flMaxBodyYawDegrees,
+ gpGlobals->frametime, m_flCurrentFeetYaw );
+
+ m_flLastTurnTime = gpGlobals->curtime;
+ }
+
+ float flCurrentTorsoYaw = AngleNormalize( m_flEyeYaw - m_flCurrentFeetYaw );
+
+ // Rotate entire body into position
+ m_angRender[YAW] = m_flCurrentFeetYaw;
+ m_angRender[PITCH] = m_angRender[ROLL] = 0;
+
+ SetOuterBodyYaw( flCurrentTorsoYaw );
+ g_flLastBodyYaw = flCurrentTorsoYaw;
+}
+
+
+
+float CBasePlayerAnimState::SetOuterBodyYaw( float flValue )
+{
+ int body_yaw = GetOuter()->LookupPoseParameter( "body_yaw" );
+ if ( body_yaw < 0 )
+ {
+ return 0;
+ }
+
+ SetOuterPoseParameter( body_yaw, flValue );
+ return flValue;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : activity -
+// Output : Activity
+//-----------------------------------------------------------------------------
+Activity CBasePlayerAnimState::BodyYawTranslateActivity( Activity activity )
+{
+ // Not even standing still, sigh
+ if ( activity != ACT_IDLE )
+ return activity;
+
+ // Not turning
+ switch ( m_nTurningInPlace )
+ {
+ default:
+ case TURN_NONE:
+ return activity;
+ case TURN_RIGHT:
+ case TURN_LEFT:
+ return mp_ik.GetBool() ? ACT_TURN : activity;
+ }
+
+ Assert( 0 );
+ return activity;
+}
+
+const QAngle& CBasePlayerAnimState::GetRenderAngles()
+{
+ return m_angRender;
+}
+
+
+void CBasePlayerAnimState::GetOuterAbsVelocity( Vector& vel ) const
+{
+#if defined( CLIENT_DLL )
+ GetOuter()->EstimateAbsVelocity( vel );
+#else
+ vel = GetOuter()->GetAbsVelocity();
+#endif
+}
+
+
+float CBasePlayerAnimState::GetOuterXYSpeed() const
+{
+ Vector vel;
+ GetOuterAbsVelocity( vel );
+ return vel.Length2D();
+}
+
+// -----------------------------------------------------------------------------
+void CBasePlayerAnimState::AnimStateLog( 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 ( showanimstate_log.GetInt() == 1 || showanimstate_log.GetInt() == 3 )
+ {
+ Msg( "%s", str );
+ }
+
+ if ( showanimstate_log.GetInt() > 1 )
+ {
+#ifdef CLIENT_DLL
+ const char *fname = "AnimStateClient.log";
+#else
+ const char *fname = "AnimStateServer.log";
+#endif
+ static FileHandle_t hFile = filesystem->Open( fname, "wt" );
+ filesystem->FPrintf( hFile, "%s", str );
+ filesystem->Flush( hFile );
+ }
+}
+
+
+// -----------------------------------------------------------------------------
+void CBasePlayerAnimState::AnimStatePrintf( 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.
+ AnimStateLog( "%s\n", str );
+}
+
+
+// -----------------------------------------------------------------------------
+void CBasePlayerAnimState::DebugShowAnimState( int iStartLine )
+{
+ Vector vOuterVel;
+ GetOuterAbsVelocity( vOuterVel );
+
+ int iLine = iStartLine;
+ AnimStatePrintf( iLine++, "main: %s(%d), cycle: %.2f cyclerate: %.2f playbackrate: %.2f\n",
+ GetSequenceName( m_pOuter->GetModelPtr(), m_pOuter->GetSequence() ),
+ m_pOuter->GetSequence(),
+ m_pOuter->GetCycle(),
+ m_pOuter->GetSequenceCycleRate(m_pOuter->GetModelPtr(), m_pOuter->GetSequence()),
+ m_pOuter->GetPlaybackRate()
+ );
+
+ if ( m_AnimConfig.m_LegAnimType == LEGANIM_8WAY )
+ {
+ CAnimationLayer *pLayer = m_pOuter->GetAnimOverlay( MAIN_IDLE_SEQUENCE_LAYER );
+
+ AnimStatePrintf( iLine++, "idle: %s, weight: %.2f\n",
+ GetSequenceName( m_pOuter->GetModelPtr(), pLayer->m_nSequence ),
+ (float)pLayer->m_flWeight );
+ }
+
+ for ( int i=0; i < m_pOuter->GetNumAnimOverlays()-1; i++ )
+ {
+ CAnimationLayer *pLayer = m_pOuter->GetAnimOverlay( AIMSEQUENCE_LAYER + i );
+#ifdef CLIENT_DLL
+ AnimStatePrintf( iLine++, "%s(%d), weight: %.2f, cycle: %.2f, order (%d), aim (%d)",
+ !pLayer->IsActive() ? "-- ": (pLayer->m_nSequence == 0 ? "-- " : GetSequenceName( m_pOuter->GetModelPtr(), pLayer->m_nSequence ) ),
+ !pLayer->IsActive() ? 0 : (int)pLayer->m_nSequence,
+ !pLayer->IsActive() ? 0 : (float)pLayer->m_flWeight,
+ !pLayer->IsActive() ? 0 : (float)pLayer->m_flCycle,
+ !pLayer->IsActive() ? 0 : (int)pLayer->m_nOrder,
+ i
+ );
+#else
+ AnimStatePrintf( iLine++, "%s(%d), flags (%d), weight: %.2f, cycle: %.2f, order (%d), aim (%d)",
+ !pLayer->IsActive() ? "-- " : ( pLayer->m_nSequence == 0 ? "-- " : GetSequenceName( m_pOuter->GetModelPtr(), pLayer->m_nSequence ) ),
+ !pLayer->IsActive() ? 0 : (int)pLayer->m_nSequence,
+ !pLayer->IsActive() ? 0 : (int)pLayer->m_fFlags,// Doesn't exist on client
+ !pLayer->IsActive() ? 0 : (float)pLayer->m_flWeight,
+ !pLayer->IsActive() ? 0 : (float)pLayer->m_flCycle,
+ !pLayer->IsActive() ? 0 : (int)pLayer->m_nOrder,
+ i
+ );
+#endif
+ }
+
+ AnimStatePrintf( iLine++, "vel: %.2f, time: %.2f, max: %.2f, animspeed: %.2f",
+ vOuterVel.Length2D(), gpGlobals->curtime, GetInterpolatedGroundSpeed(), m_pOuter->GetSequenceGroundSpeed(m_pOuter->GetSequence()) );
+
+ if ( m_AnimConfig.m_LegAnimType == LEGANIM_8WAY )
+ {
+ AnimStatePrintf( iLine++, "ent yaw: %.2f, body_yaw: %.2f, move_yaw: %.2f, gait_yaw: %.2f, body_pitch: %.2f",
+ m_angRender[YAW], g_flLastBodyYaw, m_flLastMoveYaw, m_flGaitYaw, g_flLastBodyPitch );
+ }
+ else
+ {
+ AnimStatePrintf( iLine++, "ent yaw: %.2f, body_yaw: %.2f, body_pitch: %.2f, move_x: %.2f, move_y: %.2f",
+ m_angRender[YAW], g_flLastBodyYaw, g_flLastBodyPitch, m_vLastMovePose.x, m_vLastMovePose.y );
+ }
+
+ // Draw a red triangle on the ground for the eye yaw.
+ float flBaseSize = 10;
+ float flHeight = 80;
+ Vector vBasePos = GetOuter()->GetAbsOrigin() + Vector( 0, 0, 3 );
+ QAngle angles( 0, 0, 0 );
+ angles[YAW] = m_flEyeYaw;
+ Vector vForward, vRight, vUp;
+ AngleVectors( angles, &vForward, &vRight, &vUp );
+ debugoverlay->AddTriangleOverlay( vBasePos+vRight*flBaseSize/2, vBasePos-vRight*flBaseSize/2, vBasePos+vForward*flHeight, 255, 0, 0, 255, false, 0.01 );
+
+ // Draw a blue triangle on the ground for the body yaw.
+ angles[YAW] = m_angRender[YAW];
+ AngleVectors( angles, &vForward, &vRight, &vUp );
+ debugoverlay->AddTriangleOverlay( vBasePos+vRight*flBaseSize/2, vBasePos-vRight*flBaseSize/2, vBasePos+vForward*flHeight, 0, 0, 255, 255, false, 0.01 );
+
+}
+
+// -----------------------------------------------------------------------------
+void CBasePlayerAnimState::DebugShowAnimStateFull( int iStartLine )
+{
+ AnimStateLog( "----------------- frame %d -----------------\n", gpGlobals->framecount );
+
+ DebugShowAnimState( iStartLine );
+
+ AnimStateLog( "--------------------------------------------\n\n" );
+}
+
+// -----------------------------------------------------------------------------
+int CBasePlayerAnimState::SelectWeightedSequence( Activity activity )
+{
+ return GetOuter()->SelectWeightedSequence( activity );
+}
+