source-engine/game/client/hl2/c_basehlplayer.cpp

650 lines
19 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "c_basehlplayer.h"
#include "playerandobjectenumerator.h"
#include "engine/ivdebugoverlay.h"
#include "c_ai_basenpc.h"
#include "in_buttons.h"
#include "collisionutils.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// How fast to avoid collisions with center of other object, in units per second
#define AVOID_SPEED 2000.0f
extern ConVar cl_forwardspeed;
extern ConVar cl_backspeed;
extern ConVar cl_sidespeed;
extern ConVar zoom_sensitivity_ratio;
extern ConVar default_fov;
extern ConVar sensitivity;
ConVar cl_npc_speedmod_intime( "cl_npc_speedmod_intime", "0.25", FCVAR_CLIENTDLL | FCVAR_ARCHIVE );
ConVar cl_npc_speedmod_outtime( "cl_npc_speedmod_outtime", "1.5", FCVAR_CLIENTDLL | FCVAR_ARCHIVE );
IMPLEMENT_CLIENTCLASS_DT(C_BaseHLPlayer, DT_HL2_Player, CHL2_Player)
RecvPropDataTable( RECVINFO_DT(m_HL2Local),0, &REFERENCE_RECV_TABLE(DT_HL2Local) ),
RecvPropBool( RECVINFO( m_fIsSprinting ) ),
END_RECV_TABLE()
BEGIN_PREDICTION_DATA( C_BaseHLPlayer )
DEFINE_PRED_TYPEDESCRIPTION( m_HL2Local, C_HL2PlayerLocalData ),
DEFINE_PRED_FIELD( m_fIsSprinting, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
END_PREDICTION_DATA()
//-----------------------------------------------------------------------------
// Purpose: Drops player's primary weapon
//-----------------------------------------------------------------------------
void CC_DropPrimary( void )
{
C_BasePlayer *pPlayer = (C_BasePlayer *) C_BasePlayer::GetLocalPlayer();
if ( pPlayer == NULL )
return;
pPlayer->Weapon_DropPrimary();
}
static ConCommand dropprimary("dropprimary", CC_DropPrimary, "dropprimary: Drops the primary weapon of the player.");
//-----------------------------------------------------------------------------
// Constructor
//-----------------------------------------------------------------------------
C_BaseHLPlayer::C_BaseHLPlayer()
{
AddVar( &m_Local.m_vecPunchAngle, &m_Local.m_iv_vecPunchAngle, LATCH_SIMULATION_VAR );
AddVar( &m_Local.m_vecPunchAngleVel, &m_Local.m_iv_vecPunchAngleVel, LATCH_SIMULATION_VAR );
m_flZoomStart = 0.0f;
m_flZoomEnd = 0.0f;
m_flZoomRate = 0.0f;
m_flZoomStartTime = 0.0f;
m_flSpeedMod = cl_forwardspeed.GetFloat();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : updateType -
//-----------------------------------------------------------------------------
void C_BaseHLPlayer::OnDataChanged( DataUpdateType_t updateType )
{
// Make sure we're thinking
if ( updateType == DATA_UPDATE_CREATED )
{
SetNextClientThink( CLIENT_THINK_ALWAYS );
}
BaseClass::OnDataChanged( updateType );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_BaseHLPlayer::Weapon_DropPrimary( void )
{
engine->ServerCmd( "DropPrimary" );
}
float C_BaseHLPlayer::GetFOV()
{
//Find our FOV with offset zoom value
float flFOVOffset = BaseClass::GetFOV() + GetZoom();
// Clamp FOV in MP
int min_fov = ( gpGlobals->maxClients == 1 ) ? 5 : default_fov.GetInt();
// Don't let it go too low
flFOVOffset = MAX( min_fov, flFOVOffset );
return flFOVOffset;
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : float
//-----------------------------------------------------------------------------
float C_BaseHLPlayer::GetZoom( void )
{
float fFOV = m_flZoomEnd;
//See if we need to lerp the values
if ( ( m_flZoomStart != m_flZoomEnd ) && ( m_flZoomRate > 0.0f ) )
{
float deltaTime = (float)( gpGlobals->curtime - m_flZoomStartTime ) / m_flZoomRate;
if ( deltaTime >= 1.0f )
{
//If we're past the zoom time, just take the new value and stop lerping
fFOV = m_flZoomStart = m_flZoomEnd;
}
else
{
fFOV = SimpleSplineRemapVal( deltaTime, 0.0f, 1.0f, m_flZoomStart, m_flZoomEnd );
}
}
return fFOV;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : FOVOffset -
// time -
//-----------------------------------------------------------------------------
void C_BaseHLPlayer::Zoom( float FOVOffset, float time )
{
m_flZoomStart = GetZoom();
m_flZoomEnd = FOVOffset;
m_flZoomRate = time;
m_flZoomStartTime = gpGlobals->curtime;
}
//-----------------------------------------------------------------------------
// Purpose: Hack to zero out player's pitch, use value from poseparameter instead
// Input : flags -
// Output : int
//-----------------------------------------------------------------------------
int C_BaseHLPlayer::DrawModel( int flags )
{
// Not pitch for player
QAngle saveAngles = GetLocalAngles();
QAngle useAngles = saveAngles;
useAngles[ PITCH ] = 0.0f;
SetLocalAngles( useAngles );
int iret = BaseClass::DrawModel( flags );
SetLocalAngles( saveAngles );
return iret;
}
//-----------------------------------------------------------------------------
// Purpose: Helper to remove from ladder
//-----------------------------------------------------------------------------
void C_BaseHLPlayer::ExitLadder()
{
if ( MOVETYPE_LADDER != GetMoveType() )
return;
SetMoveType( MOVETYPE_WALK );
SetMoveCollide( MOVECOLLIDE_DEFAULT );
// Remove from ladder
m_HL2Local.m_hLadder = NULL;
}
//-----------------------------------------------------------------------------
// Purpose: Determines if a player can be safely moved towards a point
// Input: pos - position to test move to, fVertDist - how far to trace downwards to see if the player would fall,
// radius - how close the player can be to the object, objPos - position of the object to avoid,
// objDir - direction the object is travelling
//-----------------------------------------------------------------------------
bool C_BaseHLPlayer::TestMove( const Vector &pos, float fVertDist, float radius, const Vector &objPos, const Vector &objDir )
{
trace_t trUp;
trace_t trOver;
trace_t trDown;
float flHit1, flHit2;
UTIL_TraceHull( GetAbsOrigin(), pos, GetPlayerMins(), GetPlayerMaxs(), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &trOver );
if ( trOver.fraction < 1.0f )
{
// check if the endpos intersects with the direction the object is travelling. if it doesn't, this is a good direction to move.
if ( objDir.IsZero() ||
( IntersectInfiniteRayWithSphere( objPos, objDir, trOver.endpos, radius, &flHit1, &flHit2 ) &&
( ( flHit1 >= 0.0f ) || ( flHit2 >= 0.0f ) ) )
)
{
// our first trace failed, so see if we can go farther if we step up.
// trace up to see if we have enough room.
UTIL_TraceHull( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, m_Local.m_flStepSize ),
GetPlayerMins(), GetPlayerMaxs(), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &trUp );
// do a trace from the stepped up height
UTIL_TraceHull( trUp.endpos, pos + Vector( 0, 0, trUp.endpos.z - trUp.startpos.z ),
GetPlayerMins(), GetPlayerMaxs(), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &trOver );
if ( trOver.fraction < 1.0f )
{
// check if the endpos intersects with the direction the object is travelling. if it doesn't, this is a good direction to move.
if ( objDir.IsZero() ||
( IntersectInfiniteRayWithSphere( objPos, objDir, trOver.endpos, radius, &flHit1, &flHit2 ) && ( ( flHit1 >= 0.0f ) || ( flHit2 >= 0.0f ) ) ) )
{
return false;
}
}
}
}
// trace down to see if this position is on the ground
UTIL_TraceLine( trOver.endpos, trOver.endpos - Vector( 0, 0, fVertDist ),
MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &trDown );
if ( trDown.fraction == 1.0f )
return false;
return true;
}
//-----------------------------------------------------------------------------
// Client-side obstacle avoidance
//-----------------------------------------------------------------------------
void C_BaseHLPlayer::PerformClientSideObstacleAvoidance( float flFrameTime, CUserCmd *pCmd )
{
// Don't avoid if noclipping or in movetype none
switch ( GetMoveType() )
{
case MOVETYPE_NOCLIP:
case MOVETYPE_NONE:
case MOVETYPE_OBSERVER:
return;
default:
break;
}
// Try to steer away from any objects/players we might interpenetrate
Vector size = WorldAlignSize();
float radius = 0.7f * sqrt( size.x * size.x + size.y * size.y );
float curspeed = GetLocalVelocity().Length2D();
//int slot = 1;
//engine->Con_NPrintf( slot++, "speed %f\n", curspeed );
//engine->Con_NPrintf( slot++, "radius %f\n", radius );
// If running, use a larger radius
float factor = 1.0f;
if ( curspeed > 150.0f )
{
curspeed = MIN( 2048.0f, curspeed );
factor = ( 1.0f + ( curspeed - 150.0f ) / 150.0f );
//engine->Con_NPrintf( slot++, "scaleup (%f) to radius %f\n", factor, radius * factor );
radius = radius * factor;
}
Vector currentdir;
Vector rightdir;
QAngle vAngles = pCmd->viewangles;
vAngles.x = 0;
AngleVectors( vAngles, &currentdir, &rightdir, NULL );
bool istryingtomove = false;
bool ismovingforward = false;
if ( fabs( pCmd->forwardmove ) > 0.0f ||
fabs( pCmd->sidemove ) > 0.0f )
{
istryingtomove = true;
if ( pCmd->forwardmove > 1.0f )
{
ismovingforward = true;
}
}
if ( istryingtomove == true )
radius *= 1.3f;
CPlayerAndObjectEnumerator avoid( radius );
partition->EnumerateElementsInSphere( PARTITION_CLIENT_SOLID_EDICTS, GetAbsOrigin(), radius, false, &avoid );
// Okay, decide how to avoid if there's anything close by
int c = avoid.GetObjectCount();
if ( c <= 0 )
return;
//engine->Con_NPrintf( slot++, "moving %s forward %s\n", istryingtomove ? "true" : "false", ismovingforward ? "true" : "false" );
float adjustforwardmove = 0.0f;
float adjustsidemove = 0.0f;
for ( int i = 0; i < c; i++ )
{
C_AI_BaseNPC *obj = dynamic_cast< C_AI_BaseNPC *>(avoid.GetObject( i ));
if( !obj )
continue;
Vector vecToObject = obj->GetAbsOrigin() - GetAbsOrigin();
float flDist = vecToObject.Length2D();
// Figure out a 2D radius for the object
Vector vecWorldMins, vecWorldMaxs;
obj->CollisionProp()->WorldSpaceAABB( &vecWorldMins, &vecWorldMaxs );
Vector objSize = vecWorldMaxs - vecWorldMins;
float objectradius = 0.5f * sqrt( objSize.x * objSize.x + objSize.y * objSize.y );
//Don't run this code if the NPC is not moving UNLESS we are in stuck inside of them.
if ( !obj->IsMoving() && flDist > objectradius )
continue;
if ( flDist > objectradius && obj->IsEffectActive( EF_NODRAW ) )
{
obj->RemoveEffects( EF_NODRAW );
}
Vector vecNPCVelocity;
obj->EstimateAbsVelocity( vecNPCVelocity );
float flNPCSpeed = VectorNormalize( vecNPCVelocity );
Vector vPlayerVel = GetAbsVelocity();
VectorNormalize( vPlayerVel );
float flHit1, flHit2;
Vector vRayDir = vecToObject;
VectorNormalize( vRayDir );
float flVelProduct = DotProduct( vecNPCVelocity, vPlayerVel );
float flDirProduct = DotProduct( vRayDir, vPlayerVel );
if ( !IntersectInfiniteRayWithSphere(
GetAbsOrigin(),
vRayDir,
obj->GetAbsOrigin(),
radius,
&flHit1,
&flHit2 ) )
continue;
Vector dirToObject = -vecToObject;
VectorNormalize( dirToObject );
float fwd = 0;
float rt = 0;
float sidescale = 2.0f;
float forwardscale = 1.0f;
bool foundResult = false;
Vector vMoveDir = vecNPCVelocity;
if ( flNPCSpeed > 0.001f )
{
// This NPC is moving. First try deflecting the player left or right relative to the NPC's velocity.
// Start with whatever side they're on relative to the NPC's velocity.
Vector vecNPCTrajectoryRight = CrossProduct( vecNPCVelocity, Vector( 0, 0, 1) );
int iDirection = ( vecNPCTrajectoryRight.Dot( dirToObject ) > 0 ) ? 1 : -1;
for ( int nTries = 0; nTries < 2; nTries++ )
{
Vector vecTryMove = vecNPCTrajectoryRight * iDirection;
VectorNormalize( vecTryMove );
Vector vTestPosition = GetAbsOrigin() + vecTryMove * radius * 2;
if ( TestMove( vTestPosition, size.z * 2, radius * 2, obj->GetAbsOrigin(), vMoveDir ) )
{
fwd = currentdir.Dot( vecTryMove );
rt = rightdir.Dot( vecTryMove );
//Msg( "PUSH DEFLECT fwd=%f, rt=%f\n", fwd, rt );
foundResult = true;
break;
}
else
{
// Try the other direction.
iDirection *= -1;
}
}
}
else
{
// the object isn't moving, so try moving opposite the way it's facing
Vector vecNPCForward;
obj->GetVectors( &vecNPCForward, NULL, NULL );
Vector vTestPosition = GetAbsOrigin() - vecNPCForward * radius * 2;
if ( TestMove( vTestPosition, size.z * 2, radius * 2, obj->GetAbsOrigin(), vMoveDir ) )
{
fwd = currentdir.Dot( -vecNPCForward );
rt = rightdir.Dot( -vecNPCForward );
if ( flDist < objectradius )
{
obj->AddEffects( EF_NODRAW );
}
//Msg( "PUSH AWAY FACE fwd=%f, rt=%f\n", fwd, rt );
foundResult = true;
}
}
if ( !foundResult )
{
// test if we can move in the direction the object is moving
Vector vTestPosition = GetAbsOrigin() + vMoveDir * radius * 2;
if ( TestMove( vTestPosition, size.z * 2, radius * 2, obj->GetAbsOrigin(), vMoveDir ) )
{
fwd = currentdir.Dot( vMoveDir );
rt = rightdir.Dot( vMoveDir );
if ( flDist < objectradius )
{
obj->AddEffects( EF_NODRAW );
}
//Msg( "PUSH ALONG fwd=%f, rt=%f\n", fwd, rt );
foundResult = true;
}
else
{
// try moving directly away from the object
Vector vTestPosition = GetAbsOrigin() - dirToObject * radius * 2;
if ( TestMove( vTestPosition, size.z * 2, radius * 2, obj->GetAbsOrigin(), vMoveDir ) )
{
fwd = currentdir.Dot( -dirToObject );
rt = rightdir.Dot( -dirToObject );
foundResult = true;
//Msg( "PUSH AWAY fwd=%f, rt=%f\n", fwd, rt );
}
}
}
if ( !foundResult )
{
// test if we can move through the object
Vector vTestPosition = GetAbsOrigin() - vMoveDir * radius * 2;
fwd = currentdir.Dot( -vMoveDir );
rt = rightdir.Dot( -vMoveDir );
if ( flDist < objectradius )
{
obj->AddEffects( EF_NODRAW );
}
//Msg( "PUSH THROUGH fwd=%f, rt=%f\n", fwd, rt );
foundResult = true;
}
// If running, then do a lot more sideways veer since we're not going to do anything to
// forward velocity
if ( istryingtomove )
{
sidescale = 6.0f;
}
if ( flVelProduct > 0.0f && flDirProduct > 0.0f )
{
sidescale = 0.1f;
}
float force = 1.0f;
float forward = forwardscale * fwd * force * AVOID_SPEED;
float side = sidescale * rt * force * AVOID_SPEED;
adjustforwardmove += forward;
adjustsidemove += side;
}
pCmd->forwardmove += adjustforwardmove;
pCmd->sidemove += adjustsidemove;
// Clamp the move to within legal limits, preserving direction. This is a little
// complicated because we have different limits for forward, back, and side
//Msg( "PRECLAMP: forwardmove=%f, sidemove=%f\n", pCmd->forwardmove, pCmd->sidemove );
float flForwardScale = 1.0f;
if ( pCmd->forwardmove > fabs( cl_forwardspeed.GetFloat() ) )
{
flForwardScale = fabs( cl_forwardspeed.GetFloat() ) / pCmd->forwardmove;
}
else if ( pCmd->forwardmove < -fabs( cl_backspeed.GetFloat() ) )
{
flForwardScale = fabs( cl_backspeed.GetFloat() ) / fabs( pCmd->forwardmove );
}
float flSideScale = 1.0f;
if ( fabs( pCmd->sidemove ) > fabs( cl_sidespeed.GetFloat() ) )
{
flSideScale = fabs( cl_sidespeed.GetFloat() ) / fabs( pCmd->sidemove );
}
float flScale = MIN( flForwardScale, flSideScale );
pCmd->forwardmove *= flScale;
pCmd->sidemove *= flScale;
//Msg( "POSTCLAMP: forwardmove=%f, sidemove=%f\n", pCmd->forwardmove, pCmd->sidemove );
}
void C_BaseHLPlayer::PerformClientSideNPCSpeedModifiers( float flFrameTime, CUserCmd *pCmd )
{
if ( m_hClosestNPC == NULL )
{
if ( m_flSpeedMod != cl_forwardspeed.GetFloat() )
{
float flDeltaTime = (m_flSpeedModTime - gpGlobals->curtime);
m_flSpeedMod = RemapValClamped( flDeltaTime, cl_npc_speedmod_outtime.GetFloat(), 0, m_flExitSpeedMod, cl_forwardspeed.GetFloat() );
}
}
else
{
C_AI_BaseNPC *pNPC = dynamic_cast< C_AI_BaseNPC *>( m_hClosestNPC.Get() );
if ( pNPC )
{
float flDist = (GetAbsOrigin() - pNPC->GetAbsOrigin()).LengthSqr();
bool bShouldModSpeed = false;
// Within range?
if ( flDist < pNPC->GetSpeedModifyRadius() )
{
// Now, only slowdown if we're facing & running parallel to the target's movement
// Facing check first (in 2D)
Vector vecTargetOrigin = pNPC->GetAbsOrigin();
Vector los = ( vecTargetOrigin - EyePosition() );
los.z = 0;
VectorNormalize( los );
Vector facingDir;
AngleVectors( GetAbsAngles(), &facingDir );
float flDot = DotProduct( los, facingDir );
if ( flDot > 0.8 )
{
/*
// Velocity check (abort if the target isn't moving)
Vector vecTargetVelocity;
pNPC->EstimateAbsVelocity( vecTargetVelocity );
float flSpeed = VectorNormalize(vecTargetVelocity);
Vector vecMyVelocity = GetAbsVelocity();
VectorNormalize(vecMyVelocity);
if ( flSpeed > 1.0 )
{
// Velocity roughly parallel?
if ( DotProduct(vecTargetVelocity,vecMyVelocity) > 0.4 )
{
bShouldModSpeed = true;
}
}
else
{
// NPC's not moving, slow down if we're moving at him
//Msg("Dot: %.2f\n", DotProduct( los, vecMyVelocity ) );
if ( DotProduct( los, vecMyVelocity ) > 0.8 )
{
bShouldModSpeed = true;
}
}
*/
bShouldModSpeed = true;
}
}
if ( !bShouldModSpeed )
{
m_hClosestNPC = NULL;
m_flSpeedModTime = gpGlobals->curtime + cl_npc_speedmod_outtime.GetFloat();
m_flExitSpeedMod = m_flSpeedMod;
return;
}
else
{
if ( m_flSpeedMod != pNPC->GetSpeedModifySpeed() )
{
float flDeltaTime = (m_flSpeedModTime - gpGlobals->curtime);
m_flSpeedMod = RemapValClamped( flDeltaTime, cl_npc_speedmod_intime.GetFloat(), 0, cl_forwardspeed.GetFloat(), pNPC->GetSpeedModifySpeed() );
}
}
}
}
if ( pCmd->forwardmove > 0.0f )
{
pCmd->forwardmove = clamp( pCmd->forwardmove, -m_flSpeedMod, m_flSpeedMod );
}
else
{
pCmd->forwardmove = clamp( pCmd->forwardmove, -m_flSpeedMod, m_flSpeedMod );
}
pCmd->sidemove = clamp( pCmd->sidemove, -m_flSpeedMod, m_flSpeedMod );
//Msg( "fwd %f right %f\n", pCmd->forwardmove, pCmd->sidemove );
}
//-----------------------------------------------------------------------------
// Purpose: Input handling
//-----------------------------------------------------------------------------
bool C_BaseHLPlayer::CreateMove( float flInputSampleTime, CUserCmd *pCmd )
{
bool bResult = BaseClass::CreateMove( flInputSampleTime, pCmd );
if ( !IsInAVehicle() )
{
PerformClientSideObstacleAvoidance( TICK_INTERVAL, pCmd );
PerformClientSideNPCSpeedModifiers( TICK_INTERVAL, pCmd );
}
return bResult;
}
//-----------------------------------------------------------------------------
// Purpose: Input handling
//-----------------------------------------------------------------------------
void C_BaseHLPlayer::BuildTransformations( CStudioHdr *hdr, Vector *pos, Quaternion q[], const matrix3x4_t& cameraTransform, int boneMask, CBoneBitList &boneComputed )
{
BaseClass::BuildTransformations( hdr, pos, q, cameraTransform, boneMask, boneComputed );
BuildFirstPersonMeathookTransformations( hdr, pos, q, cameraTransform, boneMask, boneComputed, "ValveBiped.Bip01_Head1" );
}