source-engine/game/shared/tf2base/tf_playeranimstate.cpp
2022-08-13 03:20:41 +03:00

633 lines
16 KiB
C++

//====== Copyright © 1996-2003, Valve Corporation, All rights reserved. =======
//
// Purpose:
//
//=============================================================================
#include "cbase.h"
#include "tier0/vprof.h"
#include "animation.h"
#include "studio.h"
#include "apparent_velocity_helper.h"
#include "utldict.h"
#include "tf_playeranimstate.h"
#include "base_playeranimstate.h"
#include "datacache/imdlcache.h"
#ifdef CLIENT_DLL
#include "c_tf_player.h"
#else
#include "tf_player.h"
#endif
#define TF_RUN_SPEED 320.0f
#define TF_WALK_SPEED 75.0f
#define TF_CROUCHWALK_SPEED 110.0f
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pPlayer -
// Output : CMultiPlayerAnimState*
//-----------------------------------------------------------------------------
CTFPlayerAnimState* CreateTFPlayerAnimState( CTFPlayer *pPlayer )
{
MDLCACHE_CRITICAL_SECTION();
// Setup the movement data.
MultiPlayerMovementData_t movementData;
movementData.m_flBodyYawRate = 720.0f;
movementData.m_flRunSpeed = TF_RUN_SPEED;
movementData.m_flWalkSpeed = TF_WALK_SPEED;
movementData.m_flSprintSpeed = -1.0f;
// Create animation state for this player.
CTFPlayerAnimState *pRet = new CTFPlayerAnimState( pPlayer, movementData );
// Specific TF player initialization.
pRet->InitTF( pPlayer );
return pRet;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : -
//-----------------------------------------------------------------------------
CTFPlayerAnimState::CTFPlayerAnimState()
{
m_pTFPlayer = NULL;
// Don't initialize TF specific variables here. Init them in InitTF()
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pPlayer -
// &movementData -
//-----------------------------------------------------------------------------
CTFPlayerAnimState::CTFPlayerAnimState( CBasePlayer *pPlayer, MultiPlayerMovementData_t &movementData )
: CMultiPlayerAnimState( pPlayer, movementData )
{
m_pTFPlayer = NULL;
// Don't initialize TF specific variables here. Init them in InitTF()
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : -
//-----------------------------------------------------------------------------
CTFPlayerAnimState::~CTFPlayerAnimState()
{
}
//-----------------------------------------------------------------------------
// Purpose: Initialize Team Fortress specific animation state.
// Input : *pPlayer -
//-----------------------------------------------------------------------------
void CTFPlayerAnimState::InitTF( CTFPlayer *pPlayer )
{
m_pTFPlayer = pPlayer;
m_bInAirWalk = false;
m_flHoldDeployedPoseUntilTime = 0.0f;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerAnimState::ClearAnimationState( void )
{
m_bInAirWalk = false;
BaseClass::ClearAnimationState();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : actDesired -
// Output : Activity
//-----------------------------------------------------------------------------
Activity CTFPlayerAnimState::TranslateActivity( Activity actDesired )
{
Activity translateActivity = BaseClass::TranslateActivity( actDesired );
if ( GetTFPlayer()->GetActiveWeapon() )
{
translateActivity = GetTFPlayer()->GetActiveWeapon()->ActivityOverride( translateActivity, NULL );
}
return translateActivity;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerAnimState::Update( float eyeYaw, float eyePitch )
{
// Profile the animation update.
VPROF( "CMultiPlayerAnimState::Update" );
// Get the TF player.
CTFPlayer *pTFPlayer = GetTFPlayer();
if ( !pTFPlayer )
return;
// Get the studio header for the player.
CStudioHdr *pStudioHdr = pTFPlayer->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 );
// Compute the player sequences.
ComputeSequences( pStudioHdr );
if ( SetupPoseParameters( pStudioHdr ) && !pTFPlayer->m_Shared.InCond( TF_COND_TAUNTING ) )
{
// 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 );
}
#ifdef CLIENT_DLL
if ( C_BasePlayer::ShouldDrawLocalPlayer() )
{
GetBasePlayer()->SetPlaybackRate( 1.0f );
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : event -
//-----------------------------------------------------------------------------
void CTFPlayerAnimState::DoAnimationEvent( PlayerAnimEvent_t event, int nData )
{
Activity iGestureActivity = ACT_INVALID;
switch( event )
{
case PLAYERANIMEVENT_ATTACK_PRIMARY:
{
CTFPlayer *pPlayer = GetTFPlayer();
if ( !pPlayer )
return;
CTFWeaponBase *pWpn = pPlayer->GetActiveTFWeapon();
bool bIsMinigun = ( pWpn && pWpn->GetWeaponID() == TF_WEAPON_MINIGUN );
bool bIsSniperRifle = ( pWpn && pWpn->GetWeaponID() == TF_WEAPON_SNIPERRIFLE );
// Heavy weapons primary fire.
if ( bIsMinigun )
{
// Play standing primary fire.
iGestureActivity = ACT_MP_ATTACK_STAND_PRIMARYFIRE;
if ( m_bInSwim )
{
// Play swimming primary fire.
iGestureActivity = ACT_MP_ATTACK_SWIM_PRIMARYFIRE;
}
else if ( GetBasePlayer()->GetFlags() & FL_DUCKING )
{
// Play crouching primary fire.
iGestureActivity = ACT_MP_ATTACK_CROUCH_PRIMARYFIRE;
}
if ( !IsGestureSlotPlaying( GESTURE_SLOT_ATTACK_AND_RELOAD, TranslateActivity(iGestureActivity) ) )
{
RestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, iGestureActivity );
}
}
else if ( bIsSniperRifle && pPlayer->m_Shared.InCond( TF_COND_ZOOMED ) )
{
// Weapon primary fire, zoomed in
if ( GetBasePlayer()->GetFlags() & FL_DUCKING )
{
RestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, ACT_MP_ATTACK_CROUCH_PRIMARYFIRE_DEPLOYED );
}
else
{
RestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, ACT_MP_ATTACK_STAND_PRIMARYFIRE_DEPLOYED );
}
iGestureActivity = ACT_VM_PRIMARYATTACK;
// Hold our deployed pose for a few seconds
m_flHoldDeployedPoseUntilTime = gpGlobals->curtime + 2.0;
}
else
{
// Weapon primary fire.
if ( GetBasePlayer()->GetFlags() & FL_DUCKING )
{
RestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, ACT_MP_ATTACK_CROUCH_PRIMARYFIRE );
}
else
{
RestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, ACT_MP_ATTACK_STAND_PRIMARYFIRE );
}
iGestureActivity = ACT_VM_PRIMARYATTACK;
}
break;
}
case PLAYERANIMEVENT_VOICE_COMMAND_GESTURE:
{
if ( !IsGestureSlotActive( GESTURE_SLOT_ATTACK_AND_RELOAD ) )
{
RestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, (Activity)nData );
}
break;
}
case PLAYERANIMEVENT_ATTACK_SECONDARY:
{
// Weapon secondary fire.
if ( GetBasePlayer()->GetFlags() & FL_DUCKING )
{
RestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, ACT_MP_ATTACK_CROUCH_SECONDARYFIRE );
}
else
{
RestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, ACT_MP_ATTACK_STAND_SECONDARYFIRE );
}
iGestureActivity = ACT_VM_PRIMARYATTACK;
break;
}
case PLAYERANIMEVENT_ATTACK_PRE:
{
CTFPlayer *pPlayer = GetTFPlayer();
if ( !pPlayer )
return;
CTFWeaponBase *pWpn = pPlayer->GetActiveTFWeapon();
bool bIsMinigun = ( pWpn && pWpn->GetWeaponID() == TF_WEAPON_MINIGUN );
bool bAutoKillPreFire = false;
if ( bIsMinigun )
{
bAutoKillPreFire = true;
}
if ( m_bInSwim && bIsMinigun )
{
// Weapon pre-fire. Used for minigun windup while swimming
iGestureActivity = ACT_MP_ATTACK_SWIM_PREFIRE;
}
else if ( GetBasePlayer()->GetFlags() & FL_DUCKING )
{
// Weapon pre-fire. Used for minigun windup, sniper aiming start, etc in crouch.
iGestureActivity = ACT_MP_ATTACK_CROUCH_PREFIRE;
}
else
{
// Weapon pre-fire. Used for minigun windup, sniper aiming start, etc.
iGestureActivity = ACT_MP_ATTACK_STAND_PREFIRE;
}
RestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, iGestureActivity, bAutoKillPreFire );
break;
}
case PLAYERANIMEVENT_ATTACK_POST:
{
CTFPlayer *pPlayer = GetTFPlayer();
if ( !pPlayer )
return;
CTFWeaponBase *pWpn = pPlayer->GetActiveTFWeapon();
bool bIsMinigun = ( pWpn && pWpn->GetWeaponID() == TF_WEAPON_MINIGUN );
if ( m_bInSwim && bIsMinigun )
{
// Weapon pre-fire. Used for minigun winddown while swimming
iGestureActivity = ACT_MP_ATTACK_SWIM_POSTFIRE;
}
else if ( GetBasePlayer()->GetFlags() & FL_DUCKING )
{
// Weapon post-fire. Used for minigun winddown in crouch.
iGestureActivity = ACT_MP_ATTACK_CROUCH_POSTFIRE;
}
else
{
// Weapon post-fire. Used for minigun winddown.
iGestureActivity = ACT_MP_ATTACK_STAND_POSTFIRE;
}
RestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, iGestureActivity );
break;
}
case PLAYERANIMEVENT_RELOAD:
{
// Weapon reload.
if ( m_bInAirWalk )
{
RestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, ACT_MP_RELOAD_AIRWALK );
}
else
{
BaseClass::DoAnimationEvent( event, nData );
}
break;
}
case PLAYERANIMEVENT_RELOAD_LOOP:
{
// Weapon reload.
if ( m_bInAirWalk )
{
RestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, ACT_MP_RELOAD_AIRWALK_LOOP );
}
else
{
BaseClass::DoAnimationEvent( event, nData );
}
break;
}
case PLAYERANIMEVENT_RELOAD_END:
{
// Weapon reload.
if ( m_bInAirWalk )
{
RestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, ACT_MP_RELOAD_AIRWALK_END );
}
else
{
BaseClass::DoAnimationEvent( event, nData );
}
break;
}
case PLAYERANIMEVENT_DOUBLEJUMP:
{
// Check to see if we are jumping!
if ( !m_bJumping )
{
m_bJumping = true;
m_bFirstJumpFrame = true;
m_flJumpStartTime = gpGlobals->curtime;
RestartMainSequence();
}
// Force the air walk off.
m_bInAirWalk = false;
// Player the air dash gesture.
RestartGesture( GESTURE_SLOT_JUMP, ACT_MP_DOUBLEJUMP );
break;
}
default:
{
BaseClass::DoAnimationEvent( event, nData );
break;
}
}
#ifdef CLIENT_DLL
// Make the weapon play the animation as well
if ( iGestureActivity != ACT_INVALID )
{
CBaseCombatWeapon *pWeapon = GetTFPlayer()->GetActiveWeapon();
if ( pWeapon )
{
pWeapon->SendWeaponAnim( iGestureActivity );
}
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *idealActivity -
//-----------------------------------------------------------------------------
bool CTFPlayerAnimState::HandleSwimming( Activity &idealActivity )
{
bool bInWater = BaseClass::HandleSwimming( idealActivity );
if ( bInWater )
{
if ( m_pTFPlayer->m_Shared.InCond( TF_COND_AIMING ) )
{
CTFWeaponBase *pWpn = m_pTFPlayer->GetActiveTFWeapon();
if ( pWpn && pWpn->GetWeaponID() == TF_WEAPON_MINIGUN )
{
idealActivity = ACT_MP_SWIM_DEPLOYED;
}
// Check for sniper deployed underwater - should only be when standing on something
else if ( pWpn && pWpn->GetWeaponID() == TF_WEAPON_SNIPERRIFLE )
{
if ( m_pTFPlayer->m_Shared.InCond( TF_COND_ZOOMED ) )
{
idealActivity = ACT_MP_SWIM_DEPLOYED;
}
}
}
}
return bInWater;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *idealActivity -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CTFPlayerAnimState::HandleMoving( Activity &idealActivity )
{
float flSpeed = GetOuterXYSpeed();
// If we move, cancel the deployed anim hold
if ( flSpeed > MOVING_MINIMUM_SPEED )
{
m_flHoldDeployedPoseUntilTime = 0.0;
}
if ( m_pTFPlayer->m_Shared.InCond( TF_COND_AIMING ) )
{
if ( flSpeed > MOVING_MINIMUM_SPEED )
{
idealActivity = ACT_MP_DEPLOYED;
}
else
{
idealActivity = ACT_MP_DEPLOYED_IDLE;
}
}
else if ( m_flHoldDeployedPoseUntilTime > gpGlobals->curtime )
{
// Unless we move, hold the deployed pose for a number of seconds after being deployed
idealActivity = ACT_MP_DEPLOYED_IDLE;
}
else
{
return BaseClass::HandleMoving( idealActivity );
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *idealActivity -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CTFPlayerAnimState::HandleDucking( Activity &idealActivity )
{
if ( GetBasePlayer()->GetFlags() & FL_DUCKING )
{
if ( GetOuterXYSpeed() < MOVING_MINIMUM_SPEED )
{
idealActivity = ACT_MP_CROUCH_IDLE;
if ( m_pTFPlayer->m_Shared.InCond( TF_COND_AIMING ) || m_flHoldDeployedPoseUntilTime > gpGlobals->curtime )
{
idealActivity = ACT_MP_CROUCH_DEPLOYED_IDLE;
}
}
else
{
idealActivity = ACT_MP_CROUCHWALK;
if ( m_pTFPlayer->m_Shared.InCond( TF_COND_AIMING ) )
{
// Don't do this for the heavy! we don't usually let him deployed crouch walk
bool bIsMinigun = false;
CTFPlayer *pPlayer = GetTFPlayer();
if ( pPlayer && pPlayer->GetActiveTFWeapon() )
{
bIsMinigun = ( pPlayer->GetActiveTFWeapon()->GetWeaponID() == TF_WEAPON_MINIGUN );
}
if ( !bIsMinigun )
{
idealActivity = ACT_MP_CROUCH_DEPLOYED;
}
}
}
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
bool CTFPlayerAnimState::HandleJumping( Activity &idealActivity )
{
Vector vecVelocity;
GetOuterAbsVelocity( vecVelocity );
// Don't allow a firing heavy to jump or air walk.
if ( m_pTFPlayer->GetPlayerClass()->IsClass( TF_CLASS_HEAVYWEAPONS ) && m_pTFPlayer->m_Shared.InCond( TF_COND_AIMING ) )
return false;
// Handle air walking before handling jumping - air walking supersedes jump
TFPlayerClassData_t *pData = m_pTFPlayer->GetPlayerClass()->GetData();
bool bValidAirWalkClass = ( pData && pData->m_bDontDoAirwalk == false );
if ( bValidAirWalkClass && ( vecVelocity.z > 300.0f || m_bInAirWalk ) )
{
// Check to see if we were in an airwalk and now we are basically on the ground.
if ( ( GetBasePlayer()->GetFlags() & FL_ONGROUND ) && m_bInAirWalk )
{
m_bInAirWalk = false;
RestartMainSequence();
RestartGesture( GESTURE_SLOT_JUMP, ACT_MP_JUMP_LAND );
}
else if ( GetBasePlayer()->GetWaterLevel() >= WL_Waist )
{
// Turn off air walking and reset the animation.
m_bInAirWalk = false;
RestartMainSequence();
}
else if ( ( GetBasePlayer()->GetFlags() & FL_ONGROUND ) == 0 )
{
// In an air walk.
idealActivity = ACT_MP_AIRWALK;
m_bInAirWalk = true;
}
}
// Jumping.
else
{
if ( m_bJumping )
{
// Remove me once all classes are doing the new jump
TFPlayerClassData_t *pData = m_pTFPlayer->GetPlayerClass()->GetData();
bool bNewJump = ( pData && pData->m_bDontDoNewJump == false );
if ( m_bFirstJumpFrame )
{
m_bFirstJumpFrame = false;
RestartMainSequence(); // Reset the animation.
}
// Reset if we hit water and start swimming.
if ( GetBasePlayer()->GetWaterLevel() >= WL_Waist )
{
m_bJumping = false;
RestartMainSequence();
}
// 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.
else if ( gpGlobals->curtime - m_flJumpStartTime > 0.2f )
{
if ( GetBasePlayer()->GetFlags() & FL_ONGROUND )
{
m_bJumping = false;
RestartMainSequence();
if ( bNewJump )
{
RestartGesture( GESTURE_SLOT_JUMP, ACT_MP_JUMP_LAND );
}
}
}
// if we're still jumping
if ( m_bJumping )
{
if ( bNewJump )
{
if ( gpGlobals->curtime - m_flJumpStartTime > 0.5 )
{
idealActivity = ACT_MP_JUMP_FLOAT;
}
else
{
idealActivity = ACT_MP_JUMP_START;
}
}
else
{
idealActivity = ACT_MP_JUMP;
}
}
}
}
if ( m_bJumping || m_bInAirWalk )
return true;
return false;
}