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.
926 lines
26 KiB
926 lines
26 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
// |
|
//=============================================================================// |
|
#include "cbase.h" |
|
|
|
#ifdef CLIENT_DLL |
|
#include "c_portal_player.h" |
|
#include "prediction.h" |
|
#define CRecipientFilter C_RecipientFilter |
|
#else |
|
#include "portal_player.h" |
|
#include "ai_basenpc.h" |
|
#include "portal_gamestats.h" |
|
#include "util.h" |
|
#endif |
|
|
|
#include "engine/IEngineSound.h" |
|
#include "SoundEmitterSystem/isoundemittersystembase.h" |
|
|
|
acttable_t unarmedActtable[] = |
|
{ |
|
{ ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_MELEE, false }, |
|
{ ACT_HL2MP_RUN, ACT_HL2MP_RUN_MELEE, false }, |
|
{ ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_MELEE, false }, |
|
{ ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_MELEE, false }, |
|
{ ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE, false }, |
|
{ ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_MELEE, false }, |
|
{ ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_MELEE, false }, |
|
}; |
|
|
|
const char *g_pszChellConcepts[] = |
|
{ |
|
"CONCEPT_CHELL_IDLE", |
|
"CONCEPT_CHELL_DEAD", |
|
}; |
|
|
|
extern ConVar sv_footsteps; |
|
extern ConVar sv_debug_player_use; |
|
|
|
extern float IntervalDistance( float x, float x0, float x1 ); |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// 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 CPortal_Player::GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget ) |
|
{ |
|
if ( pWeapon ) |
|
return pWeapon->GetBulletSpread( WEAPON_PROFICIENCY_PERFECT ); |
|
|
|
return VECTOR_CONE_15DEGREES; |
|
} |
|
|
|
void CPortal_Player::GetStepSoundVelocities( float *velwalk, float *velrun ) |
|
{ |
|
// UNDONE: need defined numbers for run, walk, crouch, crouch run velocities!!!! |
|
if ( ( GetFlags() & FL_DUCKING ) || ( GetMoveType() == MOVETYPE_LADDER ) ) |
|
{ |
|
*velwalk = 10; // These constants should be based on cl_movespeedkey * cl_forwardspeed somehow |
|
*velrun = 60; |
|
} |
|
else |
|
{ |
|
*velwalk = 90; |
|
*velrun = 220; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : step - |
|
// fvol - |
|
// force - force sound to play |
|
//----------------------------------------------------------------------------- |
|
void CPortal_Player::PlayStepSound( Vector &vecOrigin, surfacedata_t *psurface, float fvol, bool force ) |
|
{ |
|
#ifndef CLIENT_DLL |
|
IncrementStepsTaken(); |
|
#endif |
|
|
|
BaseClass::PlayStepSound( vecOrigin, psurface, fvol, force ); |
|
} |
|
|
|
Activity CPortal_Player::TranslateActivity( Activity baseAct, bool *pRequired /* = NULL */ ) |
|
{ |
|
Activity translated = baseAct; |
|
|
|
if ( GetActiveWeapon() ) |
|
{ |
|
translated = GetActiveWeapon()->ActivityOverride( baseAct, pRequired ); |
|
} |
|
else if ( unarmedActtable ) |
|
{ |
|
acttable_t *pTable = unarmedActtable; |
|
int actCount = ARRAYSIZE(unarmedActtable); |
|
|
|
for ( int i = 0; i < actCount; i++, pTable++ ) |
|
{ |
|
if ( baseAct == pTable->baseAct ) |
|
{ |
|
translated = (Activity)pTable->weaponAct; |
|
} |
|
} |
|
} |
|
else if (pRequired) |
|
{ |
|
*pRequired = false; |
|
} |
|
|
|
return translated; |
|
} |
|
|
|
CWeaponPortalBase* CPortal_Player::GetActivePortalWeapon() const |
|
{ |
|
CBaseCombatWeapon *pWeapon = GetActiveWeapon(); |
|
if ( pWeapon ) |
|
{ |
|
return dynamic_cast< CWeaponPortalBase* >( pWeapon ); |
|
} |
|
else |
|
{ |
|
return NULL; |
|
} |
|
} |
|
|
|
CBaseEntity *CPortal_Player::FindUseEntity() |
|
{ |
|
Vector forward, up; |
|
EyeVectors( &forward, NULL, &up ); |
|
|
|
trace_t tr; |
|
// Search for objects in a sphere (tests for entities that are not solid, yet still useable) |
|
Vector searchCenter = EyePosition(); |
|
|
|
// NOTE: Some debris objects are useable too, so hit those as well |
|
// A button, etc. can be made out of clip brushes, make sure it's +useable via a traceline, too. |
|
int useableContents = MASK_SOLID | CONTENTS_DEBRIS | CONTENTS_PLAYERCLIP; |
|
|
|
UTIL_TraceLine( searchCenter, searchCenter + forward * 1024, useableContents, this, COLLISION_GROUP_NONE, &tr ); |
|
// try the hit entity if there is one, or the ground entity if there isn't. |
|
CBaseEntity *pNearest = NULL; |
|
CBaseEntity *pObject = tr.m_pEnt; |
|
|
|
// TODO: Removed because we no longer have ghost animatings. We may need similar code that clips rays against transformed objects. |
|
//#ifndef CLIENT_DLL |
|
// // Check for ghost animatings (these aren't hit in the normal trace because they aren't solid) |
|
// if ( !IsUseableEntity(pObject, 0) ) |
|
// { |
|
// Ray_t rayGhostAnimating; |
|
// rayGhostAnimating.Init( searchCenter, searchCenter + forward * 1024 ); |
|
// |
|
// CBaseEntity *list[1024]; |
|
// int nCount = UTIL_EntitiesAlongRay( list, 1024, rayGhostAnimating, 0 ); |
|
// |
|
// // Loop through all entities along the pick up ray |
|
// for ( int i = 0; i < nCount; i++ ) |
|
// { |
|
// CGhostAnimating *pGhostAnimating = dynamic_cast<CGhostAnimating*>( list[i] ); |
|
// |
|
// // If the entity is a ghost animating |
|
// if( pGhostAnimating ) |
|
// { |
|
// trace_t trGhostAnimating; |
|
// enginetrace->ClipRayToEntity( rayGhostAnimating, MASK_ALL, pGhostAnimating, &trGhostAnimating ); |
|
// |
|
// if ( trGhostAnimating.fraction < tr.fraction ) |
|
// { |
|
// // If we're not grabbing the clipped ghost |
|
// VPlane plane = pGhostAnimating->GetLocalClipPlane(); |
|
// UTIL_Portal_PlaneTransform( pGhostAnimating->GetCloneTransform(), plane, plane ); |
|
// if ( plane.GetPointSide( trGhostAnimating.endpos ) != SIDE_FRONT ) |
|
// { |
|
// tr = trGhostAnimating; |
|
// pObject = tr.m_pEnt; |
|
// } |
|
// } |
|
// } |
|
// } |
|
// } |
|
//#endif |
|
|
|
int count = 0; |
|
// UNDONE: Might be faster to just fold this range into the sphere query |
|
const int NUM_TANGENTS = 7; |
|
while ( !IsUseableEntity(pObject, 0) && count < NUM_TANGENTS) |
|
{ |
|
// trace a box at successive angles down |
|
// 45 deg, 30 deg, 20 deg, 15 deg, 10 deg, -10, -15 |
|
const float tangents[NUM_TANGENTS] = { 1, 0.57735026919f, 0.3639702342f, 0.267949192431f, 0.1763269807f, -0.1763269807f, -0.267949192431f }; |
|
Vector down = forward - tangents[count]*up; |
|
VectorNormalize(down); |
|
UTIL_TraceHull( searchCenter, searchCenter + down * 72, -Vector(16,16,16), Vector(16,16,16), useableContents, this, COLLISION_GROUP_NONE, &tr ); |
|
pObject = tr.m_pEnt; |
|
count++; |
|
} |
|
float nearestDot = CONE_90_DEGREES; |
|
if ( IsUseableEntity(pObject, 0) ) |
|
{ |
|
Vector delta = tr.endpos - tr.startpos; |
|
float centerZ = CollisionProp()->WorldSpaceCenter().z; |
|
delta.z = IntervalDistance( tr.endpos.z, centerZ + CollisionProp()->OBBMins().z, centerZ + CollisionProp()->OBBMaxs().z ); |
|
float dist = delta.Length(); |
|
if ( dist < PLAYER_USE_RADIUS ) |
|
{ |
|
#ifndef CLIENT_DLL |
|
|
|
if ( sv_debug_player_use.GetBool() ) |
|
{ |
|
NDebugOverlay::Line( searchCenter, tr.endpos, 0, 255, 0, true, 30 ); |
|
NDebugOverlay::Cross3D( tr.endpos, 16, 0, 255, 0, true, 30 ); |
|
} |
|
|
|
if ( pObject->MyNPCPointer() && pObject->MyNPCPointer()->IsPlayerAlly( this ) ) |
|
{ |
|
// If about to select an NPC, do a more thorough check to ensure |
|
// that we're selecting the right one from a group. |
|
pObject = DoubleCheckUseNPC( pObject, searchCenter, forward ); |
|
} |
|
|
|
g_PortalGameStats.Event_PlayerUsed( searchCenter, forward, pObject ); |
|
#endif |
|
|
|
return pObject; |
|
} |
|
} |
|
|
|
#ifndef CLIENT_DLL |
|
CBaseEntity *pFoundByTrace = pObject; |
|
#endif |
|
|
|
// check ground entity first |
|
// if you've got a useable ground entity, then shrink the cone of this search to 45 degrees |
|
// otherwise, search out in a 90 degree cone (hemisphere) |
|
if ( GetGroundEntity() && IsUseableEntity(GetGroundEntity(), FCAP_USE_ONGROUND) ) |
|
{ |
|
pNearest = GetGroundEntity(); |
|
nearestDot = CONE_45_DEGREES; |
|
} |
|
|
|
for ( CEntitySphereQuery sphere( searchCenter, PLAYER_USE_RADIUS ); ( pObject = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() ) |
|
{ |
|
if ( !pObject ) |
|
continue; |
|
|
|
if ( !IsUseableEntity( pObject, FCAP_USE_IN_RADIUS ) ) |
|
continue; |
|
|
|
// see if it's more roughly in front of the player than previous guess |
|
Vector point; |
|
pObject->CollisionProp()->CalcNearestPoint( searchCenter, &point ); |
|
|
|
Vector dir = point - searchCenter; |
|
VectorNormalize(dir); |
|
float dot = DotProduct( dir, forward ); |
|
|
|
// Need to be looking at the object more or less |
|
if ( dot < 0.8 ) |
|
continue; |
|
|
|
if ( dot > nearestDot ) |
|
{ |
|
// Since this has purely been a radius search to this point, we now |
|
// make sure the object isn't behind glass or a grate. |
|
trace_t trCheckOccluded; |
|
UTIL_TraceLine( searchCenter, point, useableContents, this, COLLISION_GROUP_NONE, &trCheckOccluded ); |
|
|
|
if ( trCheckOccluded.fraction == 1.0 || trCheckOccluded.m_pEnt == pObject ) |
|
{ |
|
pNearest = pObject; |
|
nearestDot = dot; |
|
} |
|
} |
|
} |
|
|
|
#ifndef CLIENT_DLL |
|
if ( !pNearest ) |
|
{ |
|
// Haven't found anything near the player to use, nor any NPC's at distance. |
|
// Check to see if the player is trying to select an NPC through a rail, fence, or other 'see-though' volume. |
|
trace_t trAllies; |
|
UTIL_TraceLine( searchCenter, searchCenter + forward * PLAYER_USE_RADIUS, MASK_OPAQUE_AND_NPCS, this, COLLISION_GROUP_NONE, &trAllies ); |
|
|
|
if ( trAllies.m_pEnt && IsUseableEntity( trAllies.m_pEnt, 0 ) && trAllies.m_pEnt->MyNPCPointer() && trAllies.m_pEnt->MyNPCPointer()->IsPlayerAlly( this ) ) |
|
{ |
|
// This is an NPC, take it! |
|
pNearest = trAllies.m_pEnt; |
|
} |
|
} |
|
|
|
if ( pNearest && pNearest->MyNPCPointer() && pNearest->MyNPCPointer()->IsPlayerAlly( this ) ) |
|
{ |
|
pNearest = DoubleCheckUseNPC( pNearest, searchCenter, forward ); |
|
} |
|
|
|
if ( sv_debug_player_use.GetBool() ) |
|
{ |
|
if ( !pNearest ) |
|
{ |
|
NDebugOverlay::Line( searchCenter, tr.endpos, 255, 0, 0, true, 30 ); |
|
NDebugOverlay::Cross3D( tr.endpos, 16, 255, 0, 0, true, 30 ); |
|
} |
|
else if ( pNearest == pFoundByTrace ) |
|
{ |
|
NDebugOverlay::Line( searchCenter, tr.endpos, 0, 255, 0, true, 30 ); |
|
NDebugOverlay::Cross3D( tr.endpos, 16, 0, 255, 0, true, 30 ); |
|
} |
|
else |
|
{ |
|
NDebugOverlay::Box( pNearest->WorldSpaceCenter(), Vector(-8, -8, -8), Vector(8, 8, 8), 0, 255, 0, true, 30 ); |
|
} |
|
} |
|
|
|
g_PortalGameStats.Event_PlayerUsed( searchCenter, forward, pNearest ); |
|
#endif |
|
|
|
return pNearest; |
|
} |
|
|
|
CBaseEntity* CPortal_Player::FindUseEntityThroughPortal( void ) |
|
{ |
|
Vector forward, up; |
|
EyeVectors( &forward, NULL, &up ); |
|
|
|
CProp_Portal *pPortal = GetHeldObjectPortal(); |
|
|
|
trace_t tr; |
|
// Search for objects in a sphere (tests for entities that are not solid, yet still useable) |
|
Vector searchCenter = EyePosition(); |
|
|
|
Vector vTransformedForward, vTransformedUp, vTransformedSearchCenter; |
|
|
|
VMatrix matThisToLinked = pPortal->MatrixThisToLinked(); |
|
UTIL_Portal_PointTransform( matThisToLinked, searchCenter, vTransformedSearchCenter ); |
|
UTIL_Portal_VectorTransform( matThisToLinked, forward, vTransformedForward ); |
|
UTIL_Portal_VectorTransform( matThisToLinked, up, vTransformedUp ); |
|
|
|
|
|
// NOTE: Some debris objects are useable too, so hit those as well |
|
// A button, etc. can be made out of clip brushes, make sure it's +useable via a traceline, too. |
|
int useableContents = MASK_SOLID | CONTENTS_DEBRIS | CONTENTS_PLAYERCLIP; |
|
|
|
//UTIL_TraceLine( vTransformedSearchCenter, vTransformedSearchCenter + vTransformedForward * 1024, useableContents, this, COLLISION_GROUP_NONE, &tr ); |
|
Ray_t rayLinked; |
|
rayLinked.Init( searchCenter, searchCenter + forward * 1024 ); |
|
UTIL_PortalLinked_TraceRay( pPortal, rayLinked, useableContents, this, COLLISION_GROUP_NONE, &tr ); |
|
|
|
// try the hit entity if there is one, or the ground entity if there isn't. |
|
CBaseEntity *pNearest = NULL; |
|
CBaseEntity *pObject = tr.m_pEnt; |
|
int count = 0; |
|
// UNDONE: Might be faster to just fold this range into the sphere query |
|
const int NUM_TANGENTS = 7; |
|
while ( !IsUseableEntity(pObject, 0) && count < NUM_TANGENTS) |
|
{ |
|
// trace a box at successive angles down |
|
// 45 deg, 30 deg, 20 deg, 15 deg, 10 deg, -10, -15 |
|
const float tangents[NUM_TANGENTS] = { 1, 0.57735026919f, 0.3639702342f, 0.267949192431f, 0.1763269807f, -0.1763269807f, -0.267949192431f }; |
|
Vector down = vTransformedForward - tangents[count]*vTransformedUp; |
|
VectorNormalize(down); |
|
UTIL_TraceHull( vTransformedSearchCenter, vTransformedSearchCenter + down * 72, -Vector(16,16,16), Vector(16,16,16), useableContents, this, COLLISION_GROUP_NONE, &tr ); |
|
pObject = tr.m_pEnt; |
|
count++; |
|
} |
|
float nearestDot = CONE_90_DEGREES; |
|
if ( IsUseableEntity(pObject, 0) ) |
|
{ |
|
Vector delta = tr.endpos - tr.startpos; |
|
float centerZ = CollisionProp()->WorldSpaceCenter().z; |
|
delta.z = IntervalDistance( tr.endpos.z, centerZ + CollisionProp()->OBBMins().z, centerZ + CollisionProp()->OBBMaxs().z ); |
|
float dist = delta.Length(); |
|
if ( dist < PLAYER_USE_RADIUS ) |
|
{ |
|
#ifndef CLIENT_DLL |
|
|
|
if ( pObject->MyNPCPointer() && pObject->MyNPCPointer()->IsPlayerAlly( this ) ) |
|
{ |
|
// If about to select an NPC, do a more thorough check to ensure |
|
// that we're selecting the right one from a group. |
|
pObject = DoubleCheckUseNPC( pObject, vTransformedSearchCenter, vTransformedForward ); |
|
} |
|
#endif |
|
|
|
return pObject; |
|
} |
|
} |
|
|
|
// check ground entity first |
|
// if you've got a useable ground entity, then shrink the cone of this search to 45 degrees |
|
// otherwise, search out in a 90 degree cone (hemisphere) |
|
if ( GetGroundEntity() && IsUseableEntity(GetGroundEntity(), FCAP_USE_ONGROUND) ) |
|
{ |
|
pNearest = GetGroundEntity(); |
|
nearestDot = CONE_45_DEGREES; |
|
} |
|
|
|
for ( CEntitySphereQuery sphere( vTransformedSearchCenter, PLAYER_USE_RADIUS ); ( pObject = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() ) |
|
{ |
|
if ( !pObject ) |
|
continue; |
|
|
|
if ( !IsUseableEntity( pObject, FCAP_USE_IN_RADIUS ) ) |
|
continue; |
|
|
|
// see if it's more roughly in front of the player than previous guess |
|
Vector point; |
|
pObject->CollisionProp()->CalcNearestPoint( vTransformedSearchCenter, &point ); |
|
|
|
Vector dir = point - vTransformedSearchCenter; |
|
VectorNormalize(dir); |
|
float dot = DotProduct( dir, vTransformedForward ); |
|
|
|
// Need to be looking at the object more or less |
|
if ( dot < 0.8 ) |
|
continue; |
|
|
|
if ( dot > nearestDot ) |
|
{ |
|
// Since this has purely been a radius search to this point, we now |
|
// make sure the object isn't behind glass or a grate. |
|
trace_t trCheckOccluded; |
|
UTIL_TraceLine( vTransformedSearchCenter, point, useableContents, this, COLLISION_GROUP_NONE, &trCheckOccluded ); |
|
|
|
if ( trCheckOccluded.fraction == 1.0 || trCheckOccluded.m_pEnt == pObject ) |
|
{ |
|
pNearest = pObject; |
|
nearestDot = dot; |
|
} |
|
} |
|
} |
|
|
|
#ifndef CLIENT_DLL |
|
if ( !pNearest ) |
|
{ |
|
// Haven't found anything near the player to use, nor any NPC's at distance. |
|
// Check to see if the player is trying to select an NPC through a rail, fence, or other 'see-though' volume. |
|
trace_t trAllies; |
|
UTIL_TraceLine( vTransformedSearchCenter, vTransformedSearchCenter + vTransformedForward * PLAYER_USE_RADIUS, MASK_OPAQUE_AND_NPCS, this, COLLISION_GROUP_NONE, &trAllies ); |
|
|
|
if ( trAllies.m_pEnt && IsUseableEntity( trAllies.m_pEnt, 0 ) && trAllies.m_pEnt->MyNPCPointer() && trAllies.m_pEnt->MyNPCPointer()->IsPlayerAlly( this ) ) |
|
{ |
|
// This is an NPC, take it! |
|
pNearest = trAllies.m_pEnt; |
|
} |
|
} |
|
|
|
if ( pNearest && pNearest->MyNPCPointer() && pNearest->MyNPCPointer()->IsPlayerAlly( this ) ) |
|
{ |
|
pNearest = DoubleCheckUseNPC( pNearest, vTransformedSearchCenter, vTransformedForward ); |
|
} |
|
|
|
#endif |
|
|
|
return pNearest; |
|
} |
|
|
|
|
|
#if 0 |
|
|
|
//========================== |
|
// 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( CPortal_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(); |
|
|
|
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 |
|
//----------------------------------------------------------------------------- |
|
CPortal_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; |
|
|
|
// 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; |
|
|
|
// 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; |
|
|
|
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::Teleport( Vector *pOldOrigin, QAngle *pOldAngles ) |
|
{ |
|
QAngle absangles = GetOuter()->GetAbsAngles(); |
|
absangles.x = 0.0f; |
|
m_angRender = absangles; |
|
|
|
m_flCurrentFeetYaw = m_flGoalFeetYaw = m_flLastYaw = m_angRender.y; |
|
m_flLastTurnTime = 0.0f; |
|
m_nTurningInPlace = TURN_NONE; |
|
} |
|
|
|
void CPlayerAnimState::GetOuterAbsVelocity( Vector& vel ) |
|
{ |
|
#if defined( CLIENT_DLL ) |
|
GetOuter()->EstimateAbsVelocity( vel ); |
|
#else |
|
vel = GetOuter()->GetAbsVelocity(); |
|
#endif |
|
} |
|
#endif // #if 0
|