You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
825 lines
21 KiB
825 lines
21 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: TF2's player object, code shared between client & server. |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#include "basetfplayer_shared.h" |
|
#include "weapon_combatshield.h" |
|
#include "weapon_objectselection.h" |
|
#include "weapon_twohandedcontainer.h" |
|
#ifdef CLIENT_DLL |
|
#include "c_weapon_builder.h" |
|
#else |
|
#include "weapon_builder.h" |
|
#include "basegrenade_shared.h" |
|
#include "grenade_objectsapper.h" |
|
#endif |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CBaseTFPlayer::IsClass( TFClass iClass ) |
|
{ |
|
if ( !GetPlayerClass() ) |
|
{ |
|
// Special case for undecided players |
|
if ( iClass == TFCLASS_UNDECIDED ) |
|
return true; |
|
return false; |
|
} |
|
|
|
return ( PlayerClass() == iClass ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CWeaponCombatShield *CBaseTFPlayer::GetCombatShield( void ) |
|
{ |
|
if ( !m_hWeaponCombatShield ) |
|
{ |
|
if ( GetTeamNumber() == TEAM_ALIENS ) |
|
{ |
|
m_hWeaponCombatShield = static_cast< CWeaponCombatShield * >( Weapon_OwnsThisType( "weapon_combat_shield_alien" ) ); |
|
#ifndef CLIENT_DLL |
|
if ( !m_hWeaponCombatShield ) |
|
{ |
|
m_hWeaponCombatShield = static_cast< CWeaponCombatShield * >( GiveNamedItem( "weapon_combat_shield_alien" ) ); |
|
} |
|
#endif |
|
} |
|
else |
|
{ |
|
m_hWeaponCombatShield = static_cast< CWeaponCombatShield * >( Weapon_OwnsThisType( "weapon_combat_shield" ) ); |
|
#ifndef CLIENT_DLL |
|
if ( !m_hWeaponCombatShield ) |
|
{ |
|
m_hWeaponCombatShield = static_cast< CWeaponCombatShield * >( GiveNamedItem( "weapon_combat_shield" ) ); |
|
} |
|
#endif |
|
} |
|
} |
|
|
|
return m_hWeaponCombatShield; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Check to see if the shot is blocked by the player's handheld shield |
|
//----------------------------------------------------------------------------- |
|
bool CBaseTFPlayer::IsHittingShield( const Vector &vecVelocity, float *flDamage ) |
|
{ |
|
if (!IsParrying() && !IsBlocking()) |
|
return false; |
|
|
|
Vector2D vecDelta = vecVelocity.AsVector2D(); |
|
Vector2DNormalize( vecDelta ); |
|
|
|
Vector forward; |
|
AngleVectors( GetLocalAngles(), &forward ); |
|
|
|
Vector2DNormalize( forward.AsVector2D() ); |
|
|
|
float flDot = DotProduct2D( vecDelta, forward.AsVector2D() ); |
|
|
|
// This gives us a little more than a 90 degree protection angle |
|
if (flDot < -0.67f) |
|
{ |
|
// We've hit the players handheld shield, see if the shield can do anything about it |
|
if ( flDamage && GetCombatShield() ) |
|
{ |
|
// Return true if the shield blocked it all |
|
*flDamage = GetCombatShield()->AttemptToBlock( *flDamage ); |
|
return ( !(*flDamage) ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Play a sound to show we've been hurt |
|
//----------------------------------------------------------------------------- |
|
void CBaseTFPlayer::PainSound( void ) |
|
{ |
|
char *sSoundName = NULL; |
|
|
|
if ( GetTeamNumber() == TEAM_HUMANS ) |
|
{ |
|
sSoundName = "Humans.Pain"; |
|
} |
|
else if ( GetTeamNumber() == TEAM_ALIENS ) |
|
{ |
|
switch( PlayerClass() ) |
|
{ |
|
case TFCLASS_COMMANDO: |
|
sSoundName = "AlienCommando.Pain"; |
|
break; |
|
|
|
case TFCLASS_MEDIC: |
|
sSoundName = "AlienMedic.Pain"; |
|
break; |
|
|
|
case TFCLASS_DEFENDER: |
|
sSoundName = "AlienDefender.Pain"; |
|
break; |
|
|
|
case TFCLASS_ESCORT: |
|
sSoundName = "AlienEscort.Pain"; |
|
break; |
|
|
|
default: |
|
break; |
|
} |
|
} |
|
|
|
if ( !sSoundName ) |
|
return; |
|
|
|
CPASAttenuationFilter filter( this, sSoundName ); |
|
EmitSound( filter, entindex(), sSoundName ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Return true if we should record our last weapon when switching between the two specified weapons |
|
//----------------------------------------------------------------------------- |
|
bool CBaseTFPlayer::Weapon_ShouldSetLast( CBaseCombatWeapon *pOldWeapon, CBaseCombatWeapon *pNewWeapon ) |
|
{ |
|
// Don't record last weapons when switching to an object |
|
if ( dynamic_cast< CWeaponObjectSelection* >( pNewWeapon ) ) |
|
{ |
|
// Store this weapon off so we can switch back to it |
|
// Don't store it if it's also an object |
|
CBaseCombatWeapon *pLast = pOldWeapon->GetLastWeapon(); |
|
#ifdef CLIENT_DLL |
|
if ( !dynamic_cast< C_WeaponBuilder* >( pLast ) ) |
|
#else |
|
if ( !dynamic_cast< CWeaponBuilder* >( pLast ) ) |
|
#endif |
|
{ |
|
m_hLastWeaponBeforeObject = pLast; |
|
} |
|
return false; |
|
} |
|
|
|
// Don't record last weapons when switching from the builder |
|
// If the old weapon is a twohanded container, check the left weapon |
|
CWeaponTwoHandedContainer *pContainer = dynamic_cast< CWeaponTwoHandedContainer * >( pOldWeapon ); |
|
if ( pContainer ) |
|
{ |
|
pOldWeapon = dynamic_cast< CBaseTFCombatWeapon * >( pContainer->GetLeftWeapon() ); |
|
} |
|
#ifdef CLIENT_DLL |
|
if ( dynamic_cast< C_WeaponBuilder* >( pOldWeapon ) ) |
|
return false; |
|
#else |
|
if ( dynamic_cast< CWeaponBuilder* >( pOldWeapon ) ) |
|
return false; |
|
#endif |
|
|
|
return BaseClass::Weapon_ShouldSetLast( pOldWeapon, pNewWeapon ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Return true if we should allow selection of the specified item |
|
//----------------------------------------------------------------------------- |
|
bool CBaseTFPlayer::Weapon_ShouldSelectItem( CBaseCombatWeapon *pWeapon ) |
|
{ |
|
CBaseCombatWeapon *pActiveWeapon = GetActiveWeapon(); |
|
// If the old weapon is a twohanded container, check the left weapon |
|
CWeaponTwoHandedContainer *pContainer = dynamic_cast< CWeaponTwoHandedContainer * >( pActiveWeapon ); |
|
if ( pContainer ) |
|
{ |
|
pActiveWeapon = pContainer->GetLeftWeapon(); |
|
} |
|
|
|
return ( pWeapon != pActiveWeapon ); |
|
} |
|
|
|
#ifndef CLIENT_DLL |
|
// Sapper handling is all here because it'll soon be shared Client / Server |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CBaseTFPlayer::IsAttachingSapper( void ) |
|
{ |
|
return ( m_TFLocal.m_bAttachingSapper ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
float CBaseTFPlayer::GetSapperAttachmentTime( void ) |
|
{ |
|
return (gpGlobals->curtime - m_flSapperAttachmentStartTime); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseTFPlayer::StartAttachingSapper( CBaseObject *pObject, CGrenadeObjectSapper *pSapper ) |
|
{ |
|
Assert( pSapper ); |
|
|
|
m_TFLocal.m_bAttachingSapper = true; |
|
m_TFLocal.m_flSapperAttachmentFrac = 0.0f; |
|
|
|
m_hSappedObject = pObject; |
|
m_flSapperAttachmentStartTime = gpGlobals->curtime; |
|
m_flSapperAttachmentFinishTime = gpGlobals->curtime + m_hSappedObject->GetSapperAttachTime(); |
|
m_hSapper = pSapper; |
|
m_hSapper->SetArmed( false ); |
|
|
|
CPASAttenuationFilter filter( m_hSapper, "WeaponObjectSapper.Attach" ); |
|
EmitSound( filter, m_hSapper->entindex(), "WeaponObjectSapper.Attach" ); |
|
|
|
// Drop the player's weapon |
|
if ( GetActiveWeapon() ) |
|
{ |
|
GetActiveWeapon()->Holster(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseTFPlayer::CheckSapperAttaching( void ) |
|
{ |
|
// Did we stop attaching? |
|
if ( !m_TFLocal.m_bAttachingSapper ) |
|
{ |
|
if ( m_TFLocal.m_flSapperAttachmentFrac ) |
|
{ |
|
StopAttaching(); |
|
} |
|
return; |
|
} |
|
|
|
// Object gone? |
|
if ( m_hSappedObject == NULL ) |
|
{ |
|
StopAttaching(); |
|
return; |
|
} |
|
|
|
// Sapper gone? |
|
if ( m_hSapper == NULL ) |
|
{ |
|
StopAttaching(); |
|
return; |
|
} |
|
|
|
// Make sure I'm still looking at the target |
|
trace_t tr; |
|
Vector vecAiming; |
|
Vector vecSrc = EyePosition(); |
|
EyeVectors( &vecAiming ); |
|
UTIL_TraceLine( vecSrc, vecSrc + (vecAiming * 128), MASK_SOLID, this, TFCOLLISION_GROUP_WEAPON, &tr ); |
|
if ( tr.fraction == 1.0 || tr.m_pEnt != m_hSappedObject ) |
|
{ |
|
StopAttaching(); |
|
return; |
|
} |
|
|
|
// Finished? |
|
if ( m_flSapperAttachmentFinishTime >= gpGlobals->curtime ) |
|
{ |
|
float dt = m_flSapperAttachmentFinishTime - m_flSapperAttachmentStartTime; |
|
if ( dt > 0.0f ) |
|
{ |
|
m_TFLocal.m_flSapperAttachmentFrac = ( gpGlobals->curtime - m_flSapperAttachmentStartTime ) / dt; |
|
m_TFLocal.m_flSapperAttachmentFrac = clamp( m_TFLocal.m_flSapperAttachmentFrac, 0.0f, 1.0f ); |
|
} |
|
else |
|
{ |
|
m_TFLocal.m_flSapperAttachmentFrac = 0.0f; |
|
} |
|
return; |
|
} |
|
|
|
FinishAttaching(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseTFPlayer::CleanupAfterAttaching( void ) |
|
{ |
|
Assert( m_TFLocal.m_bAttachingSapper ); |
|
m_TFLocal.m_bAttachingSapper = false; |
|
|
|
m_flSapperAttachmentFinishTime = -1; |
|
m_flSapperAttachmentStartTime = -1; |
|
m_TFLocal.m_flSapperAttachmentFrac = 0.0f; |
|
|
|
// Restore the player's weapon |
|
m_flNextAttack = gpGlobals->curtime; |
|
if ( GetActiveWeapon() ) |
|
{ |
|
GetActiveWeapon()->Deploy(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseTFPlayer::StopAttaching( void ) |
|
{ |
|
CleanupAfterAttaching(); |
|
|
|
if ( m_hSapper != NULL ) |
|
{ |
|
CPASAttenuationFilter filter( m_hSapper, "WeaponObjectSapper.AttachFail" ); |
|
EmitSound( filter, m_hSapper->entindex(), "WeaponObjectSapper.AttachFail" ); |
|
|
|
m_hSapper->SetTargetObject( NULL ); |
|
m_hSapper->Remove( ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseTFPlayer::FinishAttaching( void ) |
|
{ |
|
CleanupAfterAttaching(); |
|
|
|
if ( m_hSapper != NULL ) |
|
{ |
|
m_hSapper->SetTargetObject( m_hSappedObject ); |
|
m_hSapper->SetArmed( true ); |
|
} |
|
} |
|
#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 90.0f |
|
// Below this amount, don't play a turning animation/perform IK |
|
#define MIN_TURN_ANGLE_REQUIRING_TURN_ANIMATION 15.0f |
|
|
|
static ConVar tf2_feetyawrunscale( "tf2_feetyawrunscale", "2", FCVAR_REPLICATED, "Multiplier on tf2_feetyawrate to allow turning faster when running." ); |
|
extern ConVar sv_backspeed; |
|
extern ConVar mp_feetyawrate; |
|
extern ConVar mp_facefronttime; |
|
extern ConVar mp_ik; |
|
|
|
CPlayerAnimState::CPlayerAnimState( CBaseTFPlayer *outer ) |
|
: m_pOuter( outer ) |
|
{ |
|
m_flGaitYaw = 0.0f; |
|
m_flGoalFeetYaw = 0.0f; |
|
m_flCurrentFeetYaw = 0.0f; |
|
m_flCurrentTorsoYaw = 0.0f; |
|
m_flLastYaw = 0.0f; |
|
m_flLastTurnTime = 0.0f; |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPlayerAnimState::Update() |
|
{ |
|
m_angRender = GetOuter()->GetLocalAngles(); |
|
|
|
ComputePoseParam_BodyYaw(); |
|
ComputePoseParam_BodyPitch( GetOuter()->GetModelPtr() ); |
|
ComputePoseParam_BodyLookYaw(); |
|
|
|
ComputePlaybackRate(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPlayerAnimState::ComputePlaybackRate() |
|
{ |
|
// Determine ideal playback rate |
|
Vector vel; |
|
GetOuterAbsVelocity( vel ); |
|
|
|
float speed = vel.Length2D(); |
|
|
|
bool isMoving = ( speed > 0.5f ) ? true : false; |
|
|
|
Activity currentActivity = GetOuter()->GetSequenceActivity( GetOuter()->GetSequence() ); |
|
|
|
switch ( currentActivity ) |
|
{ |
|
case ACT_WALK: |
|
case ACT_RUN: |
|
case ACT_IDLE: |
|
{ |
|
float maxspeed = GetOuter()->MaxSpeed(); |
|
if ( isMoving && ( maxspeed > 0.0f ) ) |
|
{ |
|
float flFactor = 1.0f; |
|
|
|
// HACK HACK:: Defender backward animation is animated at 0.6 times speed, so scale up animation for this class |
|
// if he's running backward. |
|
|
|
// Not sure if we're really going to do all classes this way. |
|
if ( GetOuter()->IsClass( TFCLASS_DEFENDER ) || |
|
GetOuter()->IsClass( TFCLASS_MEDIC ) ) |
|
{ |
|
Vector facing; |
|
Vector moving; |
|
|
|
moving = vel; |
|
AngleVectors( GetOuter()->GetLocalAngles(), &facing ); |
|
VectorNormalize( moving ); |
|
|
|
float dot = moving.Dot( facing ); |
|
if ( dot < 0.0f ) |
|
{ |
|
float backspeed = sv_backspeed.GetFloat(); |
|
flFactor = 1.0f - fabs( dot ) * (1.0f - backspeed); |
|
|
|
if ( flFactor > 0.0f ) |
|
{ |
|
flFactor = 1.0f / flFactor; |
|
} |
|
} |
|
} |
|
|
|
// Note this gets set back to 1.0 if sequence changes due to ResetSequenceInfo below |
|
GetOuter()->SetPlaybackRate( ( speed * flFactor ) / maxspeed ); |
|
|
|
// BUG BUG: |
|
// This stuff really should be m_flPlaybackRate = speed / m_flGroundSpeed |
|
} |
|
else |
|
{ |
|
GetOuter()->SetPlaybackRate( 1.0f ); |
|
} |
|
} |
|
break; |
|
default: |
|
{ |
|
GetOuter()->SetPlaybackRate( 1.0f ); |
|
} |
|
break; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : CBasePlayer |
|
//----------------------------------------------------------------------------- |
|
CBaseTFPlayer *CPlayerAnimState::GetOuter() |
|
{ |
|
return m_pOuter; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : dt - |
|
//----------------------------------------------------------------------------- |
|
void CPlayerAnimState::EstimateYaw( void ) |
|
{ |
|
float dt = gpGlobals->frametime; |
|
|
|
if ( !dt ) |
|
{ |
|
return; |
|
} |
|
|
|
Vector est_velocity; |
|
QAngle angles; |
|
|
|
GetOuterAbsVelocity( est_velocity ); |
|
|
|
angles = GetOuter()->GetLocalAngles(); |
|
|
|
if ( est_velocity[1] == 0 && est_velocity[0] == 0 ) |
|
{ |
|
float flYawDiff = angles[YAW] - m_flGaitYaw; |
|
flYawDiff = flYawDiff - (int)(flYawDiff / 360) * 360; |
|
if (flYawDiff > 180) |
|
flYawDiff -= 360; |
|
if (flYawDiff < -180) |
|
flYawDiff += 360; |
|
|
|
if (dt < 0.25) |
|
flYawDiff *= dt * 4; |
|
else |
|
flYawDiff *= dt; |
|
|
|
m_flGaitYaw += flYawDiff; |
|
m_flGaitYaw = m_flGaitYaw - (int)(m_flGaitYaw / 360) * 360; |
|
} |
|
else |
|
{ |
|
m_flGaitYaw = (atan2(est_velocity[1], est_velocity[0]) * 180 / M_PI); |
|
|
|
if (m_flGaitYaw > 180) |
|
m_flGaitYaw = 180; |
|
else if (m_flGaitYaw < -180) |
|
m_flGaitYaw = -180; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Override for backpeddling |
|
// Input : dt - |
|
//----------------------------------------------------------------------------- |
|
void CPlayerAnimState::ComputePoseParam_BodyYaw( void ) |
|
{ |
|
int iYaw = GetOuter()->LookupPoseParameter( "move_yaw" ); |
|
if ( iYaw < 0 ) |
|
return; |
|
|
|
// view direction relative to movement |
|
float flYaw; |
|
|
|
EstimateYaw(); |
|
|
|
QAngle angles = GetOuter()->GetLocalAngles(); |
|
float ang = angles[ YAW ]; |
|
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; |
|
} |
|
|
|
GetOuter()->SetPoseParameter( iYaw, flYaw ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPlayerAnimState::ComputePoseParam_BodyPitch( CStudioHdr *pStudioHdr ) |
|
{ |
|
// Get pitch from v_angle |
|
float flPitch = GetOuter()->GetLocalAngles()[ PITCH ]; |
|
if ( flPitch > 180.0f ) |
|
{ |
|
flPitch -= 360.0f; |
|
} |
|
flPitch = clamp( flPitch, -90, 90 ); |
|
|
|
QAngle absangles = GetOuter()->GetAbsAngles(); |
|
absangles.x = 0.0f; |
|
m_angRender = absangles; |
|
|
|
// See if we have a blender for pitch |
|
int pitch = GetOuter()->LookupPoseParameter( pStudioHdr, "body_pitch" ); |
|
if ( pitch < 0 ) |
|
return; |
|
|
|
GetOuter()->SetPoseParameter( pStudioHdr, pitch, flPitch ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : goal - |
|
// maxrate - |
|
// dt - |
|
// current - |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int CPlayerAnimState::ConvergeAngles( float goal,float maxrate, float dt, float& current ) |
|
{ |
|
int direction = TURN_NONE; |
|
|
|
float anglediff = goal - current; |
|
float anglediffabs = fabs( anglediff ); |
|
|
|
anglediff = AngleNormalize( 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 ( fabs( anglediff ) < maxmove ) |
|
{ |
|
current = goal; |
|
} |
|
else |
|
{ |
|
if ( anglediff > 0 ) |
|
{ |
|
current += maxmove; |
|
direction = TURN_LEFT; |
|
} |
|
else |
|
{ |
|
current -= maxmove; |
|
direction = TURN_RIGHT; |
|
} |
|
} |
|
|
|
current = AngleNormalize( current ); |
|
|
|
return direction; |
|
} |
|
|
|
void CPlayerAnimState::ComputePoseParam_BodyLookYaw( void ) |
|
{ |
|
QAngle absangles = GetOuter()->GetAbsAngles(); |
|
absangles.y = AngleNormalize( absangles.y ); |
|
m_angRender = absangles; |
|
|
|
// See if we even have a blender for pitch |
|
int upper_body_yaw = GetOuter()->LookupPoseParameter( "body_yaw" ); |
|
if ( upper_body_yaw < 0 ) |
|
{ |
|
return; |
|
} |
|
|
|
// Assume upper and lower bodies are aligned and that we're not turning |
|
float flGoalTorsoYaw = 0.0f; |
|
int turning = TURN_NONE; |
|
float turnrate = mp_feetyawrate.GetFloat(); |
|
|
|
Vector vel; |
|
|
|
GetOuterAbsVelocity( vel ); |
|
|
|
bool isMoving = ( vel.Length() > 0.0f ) ? true : false; |
|
|
|
if ( !isMoving ) |
|
{ |
|
// Just stopped moving, try and clamp feet |
|
if ( m_flLastTurnTime <= 0.0f ) |
|
{ |
|
m_flLastTurnTime = gpGlobals->curtime; |
|
m_flLastYaw = GetOuter()->GetAbsAngles().y; |
|
// Snap feet to be perfectly aligned with torso/eyes |
|
m_flGoalFeetYaw = GetOuter()->GetAbsAngles().y; |
|
m_flCurrentFeetYaw = m_flGoalFeetYaw; |
|
m_nTurningInPlace = TURN_NONE; |
|
} |
|
|
|
// If rotating in place, update stasis timer |
|
if ( m_flLastYaw != GetOuter()->GetAbsAngles().y ) |
|
{ |
|
m_flLastTurnTime = gpGlobals->curtime; |
|
m_flLastYaw = GetOuter()->GetAbsAngles().y; |
|
} |
|
|
|
if ( m_flGoalFeetYaw != m_flCurrentFeetYaw ) |
|
{ |
|
m_flLastTurnTime = gpGlobals->curtime; |
|
} |
|
|
|
turning = ConvergeAngles( m_flGoalFeetYaw, turnrate, gpGlobals->frametime, m_flCurrentFeetYaw ); |
|
|
|
// See how far off current feetyaw is from true yaw |
|
float yawdelta = GetOuter()->GetAbsAngles().y - m_flCurrentFeetYaw; |
|
yawdelta = AngleNormalize( yawdelta ); |
|
|
|
bool rotated_too_far = false; |
|
|
|
float yawmagnitude = fabs( yawdelta ); |
|
// If too far, then need to turn in place |
|
if ( yawmagnitude > MAX_TORSO_ANGLE ) |
|
{ |
|
rotated_too_far = true; |
|
} |
|
|
|
// Standing still for a while, rotate feet around to face forward |
|
// Or rotated too far |
|
// FIXME: Play an in place turning animation |
|
if ( rotated_too_far || |
|
( gpGlobals->curtime > m_flLastTurnTime + mp_facefronttime.GetFloat() ) ) |
|
{ |
|
m_flGoalFeetYaw = GetOuter()->GetAbsAngles().y; |
|
m_flLastTurnTime = gpGlobals->curtime; |
|
|
|
float yd = m_flCurrentFeetYaw - m_flGoalFeetYaw; |
|
if ( yd > 0 ) |
|
{ |
|
m_nTurningInPlace = TURN_RIGHT; |
|
} |
|
else if ( yd < 0 ) |
|
{ |
|
m_nTurningInPlace = TURN_LEFT; |
|
} |
|
else |
|
{ |
|
m_nTurningInPlace = TURN_NONE; |
|
} |
|
|
|
turning = ConvergeAngles( m_flGoalFeetYaw, turnrate, gpGlobals->frametime, m_flCurrentFeetYaw ); |
|
yawdelta = GetOuter()->GetAbsAngles().y - m_flCurrentFeetYaw; |
|
} |
|
|
|
// Snap upper body into position since the delta is already smoothed for the feet |
|
flGoalTorsoYaw = yawdelta; |
|
m_flCurrentTorsoYaw = flGoalTorsoYaw; |
|
} |
|
else |
|
{ |
|
m_flLastTurnTime = 0.0f; |
|
m_nTurningInPlace = TURN_NONE; |
|
m_flGoalFeetYaw = GetOuter()->GetAbsAngles().y; |
|
flGoalTorsoYaw = 0.0f; |
|
turning = ConvergeAngles( m_flGoalFeetYaw, turnrate, gpGlobals->frametime, m_flCurrentFeetYaw ); |
|
m_flCurrentTorsoYaw = GetOuter()->GetAbsAngles().y - m_flCurrentFeetYaw; |
|
} |
|
|
|
|
|
if ( turning == TURN_NONE ) |
|
{ |
|
m_nTurningInPlace = turning; |
|
} |
|
|
|
if ( m_nTurningInPlace != TURN_NONE ) |
|
{ |
|
// If we're close to finishing the turn, then turn off the turning animation |
|
if ( fabs( m_flCurrentFeetYaw - m_flGoalFeetYaw ) < MIN_TURN_ANGLE_REQUIRING_TURN_ANIMATION ) |
|
{ |
|
m_nTurningInPlace = TURN_NONE; |
|
} |
|
} |
|
|
|
// Counter rotate upper body as needed |
|
ConvergeAngles( flGoalTorsoYaw, turnrate, gpGlobals->frametime, m_flCurrentTorsoYaw ); |
|
|
|
// Rotate entire body into position |
|
absangles = GetOuter()->GetAbsAngles(); |
|
absangles.y = m_flCurrentFeetYaw; |
|
m_angRender = absangles; |
|
|
|
GetOuter()->SetPoseParameter( upper_body_yaw, clamp( m_flCurrentTorsoYaw, -90.0f, 90.0f ) ); |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : activity - |
|
// Output : Activity |
|
//----------------------------------------------------------------------------- |
|
Activity CPlayerAnimState::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: |
|
return ACT_TURNRIGHT45; |
|
case TURN_LEFT: |
|
return ACT_TURNLEFT45; |
|
*/ |
|
case TURN_RIGHT: |
|
case TURN_LEFT: |
|
return mp_ik.GetBool() ? ACT_TURN : activity; |
|
} |
|
|
|
Assert( 0 ); |
|
return activity; |
|
} |
|
|
|
const QAngle& CPlayerAnimState::GetRenderAngles() |
|
{ |
|
return m_angRender; |
|
} |
|
|
|
|
|
void CPlayerAnimState::GetOuterAbsVelocity( Vector& vel ) |
|
{ |
|
#if defined( CLIENT_DLL ) |
|
GetOuter()->EstimateAbsVelocity( vel ); |
|
#else |
|
vel = GetOuter()->GetAbsVelocity(); |
|
#endif |
|
}
|
|
|