//========= 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, ¤tdir, &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" ); }