//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //=============================================================================// #include "cbase.h" #ifdef CLIENT_DLL #include "c_hl2mp_player.h" #include "prediction.h" #define CRecipientFilter C_RecipientFilter #else #include "hl2mp_player.h" #endif #include "engine/IEngineSound.h" #include "SoundEmitterSystem/isoundemittersystembase.h" extern ConVar sv_footsteps; const char *g_ppszPlayerSoundPrefixNames[PLAYER_SOUNDS_MAX] = { "NPC_Citizen", "NPC_CombineS", "NPC_MetroPolice", }; const char *CHL2MP_Player::GetPlayerModelSoundPrefix( void ) { return g_ppszPlayerSoundPrefixNames[m_iPlayerSoundType]; } void CHL2MP_Player::PrecacheFootStepSounds( void ) { int iFootstepSounds = ARRAYSIZE( g_ppszPlayerSoundPrefixNames ); int i; for ( i = 0; i < iFootstepSounds; ++i ) { char szFootStepName[128]; Q_snprintf( szFootStepName, sizeof( szFootStepName ), "%s.RunFootstepLeft", g_ppszPlayerSoundPrefixNames[i] ); PrecacheScriptSound( szFootStepName ); Q_snprintf( szFootStepName, sizeof( szFootStepName ), "%s.RunFootstepRight", g_ppszPlayerSoundPrefixNames[i] ); PrecacheScriptSound( szFootStepName ); } } //----------------------------------------------------------------------------- // Consider the weapon's built-in accuracy, this character's proficiency with // the weapon, and the status of the target. Use this information to determine // how accurately to shoot at the target. //----------------------------------------------------------------------------- Vector CHL2MP_Player::GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget ) { if ( pWeapon ) return pWeapon->GetBulletSpread( WEAPON_PROFICIENCY_PERFECT ); return VECTOR_CONE_15DEGREES; } //----------------------------------------------------------------------------- // Purpose: // Input : step - // fvol - // force - force sound to play //----------------------------------------------------------------------------- void CHL2MP_Player::PlayStepSound( Vector &vecOrigin, surfacedata_t *psurface, float fvol, bool force ) { if ( gpGlobals->maxClients > 1 && !sv_footsteps.GetFloat() ) return; #if defined( CLIENT_DLL ) // during prediction play footstep sounds only once if ( !prediction->IsFirstTimePredicted() ) return; #endif if ( GetFlags() & FL_DUCKING ) return; m_Local.m_nStepside = !m_Local.m_nStepside; char szStepSound[128]; if ( m_Local.m_nStepside ) { Q_snprintf( szStepSound, sizeof( szStepSound ), "%s.RunFootstepLeft", g_ppszPlayerSoundPrefixNames[m_iPlayerSoundType] ); } else { Q_snprintf( szStepSound, sizeof( szStepSound ), "%s.RunFootstepRight", g_ppszPlayerSoundPrefixNames[m_iPlayerSoundType] ); } CSoundParameters params; if ( GetParametersForSound( szStepSound, params, NULL ) == false ) return; CRecipientFilter filter; filter.AddRecipientsByPAS( vecOrigin ); #ifndef CLIENT_DLL // im MP, server removed all players in origins PVS, these players // generate the footsteps clientside if ( gpGlobals->maxClients > 1 ) filter.RemoveRecipientsByPVS( vecOrigin ); #endif EmitSound_t ep; ep.m_nChannel = CHAN_BODY; ep.m_pSoundName = params.soundname; ep.m_flVolume = fvol; ep.m_SoundLevel = params.soundlevel; ep.m_nFlags = 0; ep.m_nPitch = params.pitch; ep.m_pOrigin = &vecOrigin; EmitSound( filter, entindex(), ep ); } //========================== // ANIMATION CODE //========================== // 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( CHL2MP_Player *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; m_flTurnCorrectionTime = 0.0f; }; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPlayerAnimState::Update() { m_angRender = GetOuter()->GetLocalAngles(); m_angRender[ PITCH ] = m_angRender[ ROLL ] = 0.0f; ComputePoseParam_BodyYaw(); ComputePoseParam_BodyPitch(GetOuter()->GetModelPtr()); ComputePoseParam_BodyLookYaw(); ComputePlaybackRate(); #ifdef CLIENT_DLL GetOuter()->UpdateLookAt(); #endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPlayerAnimState::ComputePlaybackRate() { // Determine ideal playback rate Vector vel; GetOuterAbsVelocity( vel ); float speed = vel.Length2D(); bool isMoving = ( speed > 0.5f ) ? true : false; float maxspeed = GetOuter()->GetSequenceGroundSpeed( GetOuter()->GetSequence() ); if ( isMoving && ( maxspeed > 0.0f ) ) { float flFactor = 1.0f; // 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 ); } } //----------------------------------------------------------------------------- // Purpose: // Output : CBasePlayer //----------------------------------------------------------------------------- CHL2MP_Player *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 ); #ifndef CLIENT_DLL //Adrian: Make the model's angle match the legs so the hitboxes match on both sides. GetOuter()->SetLocalAngles( QAngle( GetOuter()->GetAnimEyeAngles().x, m_flCurrentFeetYaw, 0 ) ); #endif } //----------------------------------------------------------------------------- // 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; m_angRender[ PITCH ] = m_angRender[ ROLL ] = 0.0f; // See if we have a blender for pitch GetOuter()->SetPoseParameter( pStudioHdr, "aim_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; m_angRender[ PITCH ] = m_angRender[ ROLL ] = 0.0f; // See if we even have a blender for pitch int upper_body_yaw = GetOuter()->LookupPoseParameter( "aim_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 = 360.0f; Vector vel; GetOuterAbsVelocity( vel ); bool isMoving = ( vel.Length() > 1.0f ) ? true : false; if ( !isMoving ) { // Just stopped moving, try and clamp feet if ( m_flLastTurnTime <= 0.0f ) { m_flLastTurnTime = gpGlobals->curtime; m_flLastYaw = GetOuter()->GetAnimEyeAngles().y; // Snap feet to be perfectly aligned with torso/eyes m_flGoalFeetYaw = GetOuter()->GetAnimEyeAngles().y; m_flCurrentFeetYaw = m_flGoalFeetYaw; m_nTurningInPlace = TURN_NONE; } // If rotating in place, update stasis timer if ( m_flLastYaw != GetOuter()->GetAnimEyeAngles().y ) { m_flLastTurnTime = gpGlobals->curtime; m_flLastYaw = GetOuter()->GetAnimEyeAngles().y; } if ( m_flGoalFeetYaw != m_flCurrentFeetYaw ) { m_flLastTurnTime = gpGlobals->curtime; } turning = ConvergeAngles( m_flGoalFeetYaw, turnrate, gpGlobals->frametime, m_flCurrentFeetYaw ); QAngle eyeAngles = GetOuter()->GetAnimEyeAngles(); QAngle vAngle = GetOuter()->GetLocalAngles(); // See how far off current feetyaw is from true yaw float yawdelta = GetOuter()->GetAnimEyeAngles().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 > 45 ) { 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()->GetAnimEyeAngles().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()->GetAnimEyeAngles().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_flCurrentFeetYaw = m_flGoalFeetYaw = GetOuter()->GetAnimEyeAngles().y; flGoalTorsoYaw = 0.0f; m_flCurrentTorsoYaw = GetOuter()->GetAnimEyeAngles().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; } } // Rotate entire body into position absangles = GetOuter()->GetAbsAngles(); absangles.y = m_flCurrentFeetYaw; m_angRender = absangles; m_angRender[ PITCH ] = m_angRender[ ROLL ] = 0.0f; GetOuter()->SetPoseParameter( upper_body_yaw, clamp( m_flCurrentTorsoYaw, -60.0f, 60.0f ) ); /* // FIXME: Adrian, what is this? int body_yaw = GetOuter()->LookupPoseParameter( "body_yaw" ); if ( body_yaw >= 0 ) { GetOuter()->SetPoseParameter( body_yaw, 30 ); } */ } //----------------------------------------------------------------------------- // 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 }