Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
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

//========= 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
}