//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Player for HL2.
//
//=============================================================================//
#include "cbase.h"
#include "hl2_player.h"
#include "globalstate.h"
#include "game.h"
#include "gamerules.h"
#include "trains.h"
#include "basehlcombatweapon_shared.h"
#include "vcollide_parse.h"
#include "in_buttons.h"
#include "ai_interactions.h"
#include "ai_squad.h"
#include "igamemovement.h"
#include "ai_hull.h"
#include "hl2_shareddefs.h"
#include "info_camera_link.h"
#include "point_camera.h"
#include "engine/IEngineSound.h"
#include "ndebugoverlay.h"
#include "iservervehicle.h"
#include "IVehicle.h"
#include "globals.h"
#include "collisionutils.h"
#include "coordsize.h"
#include "effect_color_tables.h"
#include "vphysics/player_controller.h"
#include "player_pickup.h"
#include "weapon_physcannon.h"
#include "script_intro.h"
#include "effect_dispatch_data.h"
#include "te_effect_dispatch.h"
#include "ai_basenpc.h"
#include "AI_Criteria.h"
#include "npc_barnacle.h"
#include "entitylist.h"
#include "env_zoom.h"
#include "hl2_gamerules.h"
#include "prop_combine_ball.h"
#include "datacache/imdlcache.h"
#include "eventqueue.h"
#ifdef HL2_EPISODIC
#include "npc_alyx_episodic.h"
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern ConVar weapon_showproficiency;
extern ConVar autoaim_max_dist;
// Do not touch with without seeing me, please! (sjb)
// For consistency's sake, enemy gunfire is traced against a scaled down
// version of the player's hull, not the hitboxes for the player's model
// because the player isn't aware of his model, and can't do anything about
// preventing headshots and other such things. Also, game difficulty will
// not change if the model changes. This is the value by which to scale
// the X/Y of the player's hull to get the volume to trace bullets against.
#define PLAYER_HULL_REDUCTION 0.70
// This switches between the single primary weapon, and multiple weapons with buckets approach (jdw)
#define HL2_SINGLE_PRIMARY_WEAPON_MODE 0
#define TIME_IGNORE_FALL_DAMAGE 10.0
extern int gEvilImpulse101;
ConVar sv_autojump( "sv_autojump", "0" );
ConVar hl2_walkspeed( "hl2_walkspeed", "150" );
ConVar hl2_normspeed( "hl2_normspeed", "190" );
ConVar hl2_sprintspeed( "hl2_sprintspeed", "320" );
ConVar hl2_xbox_aiming( "hl2_xbox_aiming", "1" );
ConVar hl2_darkness_flashlight_factor ( "hl2_darkness_flashlight_factor", "1" );
#ifdef HL2MP
#define HL2_WALK_SPEED 150
#define HL2_NORM_SPEED 190
#define HL2_SPRINT_SPEED 320
#else
#define HL2_WALK_SPEED hl2_walkspeed.GetFloat()
#define HL2_NORM_SPEED hl2_normspeed.GetFloat()
#define HL2_SPRINT_SPEED hl2_sprintspeed.GetFloat()
#endif
#ifdef _XBOX
#define AUTOSPRINT_MIN_DURATION 1.0f
#endif//_XBOX
ConVar player_showpredictedposition( "player_showpredictedposition", "0" );
ConVar player_showpredictedposition_timestep( "player_showpredictedposition_timestep", "1.0" );
ConVar player_squad_transient_commands( "player_squad_transient_commands", "1", FCVAR_REPLICATED );
ConVar player_squad_double_tap_time( "player_squad_double_tap_time", "0.25" );
ConVar sv_infinite_aux_power( "sv_infinite_aux_power", "0", FCVAR_CHEAT );
ConVar autoaim_unlock_target( "autoaim_unlock_target", "0.8666" );
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void CC_ToggleZoom( void )
{
CBasePlayer* pPlayer = UTIL_GetCommandClient();
if( pPlayer )
{
CHL2_Player *pHL2Player = dynamic_cast<CHL2_Player*>(pPlayer);
if( pHL2Player && pHL2Player->IsSuitEquipped() )
{
pHL2Player->ToggleZoom();
}
}
}
static ConCommand toggle_zoom("toggle_zoom", CC_ToggleZoom, "Toggles zoom display" );
// ConVar cl_forwardspeed( "cl_forwardspeed", "400", FCVAR_CHEAT ); // Links us to the client's version
ConVar xc_crouch_range( "xc_crouch_range", "0.85", FCVAR_ARCHIVE, "Percentarge [1..0] of joystick range to allow ducking within" ); // Only 1/2 of the range is used
ConVar xc_use_crouch_limiter( "xc_use_crouch_limiter", "0", FCVAR_ARCHIVE, "Use the crouch limiting logic on the controller" );
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void CC_ToggleDuck( void )
{
CBasePlayer* pPlayer = UTIL_GetCommandClient();
if ( pPlayer == NULL )
return;
// Cannot be frozen
if ( pPlayer->GetFlags() & FL_FROZEN )
return;
static bool bChecked = false;
static ConVar *pCVcl_forwardspeed = NULL;
if ( !bChecked )
{
bChecked = true;
pCVcl_forwardspeed = ( ConVar * )cvar->FindVar( "cl_forwardspeed" );
}
// If we're not ducked, do extra checking
if ( xc_use_crouch_limiter.GetBool() )
{
if ( pPlayer->GetToggledDuckState() == false )
{
float flForwardSpeed = 400.0f;
if ( pCVcl_forwardspeed )
{
flForwardSpeed = pCVcl_forwardspeed->GetFloat();
}
flForwardSpeed = max( 1.0f, flForwardSpeed );
// Make sure we're not in the blindspot on the crouch detection
float flStickDistPerc = ( pPlayer->GetStickDist() / flForwardSpeed ); // Speed is the magnitude
if ( flStickDistPerc > xc_crouch_range.GetFloat() )
return;
}
}
// Toggle the duck
pPlayer->ToggleDuck();
}
static ConCommand toggle_duck("toggle_duck", CC_ToggleDuck, "Toggles duck" );
#ifndef HL2MP
LINK_ENTITY_TO_CLASS( player, CHL2_Player );
#endif
PRECACHE_REGISTER(player);
CBaseEntity *FindEntityForward( CBasePlayer *pMe, bool fHull );
BEGIN_SIMPLE_DATADESC( LadderMove_t )
DEFINE_FIELD( m_bForceLadderMove, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bForceMount, FIELD_BOOLEAN ),
DEFINE_FIELD( m_flStartTime, FIELD_TIME ),
DEFINE_FIELD( m_flArrivalTime, FIELD_TIME ),
DEFINE_FIELD( m_vecGoalPosition, FIELD_POSITION_VECTOR ),
DEFINE_FIELD( m_vecStartPosition, FIELD_POSITION_VECTOR ),
DEFINE_FIELD( m_hForceLadder, FIELD_EHANDLE ),
DEFINE_FIELD( m_hReservedSpot, FIELD_EHANDLE ),
END_DATADESC()
// Global Savedata for HL2 player
BEGIN_DATADESC( CHL2_Player )
DEFINE_FIELD( m_nControlClass, FIELD_INTEGER ),
DEFINE_EMBEDDED( m_HL2Local ),
DEFINE_FIELD( m_bSprintEnabled, FIELD_BOOLEAN ),
DEFINE_FIELD( m_flTimeAllSuitDevicesOff, FIELD_TIME ),
DEFINE_FIELD( m_fIsSprinting, FIELD_BOOLEAN ),
DEFINE_FIELD( m_fIsWalking, FIELD_BOOLEAN ),
/*
// These are initialized every time the player calls Activate()
DEFINE_FIELD( m_bIsAutoSprinting, FIELD_BOOLEAN ),
DEFINE_FIELD( m_fAutoSprintMinTime, FIELD_TIME ),
*/
// Field is used within a single tick, no need to save restore
// DEFINE_FIELD( m_bPlayUseDenySound, FIELD_BOOLEAN ),
// m_pPlayerAISquad reacquired on load
DEFINE_AUTO_ARRAY( m_vecMissPositions, FIELD_POSITION_VECTOR ),
DEFINE_FIELD( m_nNumMissPositions, FIELD_INTEGER ),
// m_pPlayerAISquad
DEFINE_EMBEDDED( m_CommanderUpdateTimer ),
// m_RealTimeLastSquadCommand
DEFINE_FIELD( m_QueuedCommand, FIELD_INTEGER ),
DEFINE_FIELD( m_flTimeIgnoreFallDamage, FIELD_TIME ),
// Suit power fields
DEFINE_FIELD( m_flSuitPowerLoad, FIELD_FLOAT ),
DEFINE_FIELD( m_flIdleTime, FIELD_TIME ),
DEFINE_FIELD( m_flMoveTime, FIELD_TIME ),
DEFINE_FIELD( m_flLastDamageTime, FIELD_TIME ),
DEFINE_FIELD( m_flTargetFindTime, FIELD_TIME ),
DEFINE_FIELD( m_flAdmireGlovesAnimTime, FIELD_TIME ),
DEFINE_FIELD( m_flNextFlashlightCheckTime, FIELD_TIME ),
DEFINE_FIELD( m_flFlashlightPowerDrainScale, FIELD_FLOAT ),
DEFINE_FIELD( m_bFlashlightDisabled, FIELD_BOOLEAN ),
DEFINE_FIELD( m_hLockedAutoAimEntity, FIELD_EHANDLE ),
DEFINE_EMBEDDED( m_LowerWeaponTimer ),
DEFINE_EMBEDDED( m_AutoaimTimer ),
DEFINE_INPUTFUNC( FIELD_FLOAT, "IgnoreFallDamage", InputIgnoreFallDamage ),
DEFINE_INPUTFUNC( FIELD_VOID, "OnSquadMemberKilled", OnSquadMemberKilled ),
DEFINE_INPUTFUNC( FIELD_VOID, "DisableFlashlight", InputDisableFlashlight ),
DEFINE_INPUTFUNC( FIELD_VOID, "EnableFlashlight", InputEnableFlashlight ),
DEFINE_INPUTFUNC( FIELD_VOID, "ForceDropPhysObjects", InputForceDropPhysObjects ),
DEFINE_SOUNDPATCH( m_sndLeeches ),
DEFINE_SOUNDPATCH( m_sndWaterSplashes ),
DEFINE_FIELD( m_flArmorReductionTime, FIELD_TIME ),
DEFINE_FIELD( m_iArmorReductionFrom, FIELD_INTEGER ),
DEFINE_FIELD( m_flTimeUseSuspended, FIELD_TIME ),
//DEFINE_FIELD( m_hPlayerProxy, FIELD_EHANDLE ), //Shut up class check!
END_DATADESC()
CHL2_Player::CHL2_Player()
{
m_nNumMissPositions = 0;
m_pPlayerAISquad = 0;
m_bSprintEnabled = true;
m_flArmorReductionTime = 0.0f;
m_iArmorReductionFrom = 0;
}
//
// SUIT POWER DEVICES
//
#define SUITPOWER_CHARGE_RATE 12.5 // 100 units in 8 seconds
#ifdef HL2MP
CSuitPowerDevice SuitDeviceSprint( bits_SUIT_DEVICE_SPRINT, 25.0f ); // 100 units in 4 seconds
#else
CSuitPowerDevice SuitDeviceSprint( bits_SUIT_DEVICE_SPRINT, 12.5f ); // 100 units in 8 seconds
#endif
#ifdef HL2_EPISODIC
CSuitPowerDevice SuitDeviceFlashlight( bits_SUIT_DEVICE_FLASHLIGHT, 1.111 ); // 100 units in 90 second
#else
CSuitPowerDevice SuitDeviceFlashlight( bits_SUIT_DEVICE_FLASHLIGHT, 2.222 ); // 100 units in 45 second
#endif
CSuitPowerDevice SuitDeviceBreather( bits_SUIT_DEVICE_BREATHER, 6.7f ); // 100 units in 15 seconds (plus three padded seconds)
IMPLEMENT_SERVERCLASS_ST(CHL2_Player, DT_HL2_Player)
SendPropDataTable(SENDINFO_DT(m_HL2Local), &REFERENCE_SEND_TABLE(DT_HL2Local), SendProxy_SendLocalDataTable),
SendPropBool( SENDINFO(m_fIsSprinting) ),
END_SEND_TABLE()
void CHL2_Player::Precache( void )
{
BaseClass::Precache();
PrecacheScriptSound( "HL2Player.SprintNoPower" );
PrecacheScriptSound( "HL2Player.SprintStart" );
PrecacheScriptSound( "HL2Player.UseDeny" );
PrecacheScriptSound( "HL2Player.FlashLightOn" );
PrecacheScriptSound( "HL2Player.FlashLightOff" );
PrecacheScriptSound( "HL2Player.PickupWeapon" );
PrecacheScriptSound( "HL2Player.TrainUse" );
PrecacheScriptSound( "HL2Player.Use" );
PrecacheScriptSound( "HL2Player.BurnPain" );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CHL2_Player::CheckSuitZoom( void )
{
//#ifndef _XBOX
//Adrian - No zooming without a suit!
if ( IsSuitEquipped() )
{
if ( m_afButtonReleased & IN_ZOOM )
{
StopZooming();
}
else if ( m_afButtonPressed & IN_ZOOM )
{
StartZooming();
}
}
//#endif//_XBOX
}
void CHL2_Player::EquipSuit( bool bPlayEffects )
{
MDLCACHE_CRITICAL_SECTION();
BaseClass::EquipSuit();
m_HL2Local.m_bDisplayReticle = true;
if ( bPlayEffects == true )
{
StartAdmireGlovesAnimation();
}
}
void CHL2_Player::RemoveSuit( void )
{
BaseClass::RemoveSuit();
m_HL2Local.m_bDisplayReticle = false;
}
void CHL2_Player::HandleSpeedChanges( void )
{
int buttonsChanged = m_afButtonPressed | m_afButtonReleased;
if( buttonsChanged & IN_SPEED )
{
// The state of the sprint/run button has changed.
if ( IsSuitEquipped() )
{
if ( !(m_afButtonPressed & IN_SPEED) && IsSprinting() )
{
#ifndef _XBOX
StopSprinting();
#endif
}
else if ( (m_afButtonPressed & IN_SPEED) && !IsSprinting() )
{
if ( CanSprint() )
{
#ifdef _XBOX
StartAutoSprint();
#else
StartSprinting();
#endif
}
else
{
// Reset key, so it will be activated post whatever is suppressing it.
m_nButtons &= ~IN_SPEED;
}
}
#ifdef _XBOX
else if( (m_afButtonPressed & IN_SPEED) && IsSprinting() )
{
StopSprinting();
}
#endif//_XBOX
}
else
{
// ROBIN: We're trialling Sprint not affecting movement speed without the HEV suit
/*
// The state of the WALK button has changed.
if( !IsWalking() && !(m_afButtonPressed & IN_SPEED) )
{
StartWalking();
}
else if( IsWalking() && !IsSprinting() && (m_afButtonPressed & IN_SPEED) && !(m_nButtons & IN_DUCK) )
{
StopWalking();
}
*/
}
}
else if( buttonsChanged & IN_WALK )
{
if ( IsSuitEquipped() )
{
// The state of the WALK button has changed.
if( IsWalking() && !(m_afButtonPressed & IN_WALK) )
{
StopWalking();
}
else if( !IsWalking() && !IsSprinting() && (m_afButtonPressed & IN_WALK) && !(m_nButtons & IN_DUCK) )
{
StartWalking();
}
}
}
if ( IsSuitEquipped() && m_fIsWalking && !(m_nButtons & IN_WALK) )
StopWalking();
}
//-----------------------------------------------------------------------------
// This happens when we powerdown from the mega physcannon to the regular one
//-----------------------------------------------------------------------------
void CHL2_Player::HandleArmorReduction( void )
{
if ( m_flArmorReductionTime < gpGlobals->curtime )
return;
if ( ArmorValue() <= 0 )
return;
float flPercent = 1.0f - (( m_flArmorReductionTime - gpGlobals->curtime ) / ARMOR_DECAY_TIME );
int iArmor = Lerp( flPercent, m_iArmorReductionFrom, 0 );
SetArmorValue( iArmor );
}
//-----------------------------------------------------------------------------
// Purpose: Allow pre-frame adjustments on the player
//-----------------------------------------------------------------------------
void CHL2_Player::PreThink(void)
{
if ( player_showpredictedposition.GetBool() )
{
Vector predPos;
UTIL_PredictedPosition( this, player_showpredictedposition_timestep.GetFloat(), &predPos );
NDebugOverlay::Box( predPos, NAI_Hull::Mins( GetHullType() ), NAI_Hull::Maxs( GetHullType() ), 0, 255, 0, 0, 0.01f );
NDebugOverlay::Line( GetAbsOrigin(), predPos, 0, 255, 0, 0, 0.01f );
}
// Riding a vehicle?
if ( IsInAVehicle() )
{
VPROF( "CHL2_Player::PreThink-Vehicle" );
// make sure we update the client, check for timed damage and update suit even if we are in a vehicle
UpdateClientData();
CheckTimeBasedDamage();
// Allow the suit to recharge when in the vehicle.
SuitPower_Update();
CheckSuitUpdate();
CheckSuitZoom();
WaterMove();
return;
}
// This is an experiment of mine- autojumping!
// only affects you if sv_autojump is nonzero.
if( (GetFlags() & FL_ONGROUND) && sv_autojump.GetFloat() != 0 )
{
VPROF( "CHL2_Player::PreThink-Autojump" );
// check autojump
Vector vecCheckDir;
vecCheckDir = GetAbsVelocity();
float flVelocity = VectorNormalize( vecCheckDir );
if( flVelocity > 200 )
{
// Going fast enough to autojump
vecCheckDir = WorldSpaceCenter() + vecCheckDir * 34 - Vector( 0, 0, 16 );
trace_t tr;
UTIL_TraceHull( WorldSpaceCenter() - Vector( 0, 0, 16 ), vecCheckDir, NAI_Hull::Mins(HULL_TINY_CENTERED),NAI_Hull::Maxs(HULL_TINY_CENTERED), MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER, &tr );
//NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, true, 10 );
if( tr.fraction == 1.0 && !tr.startsolid )
{
// Now trace down!
UTIL_TraceLine( vecCheckDir, vecCheckDir - Vector( 0, 0, 64 ), MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &tr );
//NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, true, 10 );
if( tr.fraction == 1.0 && !tr.startsolid )
{
// !!!HACKHACK
// I KNOW, I KNOW, this is definitely not the right way to do this,
// but I'm prototyping! (sjb)
Vector vecNewVelocity = GetAbsVelocity();
vecNewVelocity.z += 250;
SetAbsVelocity( vecNewVelocity );
}
}
}
}
VPROF_SCOPE_BEGIN( "CHL2_Player::PreThink-Speed" );
HandleSpeedChanges();
#ifdef HL2_EPISODIC
HandleArmorReduction();
#endif
#ifdef _XBOX
if( m_bIsAutoSprinting )
{
// Stop sprinting if the player lets off the stick for a moment.
if( GetStickDist() == 0.0f )
{
if( gpGlobals->curtime > m_fAutoSprintMinTime )
{
StopSprinting();
}
}
else
{
// Stop sprinting one half second after the player stops inputting with the move stick.
m_fAutoSprintMinTime = gpGlobals->curtime + 0.5f;
}
}
#endif//_XBOX
VPROF_SCOPE_END();
if ( g_fGameOver || IsPlayerLockedInPlace() )
return; // finale
VPROF_SCOPE_BEGIN( "CHL2_Player::PreThink-ItemPreFrame" );
ItemPreFrame( );
VPROF_SCOPE_END();
VPROF_SCOPE_BEGIN( "CHL2_Player::PreThink-WaterMove" );
WaterMove();
VPROF_SCOPE_END();
if ( g_pGameRules && g_pGameRules->FAllowFlashlight() )
m_Local.m_iHideHUD &= ~HIDEHUD_FLASHLIGHT;
else
m_Local.m_iHideHUD |= HIDEHUD_FLASHLIGHT;
VPROF_SCOPE_BEGIN( "CHL2_Player::PreThink-CommanderUpdate" );
CommanderUpdate();
VPROF_SCOPE_END();
// Operate suit accessories and manage power consumption/charge
VPROF_SCOPE_BEGIN( "CHL2_Player::PreThink-SuitPower_Update" );
SuitPower_Update();
VPROF_SCOPE_END();
// checks if new client data (for HUD and view control) needs to be sent to the client
VPROF_SCOPE_BEGIN( "CHL2_Player::PreThink-UpdateClientData" );
UpdateClientData();
VPROF_SCOPE_END();
VPROF_SCOPE_BEGIN( "CHL2_Player::PreThink-CheckTimeBasedDamage" );
CheckTimeBasedDamage();
VPROF_SCOPE_END();
VPROF_SCOPE_BEGIN( "CHL2_Player::PreThink-CheckSuitUpdate" );
CheckSuitUpdate();
VPROF_SCOPE_END();
VPROF_SCOPE_BEGIN( "CHL2_Player::PreThink-CheckSuitZoom" );
CheckSuitZoom();
VPROF_SCOPE_END();
if (m_lifeState >= LIFE_DYING)
{
PlayerDeathThink();
return;
}
#ifdef HL2_EPISODIC
CheckFlashlight();
#endif
// So the correct flags get sent to client asap.
//
if ( m_afPhysicsFlags & PFLAG_DIROVERRIDE )
AddFlag( FL_ONTRAIN );
else
RemoveFlag( FL_ONTRAIN );
// Train speed control
if ( m_afPhysicsFlags & PFLAG_DIROVERRIDE )
{
CBaseEntity *pTrain = GetGroundEntity();
float vel;
if ( pTrain )
{
if ( !(pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) )
pTrain = NULL;
}
if ( !pTrain )
{
if ( GetActiveWeapon() && (GetActiveWeapon()->ObjectCaps() & FCAP_DIRECTIONAL_USE) )
{
m_iTrain = TRAIN_ACTIVE | TRAIN_NEW;
if ( m_nButtons & IN_FORWARD )
{
m_iTrain |= TRAIN_FAST;
}
else if ( m_nButtons & IN_BACK )
{
m_iTrain |= TRAIN_BACK;
}
else
{
m_iTrain |= TRAIN_NEUTRAL;
}
return;
}
else
{
trace_t trainTrace;
// Maybe this is on the other side of a level transition
UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector(0,0,-38),
MASK_PLAYERSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &trainTrace );
if ( trainTrace.fraction != 1.0 && trainTrace.m_pEnt )
pTrain = trainTrace.m_pEnt;
if ( !pTrain || !(pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) || !pTrain->OnControls(this) )
{
// Warning( "In train mode with no train!\n" );
m_afPhysicsFlags &= ~PFLAG_DIROVERRIDE;
m_iTrain = TRAIN_NEW|TRAIN_OFF;
return;
}
}
}
else if ( !( GetFlags() & FL_ONGROUND ) || pTrain->HasSpawnFlags( SF_TRACKTRAIN_NOCONTROL ) || (m_nButtons & (IN_MOVELEFT|IN_MOVERIGHT) ) )
{
// Turn off the train if you jump, strafe, or the train controls go dead
m_afPhysicsFlags &= ~PFLAG_DIROVERRIDE;
m_iTrain = TRAIN_NEW|TRAIN_OFF;
return;
}
SetAbsVelocity( vec3_origin );
vel = 0;
if ( m_afButtonPressed & IN_FORWARD )
{
vel = 1;
pTrain->Use( this, this, USE_SET, (float)vel );
}
else if ( m_afButtonPressed & IN_BACK )
{
vel = -1;
pTrain->Use( this, this, USE_SET, (float)vel );
}
if (vel)
{
m_iTrain = TrainSpeed(pTrain->m_flSpeed, ((CFuncTrackTrain*)pTrain)->GetMaxSpeed());
m_iTrain |= TRAIN_ACTIVE|TRAIN_NEW;
}
}
else if (m_iTrain & TRAIN_ACTIVE)
{
m_iTrain = TRAIN_NEW; // turn off train
}
//
// If we're not on the ground, we're falling. Update our falling velocity.
//
if ( !( GetFlags() & FL_ONGROUND ) )
{
m_Local.m_flFallVelocity = -GetAbsVelocity().z;
}
if ( m_afPhysicsFlags & PFLAG_ONBARNACLE )
{
bool bOnBarnacle = false;
CNPC_Barnacle *pBarnacle = NULL;
do
{
// FIXME: Not a good or fast solution, but maybe it will catch the bug!
pBarnacle = (CNPC_Barnacle*)gEntList.FindEntityByClassname( pBarnacle, "npc_barnacle" );
if ( pBarnacle )
{
if ( pBarnacle->GetEnemy() == this )
{
bOnBarnacle = true;
}
}
} while ( pBarnacle );
if ( !bOnBarnacle )
{
Warning( "Attached to barnacle?\n" );
Assert( 0 );
m_afPhysicsFlags &= ~PFLAG_ONBARNACLE;
}
else
{
SetAbsVelocity( vec3_origin );
}
}
// StudioFrameAdvance( );//!!!HACKHACK!!! Can't be hit by traceline when not animating?
// Update weapon's ready status
UpdateWeaponPosture();
// Disallow shooting while zooming
#ifdef _XBOX
if ( IsZooming() )
{
if( !GetActiveWeapon() || !GetActiveWeapon()->IsWeaponZoomed() )
{
// If not zoomed because of the weapon itself, do not attack.
m_nButtons &= ~(IN_ATTACK|IN_ATTACK2);
}
}
#else
if ( m_nButtons & IN_ZOOM )
{
//FIXME: Held weapons like the grenade get sad when this happens
#ifdef HL2_EPISODIC
// Episodic allows players to zoom while using a func_tank
if ( !m_hUseEntity || GetActiveWeapon()->IsWeaponVisible() )
#endif
m_nButtons &= ~(IN_ATTACK|IN_ATTACK2);
}
#endif//_XBOX
}
void CHL2_Player::PostThink( void )
{
BaseClass::PostThink();
if ( !g_fGameOver && !IsPlayerLockedInPlace() && IsAlive() )
{
HandleAdmireGlovesAnimation();
}
}
void CHL2_Player::StartAdmireGlovesAnimation( void )
{
CBaseViewModel *vm = GetViewModel( 0 );
if ( vm && !GetActiveWeapon() )
{
vm->SetWeaponModel( "models/weapons/v_hands.mdl", NULL );
ShowViewModel( true );
int idealSequence = vm->SelectWeightedSequence( ACT_VM_IDLE );
if ( idealSequence >= 0 )
{
vm->SendViewModelMatchingSequence( idealSequence );
m_flAdmireGlovesAnimTime = gpGlobals->curtime + vm->SequenceDuration( idealSequence );
}
}
}
void CHL2_Player::HandleAdmireGlovesAnimation( void )
{
CBaseViewModel *pVM = GetViewModel();
if ( pVM && pVM->GetOwningWeapon() == NULL )
{
if ( m_flAdmireGlovesAnimTime != 0.0 )
{
if ( m_flAdmireGlovesAnimTime > gpGlobals->curtime )
{
pVM->m_flPlaybackRate = 1.0f;
pVM->StudioFrameAdvance( );
}
else if ( m_flAdmireGlovesAnimTime < gpGlobals->curtime )
{
m_flAdmireGlovesAnimTime = 0.0f;
pVM->SetWeaponModel( NULL, NULL );
}
}
}
else
m_flAdmireGlovesAnimTime = 0.0f;
}
#define HL2PLAYER_RELOADGAME_ATTACK_DELAY 1.0f
void CHL2_Player::Activate( void )
{
BaseClass::Activate();
InitSprinting();
#ifdef HL2_EPISODIC
// Delay attacks by 1 second after loading a game.
if ( GetActiveWeapon() )
{
float flRemaining = GetActiveWeapon()->m_flNextPrimaryAttack - gpGlobals->curtime;
if ( flRemaining < HL2PLAYER_RELOADGAME_ATTACK_DELAY )
{
GetActiveWeapon()->m_flNextPrimaryAttack = gpGlobals->curtime + HL2PLAYER_RELOADGAME_ATTACK_DELAY;
}
flRemaining = GetActiveWeapon()->m_flNextSecondaryAttack - gpGlobals->curtime;
if ( flRemaining < HL2PLAYER_RELOADGAME_ATTACK_DELAY )
{
GetActiveWeapon()->m_flNextSecondaryAttack = gpGlobals->curtime + HL2PLAYER_RELOADGAME_ATTACK_DELAY;
}
}
#endif
}
//------------------------------------------------------------------------------
// Purpose :
// Input :
// Output :
//------------------------------------------------------------------------------
Class_T CHL2_Player::Classify ( void )
{
// If player controlling another entity? If so, return this class
if (m_nControlClass != CLASS_NONE)
{
return m_nControlClass;
}
else
{
if(IsInAVehicle())
{
IServerVehicle *pVehicle = GetVehicle();
return pVehicle->ClassifyPassenger( this, CLASS_PLAYER );
}
else
{
return CLASS_PLAYER;
}
}
}
//-----------------------------------------------------------------------------
// Purpose: This is a generic function (to be implemented by sub-classes) to
// handle specific interactions between different types of characters
// (For example the barnacle grabbing an NPC)
// Input : Constant for the type of interaction
// Output : true - if sub-class has a response for the interaction
// false - if sub-class has no response
//-----------------------------------------------------------------------------
bool CHL2_Player::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt)
{
if ( interactionType == g_interactionBarnacleVictimDangle )
return false;
if (interactionType == g_interactionBarnacleVictimReleased)
{
m_afPhysicsFlags &= ~PFLAG_ONBARNACLE;
SetMoveType( MOVETYPE_WALK );
return true;
}
else if (interactionType == g_interactionBarnacleVictimGrab)
{
#ifdef HL2_EPISODIC
CNPC_Alyx *pAlyx = CNPC_Alyx::GetAlyx();
if ( pAlyx )
{
// Make Alyx totally hate this barnacle so that she saves the player.
int priority;
priority = pAlyx->IRelationPriority(sourceEnt);
pAlyx->AddEntityRelationship( sourceEnt, D_HT, priority + 5 );
}
#endif//HL2_EPISODIC
m_afPhysicsFlags |= PFLAG_ONBARNACLE;
ClearUseEntity();
return true;
}
return false;
}
void CHL2_Player::PlayerRunCommand(CUserCmd *ucmd, IMoveHelper *moveHelper)
{
// Handle FL_FROZEN.
if ( m_afPhysicsFlags & PFLAG_ONBARNACLE )
{
ucmd->forwardmove = 0;
ucmd->sidemove = 0;
ucmd->upmove = 0;
ucmd->buttons &= ~IN_USE;
}
// Can't use stuff while dead
if ( IsDead() )
{
ucmd->buttons &= ~IN_USE;
}
//Update our movement information
if ( ( ucmd->forwardmove != 0 ) || ( ucmd->sidemove != 0 ) || ( ucmd->upmove != 0 ) )
{
m_flIdleTime -= TICK_INTERVAL * 2.0f;
if ( m_flIdleTime < 0.0f )
{
m_flIdleTime = 0.0f;
}
m_flMoveTime += TICK_INTERVAL;
if ( m_flMoveTime > 4.0f )
{
m_flMoveTime = 4.0f;
}
}
else
{
m_flIdleTime += TICK_INTERVAL;
if ( m_flIdleTime > 4.0f )
{
m_flIdleTime = 4.0f;
}
m_flMoveTime -= TICK_INTERVAL * 2.0f;
if ( m_flMoveTime < 0.0f )
{
m_flMoveTime = 0.0f;
}
}
//Msg("Player time: [ACTIVE: %f]\t[IDLE: %f]\n", m_flMoveTime, m_flIdleTime );
BaseClass::PlayerRunCommand( ucmd, moveHelper );
}
//-----------------------------------------------------------------------------
// Purpose: Sets HL2 specific defaults.
//-----------------------------------------------------------------------------
void CHL2_Player::Spawn(void)
{
#ifndef HL2MP
SetModel( "models/player.mdl" );
#endif
BaseClass::Spawn();
//
// Our player movement speed is set once here. This will override the cl_xxxx
// cvars unless they are set to be lower than this.
//
//m_flMaxspeed = 320;
if ( !IsSuitEquipped() )
StartWalking();
SuitPower_SetCharge( 100 );
m_Local.m_iHideHUD |= HIDEHUD_CHAT;
m_pPlayerAISquad = g_AI_SquadManager.FindCreateSquad(AllocPooledString(PLAYER_SQUADNAME));
InitSprinting();
GetPlayerProxy();
SetFlashlightPowerDrainScale( 1.0f );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHL2_Player::InitSprinting( void )
{
StopSprinting();
}
//-----------------------------------------------------------------------------
// Purpose: Returns whether or not we are allowed to sprint now.
//-----------------------------------------------------------------------------
bool CHL2_Player::CanSprint()
{
return ( m_bSprintEnabled && // Only if sprint is enabled
!IsWalking() && // Not if we're walking
!( m_Local.m_bDucked && !m_Local.m_bDucking ) && // Nor if we're ducking
(GetWaterLevel() != 3) && // Certainly not underwater
(GlobalEntity_GetState("suit_no_sprint") != GLOBAL_ON) ); // Out of the question without the sprint module
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#ifdef _XBOX
void CHL2_Player::StartAutoSprint()
{
if( IsSprinting() )
{
StopSprinting();
}
else
{
StartSprinting();
m_bIsAutoSprinting = true;
m_fAutoSprintMinTime = gpGlobals->curtime + 1.5f;
}
}
#endif// _XBOX
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHL2_Player::StartSprinting( void )
{
if( m_HL2Local.m_flSuitPower < 10 )
{
// Don't sprint unless there's a reasonable
// amount of suit power.
CPASAttenuationFilter filter( this );
filter.UsePredictionRules();
EmitSound( filter, entindex(), "HL2Player.SprintNoPower" );
return;
}
if( !SuitPower_AddDevice( SuitDeviceSprint ) )
return;
CPASAttenuationFilter filter( this );
filter.UsePredictionRules();
EmitSound( filter, entindex(), "HL2Player.SprintStart" );
SetMaxSpeed( HL2_SPRINT_SPEED );
m_fIsSprinting = true;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHL2_Player::StopSprinting( void )
{
if ( m_HL2Local.m_bitsActiveDevices & SuitDeviceSprint.GetDeviceID() )
{
SuitPower_RemoveDevice( SuitDeviceSprint );
}
SetMaxSpeed( HL2_NORM_SPEED );
m_fIsSprinting = false;
#ifdef _XBOX
m_bIsAutoSprinting = false;
m_fAutoSprintMinTime = 0.0f;
#endif//_XBOX
}
//-----------------------------------------------------------------------------
// Purpose: Called to disable and enable sprint due to temporary circumstances:
// - Carrying a heavy object with the physcannon
//-----------------------------------------------------------------------------
void CHL2_Player::EnableSprint( bool bEnable )
{
if ( !bEnable && IsSprinting() )
{
StopSprinting();
}
m_bSprintEnabled = bEnable;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHL2_Player::StartWalking( void )
{
SetMaxSpeed( HL2_WALK_SPEED );
m_fIsWalking = true;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHL2_Player::StopWalking( void )
{
SetMaxSpeed( HL2_NORM_SPEED );
m_fIsWalking = false;
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CHL2_Player::CanZoom( CBaseEntity *pRequester )
{
if ( IsZooming() )
return false;
//Check our weapon
return true;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHL2_Player::ToggleZoom(void)
{
if( IsZooming() )
{
StopZooming();
}
else
{
StartZooming();
}
}
//-----------------------------------------------------------------------------
// Purpose: +zoom suit zoom
//-----------------------------------------------------------------------------
void CHL2_Player::StartZooming( void )
{
int iFOV = 25;
if ( SetFOV( this, iFOV, 0.4f ) )
{
m_HL2Local.m_bZooming = true;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CHL2_Player::StopZooming( void )
{
int iFOV = GetZoomOwnerDesiredFOV( m_hZoomOwner );
if ( SetFOV( this, iFOV, 0.2f ) )
{
m_HL2Local.m_bZooming = false;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CHL2_Player::IsZooming( void )
{
if ( m_hZoomOwner != NULL )
return true;
return false;
}
class CPhysicsPlayerCallback : public IPhysicsPlayerControllerEvent
{
public:
int ShouldMoveTo( IPhysicsObject *pObject, const Vector &position )
{
CHL2_Player *pPlayer = (CHL2_Player *)pObject->GetGameData();
if ( pPlayer )
{
if ( pPlayer->TouchedPhysics() )
{
return 0;
}
}
return 1;
}
};
static CPhysicsPlayerCallback playerCallback;
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CHL2_Player::InitVCollision( void )
{
BaseClass::InitVCollision();
// Setup the HL2 specific callback.
IPhysicsPlayerController *pPlayerController = GetPhysicsController();
if ( pPlayerController )
{
pPlayerController->SetEventHandler( &playerCallback );
}
}
CHL2_Player::~CHL2_Player( void )
{
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CHL2_Player::CommanderFindGoal( commandgoal_t *pGoal )
{
CAI_BaseNPC *pAllyNpc;
trace_t tr;
Vector vecTarget;
Vector forward;
EyeVectors( &forward );
//---------------------------------
// MASK_SHOT on purpose! So that you don't hit the invisible hulls of the NPCs.
CTraceFilterSkipTwoEntities filter( this, PhysCannonGetHeldEntity( GetActiveWeapon() ), COLLISION_GROUP_INTERACTIVE_DEBRIS );
UTIL_TraceLine( EyePosition(), EyePosition() + forward * MAX_COORD_RANGE, MASK_SHOT, &filter, &tr );
if( !tr.DidHitWorld() )
{
CUtlVector<CAI_BaseNPC *> Allies;
AISquadIter_t iter;
for ( pAllyNpc = m_pPlayerAISquad->GetFirstMember(&iter); pAllyNpc; pAllyNpc = m_pPlayerAISquad->GetNextMember(&iter) )
{
if ( pAllyNpc->IsCommandable() )
Allies.AddToTail( pAllyNpc );
}
for( int i = 0 ; i < Allies.Count() ; i++ )
{
if( Allies[ i ]->IsValidCommandTarget( tr.m_pEnt ) )
{
pGoal->m_pGoalEntity = tr.m_pEnt;
return true;
}
}
}
if( tr.fraction == 1.0 || (tr.surface.flags & SURF_SKY) )
{
// Move commands invalid against skybox.
pGoal->m_vecGoalLocation = tr.endpos;
return false;
}
if ( tr.m_pEnt->IsNPC() && ((CAI_BaseNPC *)(tr.m_pEnt))->IsCommandable() )
{
pGoal->m_vecGoalLocation = tr.m_pEnt->GetAbsOrigin();
}
else
{
vecTarget = tr.endpos;
Vector mins( -16, -16, 0 );
Vector maxs( 16, 16, 0 );
// Back up from whatever we hit so that there's enough space at the
// target location for a bounding box.
// Now trace down.
//UTIL_TraceLine( vecTarget, vecTarget - Vector( 0, 0, 8192 ), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
UTIL_TraceHull( vecTarget + tr.plane.normal * 24,
vecTarget - Vector( 0, 0, 8192 ),
mins,
maxs,
MASK_SOLID_BRUSHONLY,
this,
COLLISION_GROUP_NONE,
&tr );
if ( !tr.startsolid )
pGoal->m_vecGoalLocation = tr.endpos;
else
pGoal->m_vecGoalLocation = vecTarget;
}
pAllyNpc = GetSquadCommandRepresentative();
if ( !pAllyNpc )
return false;
vecTarget = pGoal->m_vecGoalLocation;
if ( !pAllyNpc->FindNearestValidGoalPos( vecTarget, &pGoal->m_vecGoalLocation ) )
return false;
return ( ( vecTarget - pGoal->m_vecGoalLocation ).LengthSqr() < Square( 15*12 ) );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CAI_BaseNPC *CHL2_Player::GetSquadCommandRepresentative()
{
if ( m_pPlayerAISquad != NULL )
{
CAI_BaseNPC *pAllyNpc = m_pPlayerAISquad->GetFirstMember();
if ( pAllyNpc )
{
return pAllyNpc->GetSquadCommandRepresentative();
}
}
return NULL;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CHL2_Player::GetNumSquadCommandables()
{
AISquadIter_t iter;
int c = 0;
for ( CAI_BaseNPC *pAllyNpc = m_pPlayerAISquad->GetFirstMember(&iter); pAllyNpc; pAllyNpc = m_pPlayerAISquad->GetNextMember(&iter) )
{
if ( pAllyNpc->IsCommandable() )
c++;
}
return c;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CHL2_Player::GetNumSquadCommandableMedics()
{
AISquadIter_t iter;
int c = 0;
for ( CAI_BaseNPC *pAllyNpc = m_pPlayerAISquad->GetFirstMember(&iter); pAllyNpc; pAllyNpc = m_pPlayerAISquad->GetNextMember(&iter) )
{
if ( pAllyNpc->IsCommandable() && pAllyNpc->IsMedic() )
c++;
}
return c;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHL2_Player::CommanderUpdate()
{
CAI_BaseNPC *pCommandRepresentative = GetSquadCommandRepresentative();
bool bFollowMode = false;
if ( pCommandRepresentative )
{
bFollowMode = ( pCommandRepresentative->GetCommandGoal() == vec3_invalid );
// set the variables for network transmission (to show on the hud)
m_HL2Local.m_iSquadMemberCount = GetNumSquadCommandables();
m_HL2Local.m_iSquadMedicCount = GetNumSquadCommandableMedics();
m_HL2Local.m_fSquadInFollowMode = bFollowMode;
// debugging code for displaying extra squad indicators
/*
char *pszMoving = "";
AISquadIter_t iter;
for ( CAI_BaseNPC *pAllyNpc = m_pPlayerAISquad->GetFirstMember(&iter); pAllyNpc; pAllyNpc = m_pPlayerAISquad->GetNextMember(&iter) )
{
if ( pAllyNpc->IsCommandMoving() )
{
pszMoving = "<-";
break;
}
}
NDebugOverlay::ScreenText(
0.932, 0.919,
CFmtStr( "%d|%c%s", GetNumSquadCommandables(), ( bFollowMode ) ? 'F' : 'S', pszMoving ),
255, 128, 0, 128,
0 );
*/
}
else
{
m_HL2Local.m_iSquadMemberCount = 0;
m_HL2Local.m_iSquadMedicCount = 0;
m_HL2Local.m_fSquadInFollowMode = true;
}
if ( m_QueuedCommand != CC_NONE && ( m_QueuedCommand == CC_FOLLOW || gpGlobals->realtime - m_RealTimeLastSquadCommand >= player_squad_double_tap_time.GetFloat() ) )
{
CommanderExecute( m_QueuedCommand );
m_QueuedCommand = CC_NONE;
}
else if ( !bFollowMode && pCommandRepresentative && m_CommanderUpdateTimer.Expired() && player_squad_transient_commands.GetBool() )
{
m_CommanderUpdateTimer.Set(2.5);
if ( pCommandRepresentative->ShouldAutoSummon() )
CommanderExecute( CC_FOLLOW );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//
// bHandled - indicates whether to continue delivering this order to
// all allies. Allows us to stop delivering certain types of orders once we find
// a suitable candidate. (like picking up a single weapon. We don't wish for
// all allies to respond and try to pick up one weapon).
//-----------------------------------------------------------------------------
bool CHL2_Player::CommanderExecuteOne( CAI_BaseNPC *pNpc, const commandgoal_t &goal, CAI_BaseNPC **Allies, int numAllies )
{
if ( goal.m_pGoalEntity )
{
return pNpc->TargetOrder( goal.m_pGoalEntity, Allies, numAllies );
}
else if ( pNpc->IsInPlayerSquad() )
{
pNpc->MoveOrder( goal.m_vecGoalLocation, Allies, numAllies );
}
return true;
}
//---------------------------------------------------------
//---------------------------------------------------------
void CHL2_Player::CommanderExecute( CommanderCommand_t command )
{
CAI_BaseNPC *pPlayerSquadLeader = GetSquadCommandRepresentative();
if ( !pPlayerSquadLeader )
{
EmitSound( "HL2Player.UseDeny" );
return;
}
int i;
CUtlVector<CAI_BaseNPC *> Allies;
commandgoal_t goal;
if ( command == CC_TOGGLE )
{
if ( pPlayerSquadLeader->GetCommandGoal() != vec3_invalid )
command = CC_FOLLOW;
else
command = CC_SEND;
}
else
{
if ( command == CC_FOLLOW && pPlayerSquadLeader->GetCommandGoal() == vec3_invalid )
return;
}
if ( command == CC_FOLLOW )
{
goal.m_pGoalEntity = this;
goal.m_vecGoalLocation = vec3_invalid;
}
else
{
goal.m_pGoalEntity = NULL;
goal.m_vecGoalLocation = vec3_invalid;
// Find a goal for ourselves.
if( !CommanderFindGoal( &goal ) )
{
EmitSound( "HL2Player.UseDeny" );
return; // just keep following
}
}
#ifdef _DEBUG
if( goal.m_pGoalEntity == NULL && goal.m_vecGoalLocation == vec3_invalid )
{
DevMsg( 1, "**ERROR: Someone sent an invalid goal to CommanderExecute!\n" );
}
#endif // _DEBUG
AISquadIter_t iter;
for ( CAI_BaseNPC *pAllyNpc = m_pPlayerAISquad->GetFirstMember(&iter); pAllyNpc; pAllyNpc = m_pPlayerAISquad->GetNextMember(&iter) )
{
if ( pAllyNpc->IsCommandable() )
Allies.AddToTail( pAllyNpc );
}
//---------------------------------
// If the trace hits an NPC, send all ally NPCs a "target" order. Always
// goes to targeted one first
#ifdef DEBUG
int nAIs = g_AI_Manager.NumAIs();
#endif
CAI_BaseNPC * pTargetNpc = (goal.m_pGoalEntity) ? goal.m_pGoalEntity->MyNPCPointer() : NULL;
bool bHandled = false;
if( pTargetNpc )
{
bHandled = !CommanderExecuteOne( pTargetNpc, goal, Allies.Base(), Allies.Count() );
}
for ( i = 0; !bHandled && i < Allies.Count(); i++ )
{
if ( Allies[i] != pTargetNpc && Allies[i]->IsPlayerAlly() )
{
bHandled = !CommanderExecuteOne( Allies[i], goal, Allies.Base(), Allies.Count() );
}
Assert( nAIs == g_AI_Manager.NumAIs() ); // not coded to support mutating set of NPCs
}
}
//-----------------------------------------------------------------------------
// Enter/exit commander mode, manage ally selection.
//-----------------------------------------------------------------------------
void CHL2_Player::CommanderMode()
{
float commandInterval = gpGlobals->realtime - m_RealTimeLastSquadCommand;
m_RealTimeLastSquadCommand = gpGlobals->realtime;
if ( commandInterval < player_squad_double_tap_time.GetFloat() )
{
m_QueuedCommand = CC_FOLLOW;
}
else
{
m_QueuedCommand = (player_squad_transient_commands.GetBool()) ? CC_SEND : CC_TOGGLE;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : iImpulse -
//-----------------------------------------------------------------------------
void CHL2_Player::CheatImpulseCommands( int iImpulse )
{
switch( iImpulse )
{
case 50:
{
CommanderMode();
break;
}
case 51:
{
// Cheat to create a dynamic resupply item
Vector vecForward;
AngleVectors( EyeAngles(), &vecForward );
CBaseEntity *pItem = (CBaseEntity *)CreateEntityByName( "item_dynamic_resupply" );
if ( pItem )
{
Vector vecOrigin = GetAbsOrigin() + vecForward * 256 + Vector(0,0,64);
QAngle vecAngles( 0, GetAbsAngles().y - 90, 0 );
pItem->SetAbsOrigin( vecOrigin );
pItem->SetAbsAngles( vecAngles );
pItem->KeyValue( "targetname", "resupply" );
pItem->Spawn();
pItem->Activate();
}
break;
}
case 52:
{
// Rangefinder
trace_t tr;
UTIL_TraceLine( EyePosition(), EyePosition() + EyeDirection3D() * MAX_COORD_RANGE, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
if( tr.fraction != 1.0 )
{
float flDist = (tr.startpos - tr.endpos).Length();
float flDist2D = (tr.startpos - tr.endpos).Length2D();
DevMsg( 1,"\nStartPos: %.4f %.4f %.4f --- EndPos: %.4f %.4f %.4f\n", tr.startpos.x,tr.startpos.y,tr.startpos.z,tr.endpos.x,tr.endpos.y,tr.endpos.z );
DevMsg( 1,"3D Distance: %.4f units (%.2f feet) --- 2D Distance: %.4f units (%.2f feet)\n", flDist, flDist / 12.0, flDist2D, flDist2D / 12.0 );
}
break;
}
default:
BaseClass::CheatImpulseCommands( iImpulse );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CHL2_Player::SetupVisibility( CBaseEntity *pViewEntity, unsigned char *pvs, int pvssize )
{
BaseClass::SetupVisibility( pViewEntity, pvs, pvssize );
int area = pViewEntity ? pViewEntity->NetworkProp()->AreaNum() : NetworkProp()->AreaNum();
PointCameraSetupVisibility( this, area, pvs, pvssize );
// If the intro script is playing, we want to get it's visibility points
if ( g_hIntroScript )
{
Vector vecOrigin;
CBaseEntity *pCamera;
if ( g_hIntroScript->GetIncludedPVSOrigin( &vecOrigin, &pCamera ) )
{
// If it's a point camera, turn it on
CPointCamera *pPointCamera = dynamic_cast< CPointCamera* >(pCamera);
if ( pPointCamera )
{
pPointCamera->SetActive( true );
}
engine->AddOriginToPVS( vecOrigin );
}
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHL2_Player::SuitPower_Update( void )
{
if( SuitPower_ShouldRecharge() )
{
SuitPower_Charge( SUITPOWER_CHARGE_RATE * gpGlobals->frametime );
}
else if( m_HL2Local.m_bitsActiveDevices )
{
float flPowerLoad = m_flSuitPowerLoad;
#ifndef _XBOX
//Since XBox quickly shuts off sprint if it isn't being used, this isn't an issue.
if( SuitPower_IsDeviceActive(SuitDeviceSprint) )
{
if( !fabs(GetAbsVelocity().x) && !fabs(GetAbsVelocity().y) )
{
// If player's not moving, don't drain sprint juice.
flPowerLoad -= SuitDeviceSprint.GetDeviceDrainRate();
}
}
#endif//_XBOX
if( SuitPower_IsDeviceActive(SuitDeviceFlashlight) )
{
float factor;
factor = 1.0f / m_flFlashlightPowerDrainScale;
flPowerLoad -= ( SuitDeviceFlashlight.GetDeviceDrainRate() * (1.0f - factor) );
}
if( !SuitPower_Drain( flPowerLoad * gpGlobals->frametime ) )
{
// TURN OFF ALL DEVICES!!
if( IsSprinting() )
{
StopSprinting();
}
if( FlashlightIsOn() )
{
#ifndef HL2MP
FlashlightTurnOff();
#endif
}
}
// turn off flashlight a little bit after it hits below one aux power notch (5%)
if( m_HL2Local.m_flSuitPower < 4.8f && FlashlightIsOn() )
{
#ifndef HL2MP
FlashlightTurnOff();
#endif
}
}
}
//-----------------------------------------------------------------------------
// Charge battery fully, turn off all devices.
//-----------------------------------------------------------------------------
void CHL2_Player::SuitPower_Initialize( void )
{
m_HL2Local.m_bitsActiveDevices = 0x00000000;
m_HL2Local.m_flSuitPower = 100.0;
m_flSuitPowerLoad = 0.0;
}
//-----------------------------------------------------------------------------
// Purpose: Interface to drain power from the suit's power supply.
// Input: Amount of charge to remove (expressed as percentage of full charge)
// Output: Returns TRUE if successful, FALSE if not enough power available.
//-----------------------------------------------------------------------------
bool CHL2_Player::SuitPower_Drain( float flPower )
{
// Suitpower cheat on?
if ( sv_infinite_aux_power.GetBool() )
return true;
m_HL2Local.m_flSuitPower -= flPower;
if( m_HL2Local.m_flSuitPower < 0.0 )
{
// Power is depleted!
// Clamp and fail
m_HL2Local.m_flSuitPower = 0.0;
return false;
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Interface to add power to the suit's power supply
// Input: Amount of charge to add
//-----------------------------------------------------------------------------
void CHL2_Player::SuitPower_Charge( float flPower )
{
m_HL2Local.m_flSuitPower += flPower;
if( m_HL2Local.m_flSuitPower > 100.0 )
{
// Full charge, clamp.
m_HL2Local.m_flSuitPower = 100.0;
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CHL2_Player::SuitPower_IsDeviceActive( const CSuitPowerDevice &device )
{
return (m_HL2Local.m_bitsActiveDevices & device.GetDeviceID()) != 0;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CHL2_Player::SuitPower_AddDevice( const CSuitPowerDevice &device )
{
// Make sure this device is NOT active!!
if( m_HL2Local.m_bitsActiveDevices & device.GetDeviceID() )
return false;
if( !IsSuitEquipped() )
return false;
m_HL2Local.m_bitsActiveDevices |= device.GetDeviceID();
m_flSuitPowerLoad += device.GetDeviceDrainRate();
return true;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CHL2_Player::SuitPower_RemoveDevice( const CSuitPowerDevice &device )
{
// Make sure this device is active!!
if( ! (m_HL2Local.m_bitsActiveDevices & device.GetDeviceID()) )
return false;
if( !IsSuitEquipped() )
return false;
// Take a little bit of suit power when you disable a device. If the device is shutting off
// because the battery is drained, no harm done, the battery charge cannot go below 0.
// This code in combination with the delay before the suit can start recharging are a defense
// against exploits where the player could rapidly tap sprint and never run out of power.
SuitPower_Drain( device.GetDeviceDrainRate() * 0.1f );
m_HL2Local.m_bitsActiveDevices &= ~device.GetDeviceID();
m_flSuitPowerLoad -= device.GetDeviceDrainRate();
if( m_HL2Local.m_bitsActiveDevices == 0x00000000 )
{
// With this device turned off, we can set this timer which tells us when the
// suit power system entered a no-load state.
m_flTimeAllSuitDevicesOff = gpGlobals->curtime;
}
return true;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#define SUITPOWER_BEGIN_RECHARGE_DELAY 0.5f
bool CHL2_Player::SuitPower_ShouldRecharge( void )
{
// Make sure all devices are off.
if( m_HL2Local.m_bitsActiveDevices != 0x00000000 )
return false;
// Is the system fully charged?
if( m_HL2Local.m_flSuitPower >= 100.0f )
return false;
// Has the system been in a no-load state for long enough
// to begin recharging?
if( gpGlobals->curtime < m_flTimeAllSuitDevicesOff + SUITPOWER_BEGIN_RECHARGE_DELAY )
return false;
return true;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
ConVar sk_battery( "sk_battery","0" );
bool CHL2_Player::ApplyBattery( float powerMultiplier )
{
const float MAX_NORMAL_BATTERY = 100;
if ((ArmorValue() < MAX_NORMAL_BATTERY) && IsSuitEquipped())
{
int pct;
char szcharge[64];
IncrementArmorValue( sk_battery.GetFloat() * powerMultiplier, MAX_NORMAL_BATTERY );
CPASAttenuationFilter filter( this, "ItemBattery.Touch" );
EmitSound( filter, entindex(), "ItemBattery.Touch" );
CSingleUserRecipientFilter user( this );
user.MakeReliable();
UserMessageBegin( user, "ItemPickup" );
WRITE_STRING( "item_battery" );
MessageEnd();
// Suit reports new power level
// For some reason this wasn't working in release build -- round it.
pct = (int)( (float)(ArmorValue() * 100.0) * (1.0/MAX_NORMAL_BATTERY) + 0.5);
pct = (pct / 5);
if (pct > 0)
pct--;
Q_snprintf( szcharge,sizeof(szcharge),"!HEV_%1dP", pct );
//UTIL_EmitSoundSuit(edict(), szcharge);
//SetSuitUpdate(szcharge, FALSE, SUIT_NEXT_IN_30SEC);
return true;
}
return false;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CHL2_Player::FlashlightIsOn( void )
{
return IsEffectActive( EF_DIMLIGHT );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHL2_Player::FlashlightTurnOn( void )
{
if( m_bFlashlightDisabled )
return;
if( !SuitPower_AddDevice( SuitDeviceFlashlight ) )
return;
AddEffects( EF_DIMLIGHT );
EmitSound( "HL2Player.FlashLightOn" );
variant_t flashlighton;
flashlighton.SetFloat( m_HL2Local.m_flSuitPower / 100.0f );
FirePlayerProxyOutput( "OnFlashlightOn", flashlighton, this, this );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHL2_Player::FlashlightTurnOff( void )
{
if( !SuitPower_RemoveDevice( SuitDeviceFlashlight ) )
return;
RemoveEffects( EF_DIMLIGHT );
EmitSound( "HL2Player.FlashLightOff" );
variant_t flashlightoff;
flashlightoff.SetFloat( m_HL2Local.m_flSuitPower / 100.0f );
FirePlayerProxyOutput( "OnFlashlightOff", flashlightoff, this, this );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#define FLASHLIGHT_RANGE Square(600)
bool CHL2_Player::IsIlluminatedByFlashlight( CBaseEntity *pEntity, float *flReturnDot )
{
if( !FlashlightIsOn() )
return false;
if( pEntity->Classify() == CLASS_BARNACLE && pEntity->GetEnemy() == this )
{
// As long as my flashlight is on, the barnacle that's pulling me in is considered illuminated.
// This is because players often shine their flashlights at Alyx when they are in a barnacle's
// grasp, and wonder why Alyx isn't helping. Alyx isn't helping because the light isn't pointed
// at the barnacle. This will allow Alyx to see the barnacle no matter which way the light is pointed.
return true;
}
// Within 50 feet?
float flDistSqr = GetAbsOrigin().DistToSqr(pEntity->GetAbsOrigin());
if( flDistSqr > FLASHLIGHT_RANGE )
return false;
// Within 45 degrees?
Vector vecSpot = pEntity->WorldSpaceCenter();
Vector los;
// If the eyeposition is too close, move it back. Solves problems
// caused by the player being too close the target.
if ( flDistSqr < (128 * 128) )
{
Vector vecForward;
EyeVectors( &vecForward );
Vector vecMovedEyePos = EyePosition() - (vecForward * 128);
los = ( vecSpot - vecMovedEyePos );
}
else
{
los = ( vecSpot - EyePosition() );
}
VectorNormalize( los );
Vector facingDir = EyeDirection3D( );
float flDot = DotProduct( los, facingDir );
if ( flReturnDot )
{
*flReturnDot = flDot;
}
if ( flDot < 0.92387f )
return false;
if( !FVisible(pEntity) )
return false;
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Let NPCs know when the flashlight is trained on them
//-----------------------------------------------------------------------------
void CHL2_Player::CheckFlashlight( void )
{
if ( !FlashlightIsOn() )
return;
if ( m_flNextFlashlightCheckTime > gpGlobals->curtime )
return;
m_flNextFlashlightCheckTime = gpGlobals->curtime + FLASHLIGHT_NPC_CHECK_INTERVAL;
// Loop through NPCs looking for illuminated ones
for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ )
{
CAI_BaseNPC *pNPC = g_AI_Manager.AccessAIs()[i];
float flDot;
if ( IsIlluminatedByFlashlight( pNPC, &flDot ) )
{
pNPC->PlayerHasIlluminatedNPC( this, flDot );
}
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHL2_Player::SetPlayerUnderwater( bool state )
{
if ( state )
{
SuitPower_AddDevice( SuitDeviceBreather );
}
else
{
SuitPower_RemoveDevice( SuitDeviceBreather );
}
BaseClass::SetPlayerUnderwater( state );
}
//-----------------------------------------------------------------------------
bool CHL2_Player::PassesDamageFilter( const CTakeDamageInfo &info )
{
if( info.GetAttacker()->MyNPCPointer() && info.GetAttacker()->MyNPCPointer()->IsPlayerAlly() )
{
return false;
}
return BaseClass::PassesDamageFilter( info );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHL2_Player::InputDisableFlashlight( inputdata_t &inputdata )
{
if( FlashlightIsOn() )
FlashlightTurnOff();
m_bFlashlightDisabled = true;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHL2_Player::InputEnableFlashlight( inputdata_t &inputdata )
{
m_bFlashlightDisabled = false;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHL2_Player::InputIgnoreFallDamage( inputdata_t &inputdata )
{
float timeToIgnore = inputdata.value.Float();
if ( timeToIgnore <= 0.0 )
timeToIgnore = TIME_IGNORE_FALL_DAMAGE;
m_flTimeIgnoreFallDamage = gpGlobals->curtime + timeToIgnore;
}
//-----------------------------------------------------------------------------
// Purpose: Notification of a player's npc ally in the players squad being killed
//-----------------------------------------------------------------------------
void CHL2_Player::OnSquadMemberKilled( inputdata_t &data )
{
// send a message to the client, to notify the hud of the loss
CSingleUserRecipientFilter user( this );
user.MakeReliable();
UserMessageBegin( user, "SquadMemberDied" );
MessageEnd();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CHL2_Player::NotifyFriendsOfDamage( CBaseEntity *pAttackerEntity )
{
CAI_BaseNPC *pAttacker = pAttackerEntity->MyNPCPointer();
if ( pAttacker )
{
const Vector &origin = GetAbsOrigin();
for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ )
{
const float NEAR_Z = 12*12;
const float NEAR_XY_SQ = Square( 50*12 );
CAI_BaseNPC *pNpc = g_AI_Manager.AccessAIs()[i];
if ( pNpc->IsPlayerAlly() )
{
const Vector &originNpc = pNpc->GetAbsOrigin();
if ( fabsf( originNpc.z - origin.z ) < NEAR_Z )
{
if ( (originNpc.AsVector2D() - origin.AsVector2D()).LengthSqr() < NEAR_XY_SQ )
{
pNpc->OnFriendDamaged( this, pAttacker );
}
}
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CHL2_Player::OnTakeDamage( const CTakeDamageInfo &info )
{
if ( GlobalEntity_GetState( "gordon_invulnerable" ) == GLOBAL_ON )
return 0;
if ( ( info.GetDamageType() & DMG_FALL ) && m_flTimeIgnoreFallDamage > gpGlobals->curtime )
{
m_flTimeIgnoreFallDamage = 0;
return 0;
}
if( info.GetDamageType() & DMG_BLAST_SURFACE )
{
if( GetWaterLevel() > 2 )
{
// Don't take blast damage from anything above the surface.
if( info.GetInflictor()->GetWaterLevel() == 0 )
{
return 0;
}
}
}
if ( info.GetDamage() > 0.0f )
{
m_flLastDamageTime = gpGlobals->curtime;
if ( info.GetAttacker() )
NotifyFriendsOfDamage( info.GetAttacker() );
}
// Modify the amount of damage the player takes, based on skill.
CTakeDamageInfo playerDamage = info;
// Should we run this damage through the skill level adjustment?
bool bAdjustForSkillLevel = true;
if( info.GetDamageType() == DMG_GENERIC && info.GetAttacker() == this && info.GetInflictor() == this )
{
// Only do a skill level adjustment if the player isn't his own attacker AND inflictor.
// This prevents damage from SetHealth() inputs from being adjusted for skill level.
bAdjustForSkillLevel = false;
}
if( bAdjustForSkillLevel )
{
playerDamage.AdjustPlayerDamageTakenForSkillLevel();
}
return BaseClass::OnTakeDamage( playerDamage );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &info -
//-----------------------------------------------------------------------------
int CHL2_Player::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
// Drown
if( info.GetDamageType() & DMG_DROWN )
{
if( m_idrowndmg == m_idrownrestored )
{
EmitSound( "Player.DrownStart" );
}
else
{
EmitSound( "Player.DrownContinue" );
}
}
// Burnt
if ( info.GetDamageType() & DMG_BURN )
{
EmitSound( "HL2Player.BurnPain" );
}
if( (info.GetDamageType() & DMG_SLASH) && hl2_episodic.GetBool() )
{
if( m_afPhysicsFlags & PFLAG_USING )
{
// Stop the player using a rotating button for a short time if hit by a creature's melee attack.
// This is for the antlion burrow-corking training in EP1 (sjb).
SuspendUse( 0.5f );
}
}
// Call the base class implementation
return BaseClass::OnTakeDamage_Alive( info );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHL2_Player::OnDamagedByExplosion( const CTakeDamageInfo &info )
{
if ( info.GetInflictor() && info.GetInflictor()->ClassMatches( "mortarshell" ) )
{
// No ear ringing for mortar
UTIL_ScreenShake( info.GetInflictor()->GetAbsOrigin(), 4.0, 1.0, 0.5, 1000, SHAKE_START, false );
return;
}
BaseClass::OnDamagedByExplosion( info );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CHL2_Player::ShouldShootMissTarget( CBaseCombatCharacter *pAttacker )
{
if( gpGlobals->curtime > m_flTargetFindTime )
{
// Put this off into the future again.
m_flTargetFindTime = gpGlobals->curtime + random->RandomFloat( 3, 5 );
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Notifies Alyx that player has put a combine ball into a socket so she can comment on it.
// Input : pCombineBall - ball the was socketed
//-----------------------------------------------------------------------------
void CHL2_Player::CombineBallSocketed( CPropCombineBall *pCombineBall )
{
#ifdef HL2_EPISODIC
CNPC_Alyx *pAlyx = CNPC_Alyx::GetAlyx();
if ( pAlyx )
{
pAlyx->CombineBallSocketed( pCombineBall->NumBounces() );
}
#endif
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHL2_Player::Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info )
{
BaseClass::Event_KilledOther( pVictim, info );
#ifdef HL2_EPISODIC
CNPC_Alyx *pAlyx = CNPC_Alyx::GetAlyx();
if ( pAlyx )
{
pAlyx->PlayerKilledOther( pVictim, info );
}
#endif
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHL2_Player::Event_Killed( const CTakeDamageInfo &info )
{
BaseClass::Event_Killed( info );
NotifyScriptsOfDeath();
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHL2_Player::NotifyScriptsOfDeath( void )
{
CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "scripted_sequence" );
while( pEnt )
{
variant_t emptyVariant;
pEnt->AcceptInput( "ScriptPlayerDeath", NULL, NULL, emptyVariant, 0 );
pEnt = gEntList.FindEntityByClassname( pEnt, "scripted_sequence" );
}
pEnt = gEntList.FindEntityByClassname( NULL, "logic_choreographed_scene" );
while( pEnt )
{
variant_t emptyVariant;
pEnt->AcceptInput( "ScriptPlayerDeath", NULL, NULL, emptyVariant, 0 );
pEnt = gEntList.FindEntityByClassname( pEnt, "logic_choreographed_scene" );
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHL2_Player::GetAutoaimVector( autoaim_params_t ¶ms )
{
BaseClass::GetAutoaimVector( params );
if ( IsXbox() )
{
if( IsInAVehicle() )
{
if( m_hLockedAutoAimEntity && m_hLockedAutoAimEntity->IsAlive() && ShouldKeepLockedAutoaimTarget(m_hLockedAutoAimEntity) )
{
if( params.m_hAutoAimEntity && params.m_hAutoAimEntity != m_hLockedAutoAimEntity )
{
// Autoaim has picked a new target. Switch.
m_hLockedAutoAimEntity = params.m_hAutoAimEntity;
}
// Ignore autoaim and just keep aiming at this target.
params.m_hAutoAimEntity = m_hLockedAutoAimEntity;
Vector vecTarget = m_hLockedAutoAimEntity->BodyTarget( EyePosition(), false );
Vector vecDir = vecTarget - EyePosition();
VectorNormalize( vecDir );
params.m_vecAutoAimDir = vecDir;
params.m_vecAutoAimPoint = vecTarget;
return;
}
else
{
m_hLockedAutoAimEntity = NULL;
}
}
// If the player manually gets his crosshair onto a target, make that target sticky
if( params.m_fScale != AUTOAIM_SCALE_DIRECT_ONLY )
{
// Only affect this for 'real' queries
//if( params.m_hAutoAimEntity && params.m_bOnTargetNatural )
if( params.m_hAutoAimEntity )
{
// Turn on sticky.
m_HL2Local.m_bStickyAutoAim = true;
if( IsInAVehicle() )
{
m_hLockedAutoAimEntity = params.m_hAutoAimEntity;
}
}
else if( !params.m_hAutoAimEntity )
{
// Turn off sticky only if there's no target at all.
m_HL2Local.m_bStickyAutoAim = false;
m_hLockedAutoAimEntity = NULL;
}
}
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CHL2_Player::ShouldKeepLockedAutoaimTarget( EHANDLE hLockedTarget )
{
Vector vecLooking;
Vector vecToTarget;
vecToTarget = hLockedTarget->WorldSpaceCenter() - EyePosition();
float flDist = vecToTarget.Length2D();
VectorNormalize( vecToTarget );
if( flDist > autoaim_max_dist.GetFloat() )
return false;
float flDot;
vecLooking = EyeDirection3D();
flDot = DotProduct( vecLooking, vecToTarget );
if( flDot < autoaim_unlock_target.GetFloat() )
return false;
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : iCount -
// iAmmoIndex -
// bSuppressSound -
// Output : int
//-----------------------------------------------------------------------------
int CHL2_Player::GiveAmmo( int nCount, int nAmmoIndex, bool bSuppressSound)
{
// Don't try to give the player invalid ammo indices.
if (nAmmoIndex < 0)
return 0;
bool bCheckAutoSwitch = false;
if (!HasAnyAmmoOfType(nAmmoIndex))
{
bCheckAutoSwitch = true;
}
int nAdd = BaseClass::GiveAmmo(nCount, nAmmoIndex, bSuppressSound);
if ( nCount > 0 && nAdd == 0 )
{
// we've been denied the pickup, display a hud icon to show that
CSingleUserRecipientFilter user( this );
user.MakeReliable();
UserMessageBegin( user, "AmmoDenied" );
WRITE_SHORT( nAmmoIndex );
MessageEnd();
}
//
// If I was dry on ammo for my best weapon and justed picked up ammo for it,
// autoswitch to my best weapon now.
//
if (bCheckAutoSwitch)
{
CBaseCombatWeapon *pWeapon = g_pGameRules->GetNextBestWeapon(this, GetActiveWeapon());
if ( pWeapon && pWeapon->GetPrimaryAmmoType() == nAmmoIndex )
{
SwitchToNextBestWeapon(GetActiveWeapon());
}
}
return nAdd;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CHL2_Player::Weapon_CanUse( CBaseCombatWeapon *pWeapon )
{
#ifndef HL2MP
if ( pWeapon->ClassMatches( "weapon_stunstick" ) )
{
if ( ApplyBattery( 0.5 ) )
UTIL_Remove( pWeapon );
return false;
}
#endif
return BaseClass::Weapon_CanUse( pWeapon );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pWeapon -
//-----------------------------------------------------------------------------
void CHL2_Player::Weapon_Equip( CBaseCombatWeapon *pWeapon )
{
#if HL2_SINGLE_PRIMARY_WEAPON_MODE
if ( pWeapon->GetSlot() == WEAPON_PRIMARY_SLOT )
{
Weapon_DropSlot( WEAPON_PRIMARY_SLOT );
}
#endif
BaseClass::Weapon_Equip( pWeapon );
}
//-----------------------------------------------------------------------------
// Purpose: Player reacts to bumping a weapon.
// Input : pWeapon - the weapon that the player bumped into.
// Output : Returns true if player picked up the weapon
//-----------------------------------------------------------------------------
bool CHL2_Player::BumpWeapon( CBaseCombatWeapon *pWeapon )
{
#if HL2_SINGLE_PRIMARY_WEAPON_MODE
CBaseCombatCharacter *pOwner = pWeapon->GetOwner();
// Can I have this weapon type?
if ( pOwner || !Weapon_CanUse( pWeapon ) || !g_pGameRules->CanHavePlayerItem( this, pWeapon ) )
{
if ( gEvilImpulse101 )
{
UTIL_Remove( pWeapon );
}
return false;
}
// ----------------------------------------
// If I already have it just take the ammo
// ----------------------------------------
if (Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType()))
{
//Only remove the weapon if we attained ammo from it
if ( Weapon_EquipAmmoOnly( pWeapon ) == false )
return false;
// Only remove me if I have no ammo left
// Can't just check HasAnyAmmo because if I don't use clips, I want to be removed,
if ( pWeapon->UsesClipsForAmmo1() && pWeapon->HasPrimaryAmmo() )
return false;
UTIL_Remove( pWeapon );
return false;
}
// -------------------------
// Otherwise take the weapon
// -------------------------
else
{
//Make sure we're not trying to take a new weapon type we already have
if ( Weapon_SlotOccupied( pWeapon ) )
{
CBaseCombatWeapon *pActiveWeapon = Weapon_GetSlot( WEAPON_PRIMARY_SLOT );
if ( pActiveWeapon != NULL && pActiveWeapon->HasAnyAmmo() == false && Weapon_CanSwitchTo( pWeapon ) )
{
Weapon_Equip( pWeapon );
return true;
}
//Attempt to take ammo if this is the gun we're holding already
if ( Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType() ) )
{
Weapon_EquipAmmoOnly( pWeapon );
}
return false;
}
pWeapon->CheckRespawn();
pWeapon->AddSolidFlags( FSOLID_NOT_SOLID );
pWeapon->AddEffects( EF_NODRAW );
Weapon_Equip( pWeapon );
EmitSound( "HL2Player.PickupWeapon" );
return true;
}
#else
return BaseClass::BumpWeapon( pWeapon );
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *cmd -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CHL2_Player::ClientCommand(const char *cmd)
{
#if HL2_SINGLE_PRIMARY_WEAPON_MODE
//Drop primary weapon
if( stricmp( cmd, "DropPrimary" ) == 0 )
{
Weapon_DropSlot( WEAPON_PRIMARY_SLOT );
return true;
}
#endif
if ( !stricmp( cmd, "emit" ) )
{
CSingleUserRecipientFilter filter( this );
if ( engine->Cmd_Argc() > 1 )
{
EmitSound( filter, entindex(), engine->Cmd_Argv( 1 ) );
}
else
{
EmitSound( filter, entindex(), "Test.Sound" );
}
return true;
}
return BaseClass::ClientCommand( cmd );
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : void CBasePlayer::PlayerUse
//-----------------------------------------------------------------------------
void CHL2_Player::PlayerUse ( void )
{
// Was use pressed or released?
if ( ! ((m_nButtons | m_afButtonPressed | m_afButtonReleased) & IN_USE) )
return;
if ( m_afButtonPressed & IN_USE )
{
// Currently using a latched entity?
if ( ClearUseEntity() )
{
return;
}
else
{
if ( m_afPhysicsFlags & PFLAG_DIROVERRIDE )
{
m_afPhysicsFlags &= ~PFLAG_DIROVERRIDE;
m_iTrain = TRAIN_NEW|TRAIN_OFF;
return;
}
else
{ // Start controlling the train!
CBaseEntity *pTrain = GetGroundEntity();
if ( pTrain && !(m_nButtons & IN_JUMP) && (GetFlags() & FL_ONGROUND) && (pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) && pTrain->OnControls(this) )
{
m_afPhysicsFlags |= PFLAG_DIROVERRIDE;
m_iTrain = TrainSpeed(pTrain->m_flSpeed, ((CFuncTrackTrain*)pTrain)->GetMaxSpeed());
m_iTrain |= TRAIN_NEW;
EmitSound( "HL2Player.TrainUse" );
return;
}
}
}
// Tracker 3926: We can't +USE something if we're climbing a ladder
if ( GetMoveType() == MOVETYPE_LADDER )
{
return;
}
}
if( m_flTimeUseSuspended > gpGlobals->curtime )
{
// Something has temporarily stopped us being able to USE things.
// Obviously, this should be used very carefully.(sjb)
return;
}
CBaseEntity *pUseEntity = FindUseEntity();
bool usedSomething = false;
// Found an object
if ( pUseEntity )
{
//!!!UNDONE: traceline here to prevent +USEing buttons through walls
int caps = pUseEntity->ObjectCaps();
variant_t emptyVariant;
if ( m_afButtonPressed & IN_USE )
{
// Robin: Don't play sounds for NPCs, because NPCs will allow respond with speech.
if ( !pUseEntity->MyNPCPointer() )
{
EmitSound( "HL2Player.Use" );
}
}
if ( ( (m_nButtons & IN_USE) && (caps & FCAP_CONTINUOUS_USE) ) ||
( (m_afButtonPressed & IN_USE) && (caps & (FCAP_IMPULSE_USE|FCAP_ONOFF_USE)) ) )
{
if ( caps & FCAP_CONTINUOUS_USE )
m_afPhysicsFlags |= PFLAG_USING;
pUseEntity->AcceptInput( "Use", this, this, emptyVariant, USE_TOGGLE );
usedSomething = true;
}
// UNDONE: Send different USE codes for ON/OFF. Cache last ONOFF_USE object to send 'off' if you turn away
else if ( (m_afButtonReleased & IN_USE) && (pUseEntity->ObjectCaps() & FCAP_ONOFF_USE) ) // BUGBUG This is an "off" use
{
pUseEntity->AcceptInput( "Use", this, this, emptyVariant, USE_TOGGLE );
usedSomething = true;
}
#if HL2_SINGLE_PRIMARY_WEAPON_MODE
//Check for weapon pick-up
if ( m_afButtonPressed & IN_USE )
{
CBaseCombatWeapon *pWeapon = dynamic_cast<CBaseCombatWeapon *>(pUseEntity);
if ( ( pWeapon != NULL ) && ( Weapon_CanSwitchTo( pWeapon ) ) )
{
//Try to take ammo or swap the weapon
if ( Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType() ) )
{
Weapon_EquipAmmoOnly( pWeapon );
}
else
{
Weapon_DropSlot( pWeapon->GetSlot() );
Weapon_Equip( pWeapon );
}
usedSomething = true;
}
}
#endif
}
else if ( m_afButtonPressed & IN_USE )
{
// Signal that we want to play the deny sound, unless the user is +USEing on a ladder!
// The sound is emitted in ItemPostFrame, since that occurs after GameMovement::ProcessMove which
// lets the ladder code unset this flag.
m_bPlayUseDenySound = true;
}
// Debounce the use key
if ( usedSomething && pUseEntity )
{
m_Local.m_nOldButtons |= IN_USE;
m_afButtonPressed &= ~IN_USE;
}
}
ConVar sv_show_crosshair_target( "sv_show_crosshair_target", "0" );
//-----------------------------------------------------------------------------
// Purpose: Updates the posture of the weapon from lowered to ready
//-----------------------------------------------------------------------------
void CHL2_Player::UpdateWeaponPosture( void )
{
CBaseHLCombatWeapon *pWeapon = dynamic_cast<CBaseHLCombatWeapon *>(GetActiveWeapon());
if ( pWeapon && m_LowerWeaponTimer.Expired() && pWeapon->CanLower() )
{
m_LowerWeaponTimer.Set( .3 );
VPROF( "CHL2_Player::UpdateWeaponPosture-CheckLower" );
Vector vecAim = BaseClass::GetAutoaimVector( AUTOAIM_SCALE_DIRECT_ONLY );
const float CHECK_FRIENDLY_RANGE = 50 * 12;
trace_t tr;
UTIL_TraceLine( EyePosition(), EyePosition() + vecAim * CHECK_FRIENDLY_RANGE, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
CBaseEntity *aimTarget = tr.m_pEnt;
//If we're over something
if ( aimTarget && !tr.DidHitWorld() )
{
if ( !aimTarget->IsNPC() || aimTarget->MyNPCPointer()->GetState() != NPC_STATE_COMBAT )
{
Disposition_t dis = IRelationType( aimTarget );
//Debug info for seeing what an object "cons" as
if ( sv_show_crosshair_target.GetBool() )
{
int text_offset = BaseClass::DrawDebugTextOverlays();
char tempstr[255];
switch ( dis )
{
case D_LI:
Q_snprintf( tempstr, sizeof(tempstr), "Disposition: Like" );
break;
case D_HT:
Q_snprintf( tempstr, sizeof(tempstr), "Disposition: Hate" );
break;
case D_FR:
Q_snprintf( tempstr, sizeof(tempstr), "Disposition: Fear" );
break;
case D_NU:
Q_snprintf( tempstr, sizeof(tempstr), "Disposition: Neutral" );
break;
default:
case D_ER:
Q_snprintf( tempstr, sizeof(tempstr), "Disposition: !!!ERROR!!!" );
break;
}
//Draw the text
NDebugOverlay::EntityText( aimTarget->entindex(), text_offset, tempstr, 0 );
}
//See if we hates it
if ( dis == D_LI )
{
//We're over a friendly, drop our weapon
if ( Weapon_Lower() == false )
{
//FIXME: We couldn't lower our weapon!
}
return;
}
}
}
if ( Weapon_Ready() == false )
{
//FIXME: We couldn't raise our weapon!
}
}
if( hl2_xbox_aiming.GetBool() )
{
if( !pWeapon || !m_AutoaimTimer.Expired() )
{
return;
}
m_AutoaimTimer.Set( .1 );
VPROF( "hl2_xbox_aiming" );
// Call the autoaim code to update the local player data, which allows the client to update.
autoaim_params_t params;
params.m_fScale = AUTOAIM_SCALE_DEFAULT;
params.m_fMaxDist = autoaim_max_dist.GetFloat();
GetAutoaimVector( params );
m_HL2Local.m_hAutoAimTarget.Set( params.m_hAutoAimEntity );
m_HL2Local.m_vecAutoAimPoint.Set( params.m_vecAutoAimPoint );
return;
}
else
{
// Make sure there's no residual autoaim target if the user changes the xbox_aiming convar on the fly.
m_HL2Local.m_hAutoAimTarget.Set(NULL);
}
}
//-----------------------------------------------------------------------------
// Purpose: Lowers the weapon posture (for hovering over friendlies)
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CHL2_Player::Weapon_Lower( void )
{
VPROF( "CHL2_Player::Weapon_Lower" );
// Already lowered?
if ( m_HL2Local.m_bWeaponLowered )
return true;
m_HL2Local.m_bWeaponLowered = true;
CBaseHLCombatWeapon *pWeapon = dynamic_cast<CBaseHLCombatWeapon *>(GetActiveWeapon());
if ( pWeapon == NULL )
return false;
return pWeapon->Lower();
}
//-----------------------------------------------------------------------------
// Purpose: Returns the weapon posture to normal
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CHL2_Player::Weapon_Ready( void )
{
VPROF( "CHL2_Player::Weapon_Ready" );
// Already ready?
if ( m_HL2Local.m_bWeaponLowered == false )
return true;
m_HL2Local.m_bWeaponLowered = false;
CBaseHLCombatWeapon *pWeapon = dynamic_cast<CBaseHLCombatWeapon *>(GetActiveWeapon());
if ( pWeapon == NULL )
return false;
return pWeapon->Ready();
}
//-----------------------------------------------------------------------------
// Purpose: Returns whether or not we can switch to the given weapon.
// Input : pWeapon -
//-----------------------------------------------------------------------------
bool CHL2_Player::Weapon_CanSwitchTo( CBaseCombatWeapon *pWeapon )
{
CBasePlayer *pPlayer = (CBasePlayer *)this;
#if !defined( CLIENT_DLL )
IServerVehicle *pVehicle = pPlayer->GetVehicle();
#else
IClientVehicle *pVehicle = pPlayer->GetVehicle();
#endif
if (pVehicle && !pPlayer->UsingStandardWeaponsInVehicle())
return false;
if ( !pWeapon->HasAnyAmmo() && !GetAmmoCount( pWeapon->m_iPrimaryAmmoType ) )
return false;
if ( !pWeapon->CanDeploy() )
return false;
if ( GetActiveWeapon() )
{
if ( PhysCannonGetHeldEntity( GetActiveWeapon() ) == pWeapon &&
Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType()) )
{
return true;
}
if ( !GetActiveWeapon()->CanHolster() )
return false;
}
return true;
}
void CHL2_Player::PickupObject( CBaseEntity *pObject, bool bLimitMassAndSize )
{
// can't pick up what you're standing on
if ( GetGroundEntity() == pObject )
return;
if ( bLimitMassAndSize == true )
{
if ( CBasePlayer::CanPickupObject( pObject, 35, 128 ) == false )
return;
}
// Can't be picked up if NPCs are on me
if ( pObject->HasNPCsOnIt() )
return;
PlayerPickupObject( this, pObject );
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : CBaseEntity
//-----------------------------------------------------------------------------
bool CHL2_Player::IsHoldingEntity( CBaseEntity *pEnt )
{
return PlayerPickupControllerIsHoldingEntity( m_hUseEntity, pEnt );
}
float CHL2_Player::GetHeldObjectMass( IPhysicsObject *pHeldObject )
{
float mass = PlayerPickupGetHeldObjectMass( m_hUseEntity, pHeldObject );
if ( mass == 0.0f )
{
mass = PhysCannonGetHeldObjectMass( GetActiveWeapon(), pHeldObject );
}
return mass;
}
//-----------------------------------------------------------------------------
// Purpose: Force the player to drop any physics objects he's carrying
//-----------------------------------------------------------------------------
void CHL2_Player::ForceDropOfCarriedPhysObjects( CBaseEntity *pOnlyIfHoldingThis )
{
if ( PhysIsInCallback() )
{
variant_t value;
g_EventQueue.AddEvent( this, "ForceDropPhysObjects", value, 0.01f, pOnlyIfHoldingThis, this );
return;
}
// Drop any objects being handheld.
ClearUseEntity();
// Then force the physcannon to drop anything it's holding, if it's our active weapon
PhysCannonForceDrop( GetActiveWeapon(), pOnlyIfHoldingThis );
}
void CHL2_Player::InputForceDropPhysObjects( inputdata_t &data )
{
ForceDropOfCarriedPhysObjects( data.pActivator );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CHL2_Player::UpdateClientData( void )
{
if (m_DmgTake || m_DmgSave || m_bitsHUDDamage != m_bitsDamageType)
{
// Comes from inside me if not set
Vector damageOrigin = GetLocalOrigin();
// send "damage" message
// causes screen to flash, and pain compass to show direction of damage
damageOrigin = m_DmgOrigin;
// only send down damage type that have hud art
int visibleDamageBits = m_bitsDamageType & DMG_SHOWNHUD;
m_DmgTake = clamp( m_DmgTake, 0, 255 );
m_DmgSave = clamp( m_DmgSave, 0, 255 );
// If we're poisoned, but it wasn't this frame, don't send the indicator
// Without this check, any damage that occured to the player while they were
// recovering from a poison bite would register as poisonous as well and flash
// the whole screen! -- jdw
if ( visibleDamageBits & DMG_POISON )
{
float flLastPoisonedDelta = gpGlobals->curtime - m_tbdPrev;
if ( flLastPoisonedDelta > 0.1f )
{
visibleDamageBits &= ~DMG_POISON;
}
}
CSingleUserRecipientFilter user( this );
user.MakeReliable();
UserMessageBegin( user, "Damage" );
WRITE_BYTE( m_DmgSave );
WRITE_BYTE( m_DmgTake );
WRITE_LONG( visibleDamageBits );
WRITE_FLOAT( damageOrigin.x ); //BUG: Should be fixed point (to hud) not floats
WRITE_FLOAT( damageOrigin.y ); //BUG: However, the HUD does _not_ implement bitfield messages (yet)
WRITE_FLOAT( damageOrigin.z ); //BUG: We use WRITE_VEC3COORD for everything else
MessageEnd();
m_DmgTake = 0;
m_DmgSave = 0;
m_bitsHUDDamage = m_bitsDamageType;
// Clear off non-time-based damage indicators
m_bitsDamageType &= DMG_TIMEBASED;
}
BaseClass::UpdateClientData();
}
//---------------------------------------------------------
//---------------------------------------------------------
void CHL2_Player::OnRestore()
{
BaseClass::OnRestore();
m_pPlayerAISquad = g_AI_SquadManager.FindCreateSquad(AllocPooledString(PLAYER_SQUADNAME));
}
//---------------------------------------------------------
//---------------------------------------------------------
Vector CHL2_Player::EyeDirection2D( void )
{
Vector vecReturn = EyeDirection3D();
vecReturn.z = 0;
vecReturn.AsVector2D().NormalizeInPlace();
return vecReturn;
}
//---------------------------------------------------------
//---------------------------------------------------------
Vector CHL2_Player::EyeDirection3D( void )
{
Vector vecForward;
IServerVehicle *pVehicle = GetVehicle();
if ( pVehicle )
{
Assert( pVehicle );
int nRole = pVehicle->GetPassengerRole( this );
Vector vecEyeOrigin;
QAngle angEyeAngles;
pVehicle->GetVehicleViewPosition( nRole, &vecEyeOrigin, &angEyeAngles );
AngleVectors( angEyeAngles, &vecForward );
}
else
{
AngleVectors( EyeAngles(), &vecForward );
}
return vecForward;
}
//---------------------------------------------------------
//---------------------------------------------------------
bool CHL2_Player::Weapon_Switch( CBaseCombatWeapon *pWeapon, int viewmodelindex )
{
// Recalculate proficiency!
SetCurrentWeaponProficiency( CalcWeaponProficiency( pWeapon ) );
// Come out of suit zoom mode
if ( IsZooming() )
{
StopZooming();
}
return BaseClass::Weapon_Switch( pWeapon, viewmodelindex );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
WeaponProficiency_t CHL2_Player::CalcWeaponProficiency( CBaseCombatWeapon *pWeapon )
{
WeaponProficiency_t proficiency;
proficiency = WEAPON_PROFICIENCY_PERFECT;
if( weapon_showproficiency.GetBool() != 0 )
{
Msg("Player switched to %s, proficiency is %s\n", pWeapon->GetClassname(), GetWeaponProficiencyName( proficiency ) );
}
return proficiency;
}
//-----------------------------------------------------------------------------
// Purpose: override how single player rays hit the player
//-----------------------------------------------------------------------------
bool LineCircleIntersection(
const Vector2D ¢er,
const float radius,
const Vector2D &vLinePt,
const Vector2D &vLineDir,
float *fIntersection1,
float *fIntersection2)
{
// Line = P + Vt
// Sphere = r (assume we've translated to origin)
// (P + Vt)^2 = r^2
// VVt^2 + 2PVt + (PP - r^2)
// Solve as quadratic: (-b +/- sqrt(b^2 - 4ac)) / 2a
// If (b^2 - 4ac) is < 0 there is no solution.
// If (b^2 - 4ac) is = 0 there is one solution (a case this function doesn't support).
// If (b^2 - 4ac) is > 0 there are two solutions.
Vector2D P;
float a, b, c, sqr, insideSqr;
// Translate circle to origin.
P[0] = vLinePt[0] - center[0];
P[1] = vLinePt[1] - center[1];
a = vLineDir.Dot(vLineDir);
b = 2.0f * P.Dot(vLineDir);
c = P.Dot(P) - (radius * radius);
insideSqr = b*b - 4*a*c;
if(insideSqr <= 0.000001f)
return false;
// Ok, two solutions.
sqr = (float)FastSqrt(insideSqr);
float denom = 1.0 / (2.0f * a);
*fIntersection1 = (-b - sqr) * denom;
*fIntersection2 = (-b + sqr) * denom;
return true;
}
static void Collision_ClearTrace( const Vector &vecRayStart, const Vector &vecRayDelta, CBaseTrace *pTrace )
{
pTrace->startpos = vecRayStart;
pTrace->endpos = vecRayStart;
pTrace->endpos += vecRayDelta;
pTrace->startsolid = false;
pTrace->allsolid = false;
pTrace->fraction = 1.0f;
pTrace->contents = 0;
}
bool IntersectRayWithAACylinder( const Ray_t &ray,
const Vector ¢er, float radius, float height, CBaseTrace *pTrace )
{
Assert( ray.m_IsRay );
Collision_ClearTrace( ray.m_Start, ray.m_Delta, pTrace );
// First intersect the ray with the top + bottom planes
float halfHeight = height * 0.5;
// Handle parallel case
Vector vStart = ray.m_Start - center;
Vector vEnd = vStart + ray.m_Delta;
float flEnterFrac, flLeaveFrac;
if (FloatMakePositive(ray.m_Delta.z) < 1e-8)
{
if ( (vStart.z < -halfHeight) || (vStart.z > halfHeight) )
{
return false; // no hit
}
flEnterFrac = 0.0f; flLeaveFrac = 1.0f;
}
else
{
// Clip the ray to the top and bottom of box
flEnterFrac = IntersectRayWithAAPlane( vStart, vEnd, 2, 1, halfHeight);
flLeaveFrac = IntersectRayWithAAPlane( vStart, vEnd, 2, 1, -halfHeight);
if ( flLeaveFrac < flEnterFrac )
{
float temp = flLeaveFrac;
flLeaveFrac = flEnterFrac;
flEnterFrac = temp;
}
if ( flLeaveFrac < 0 || flEnterFrac > 1)
{
return false;
}
}
// Intersect with circle
float flCircleEnterFrac, flCircleLeaveFrac;
if ( !LineCircleIntersection( vec3_origin.AsVector2D(), radius,
vStart.AsVector2D(), ray.m_Delta.AsVector2D(), &flCircleEnterFrac, &flCircleLeaveFrac ) )
{
return false; // no hit
}
Assert( flCircleEnterFrac <= flCircleLeaveFrac );
if ( flCircleLeaveFrac < 0 || flCircleEnterFrac > 1)
{
return false;
}
if ( flEnterFrac < flCircleEnterFrac )
flEnterFrac = flCircleEnterFrac;
if ( flLeaveFrac > flCircleLeaveFrac )
flLeaveFrac = flCircleLeaveFrac;
if ( flLeaveFrac < flEnterFrac )
return false;
VectorMA( ray.m_Start, flEnterFrac , ray.m_Delta, pTrace->endpos );
pTrace->fraction = flEnterFrac;
pTrace->contents = CONTENTS_SOLID;
// Calculate the point on our center line where we're nearest the intersection point
Vector collisionCenter;
CalcClosestPointOnLineSegment( pTrace->endpos, center + Vector( 0, 0, halfHeight ), center - Vector( 0, 0, halfHeight ), collisionCenter );
// Our normal is the direction from that center point to the intersection point
pTrace->plane.normal = pTrace->endpos - collisionCenter;
VectorNormalize( pTrace->plane.normal );
return true;
}
bool CHL2_Player::TestHitboxes( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr )
{
if( g_pGameRules->IsMultiplayer() )
{
return BaseClass::TestHitboxes( ray, fContentsMask, tr );
}
else
{
Assert( ray.m_IsRay );
Vector mins, maxs;
mins = WorldAlignMins();
maxs = WorldAlignMaxs();
if ( IntersectRayWithAACylinder( ray, WorldSpaceCenter(), maxs.x * PLAYER_HULL_REDUCTION, maxs.z - mins.z, &tr ) )
{
CStudioHdr *pStudioHdr = GetModelPtr( );
if (!pStudioHdr)
return false;
mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( m_nHitboxSet );
if ( !set || !set->numhitboxes )
return false;
mstudiobbox_t *pbox = set->pHitbox( tr.hitbox );
mstudiobone_t *pBone = pStudioHdr->pBone(pbox->bone);
tr.surface.name = "**studio**";
tr.surface.flags = SURF_HITBOX;
tr.surface.surfaceProps = physprops->GetSurfaceIndex( pBone->pszSurfaceProp() );
}
return true;
}
}
//---------------------------------------------------------
// Show the player's scaled down bbox that we use for
// bullet impacts.
//---------------------------------------------------------
void CHL2_Player::DrawDebugGeometryOverlays(void)
{
BaseClass::DrawDebugGeometryOverlays();
if (m_debugOverlays & OVERLAY_BBOX_BIT)
{
Vector mins, maxs;
mins = WorldAlignMins();
maxs = WorldAlignMaxs();
mins.x *= PLAYER_HULL_REDUCTION;
mins.y *= PLAYER_HULL_REDUCTION;
maxs.x *= PLAYER_HULL_REDUCTION;
maxs.y *= PLAYER_HULL_REDUCTION;
NDebugOverlay::Box( GetAbsOrigin(), mins, maxs, 255, 0, 0, 100, 0 );
}
}
//-----------------------------------------------------------------------------
// Purpose: Helper to remove from ladder
//-----------------------------------------------------------------------------
void CHL2_Player::ExitLadder()
{
if ( MOVETYPE_LADDER != GetMoveType() )
return;
SetMoveType( MOVETYPE_WALK );
SetMoveCollide( MOVECOLLIDE_DEFAULT );
// Remove from ladder
m_HL2Local.m_hLadder.Set( NULL );
}
//-----------------------------------------------------------------------------
// Purpose: Queues up a use deny sound, played in ItemPostFrame.
//-----------------------------------------------------------------------------
void CHL2_Player::PlayUseDenySound()
{
m_bPlayUseDenySound = true;
}
void CHL2_Player::ItemPostFrame()
{
BaseClass::ItemPostFrame();
if ( m_bPlayUseDenySound )
{
m_bPlayUseDenySound = false;
EmitSound( "HL2Player.UseDeny" );
}
}
void CHL2_Player::StartWaterDeathSounds( void )
{
CPASAttenuationFilter filter( this );
if ( m_sndLeeches == NULL )
{
m_sndLeeches = (CSoundEnvelopeController::GetController()).SoundCreate( filter, entindex(), CHAN_STATIC, "coast.leech_bites_loop" , ATTN_NORM );
}
if ( m_sndLeeches )
{
(CSoundEnvelopeController::GetController()).Play( m_sndLeeches, 1.0f, 100 );
}
if ( m_sndWaterSplashes == NULL )
{
m_sndWaterSplashes = (CSoundEnvelopeController::GetController()).SoundCreate( filter, entindex(), CHAN_STATIC, "coast.leech_water_churn_loop" , ATTN_NORM );
}
if ( m_sndWaterSplashes )
{
(CSoundEnvelopeController::GetController()).Play( m_sndWaterSplashes, 1.0f, 100 );
}
}
void CHL2_Player::StopWaterDeathSounds( void )
{
if ( m_sndLeeches )
{
(CSoundEnvelopeController::GetController()).SoundFadeOut( m_sndLeeches, 0.5f, true );
m_sndLeeches = NULL;
}
if ( m_sndWaterSplashes )
{
(CSoundEnvelopeController::GetController()).SoundFadeOut( m_sndWaterSplashes, 0.5f, true );
m_sndWaterSplashes = NULL;
}
}
//-----------------------------------------------------------------------------
// Shuts down sounds
//-----------------------------------------------------------------------------
void CHL2_Player::StopLoopingSounds( void )
{
if ( m_sndLeeches != NULL )
{
(CSoundEnvelopeController::GetController()).SoundDestroy( m_sndLeeches );
m_sndLeeches = NULL;
}
if ( m_sndWaterSplashes != NULL )
{
(CSoundEnvelopeController::GetController()).SoundDestroy( m_sndWaterSplashes );
m_sndWaterSplashes = NULL;
}
BaseClass::StopLoopingSounds();
}
//-----------------------------------------------------------------------------
void CHL2_Player::ModifyOrAppendPlayerCriteria( AI_CriteriaSet& set )
{
BaseClass::ModifyOrAppendPlayerCriteria( set );
if ( GlobalEntity_GetIndex( "gordon_precriminal" ) == -1 )
{
set.AppendCriteria( "gordon_precriminal", "0" );
}
}
CLogicPlayerProxy *CHL2_Player::GetPlayerProxy( void )
{
CLogicPlayerProxy *pProxy = dynamic_cast< CLogicPlayerProxy* > ( m_hPlayerProxy.Get() );
if ( pProxy == NULL )
{
pProxy = (CLogicPlayerProxy*)gEntList.FindEntityByClassname(NULL, "logic_playerproxy" );
if ( pProxy == NULL )
return NULL;
pProxy->m_hPlayer = this;
m_hPlayerProxy = pProxy;
}
return pProxy;
}
void CHL2_Player::FirePlayerProxyOutput( const char *pszOutputName, variant_t variant, CBaseEntity *pActivator, CBaseEntity *pCaller )
{
if ( GetPlayerProxy() == NULL )
return;
GetPlayerProxy()->FireNamedOutput( pszOutputName, variant, pActivator, pCaller );
}
LINK_ENTITY_TO_CLASS( logic_playerproxy, CLogicPlayerProxy);
BEGIN_DATADESC( CLogicPlayerProxy )
DEFINE_OUTPUT( m_OnFlashlightOn, "OnFlashlightOn" ),
DEFINE_OUTPUT( m_OnFlashlightOff, "OnFlashlightOff" ),
DEFINE_OUTPUT( m_RequestedPlayerHealth, "PlayerHealth" ),
DEFINE_OUTPUT( m_PlayerHasAmmo, "PlayerHasAmmo" ),
DEFINE_OUTPUT( m_PlayerHasNoAmmo, "PlayerHasNoAmmo" ),
DEFINE_INPUTFUNC( FIELD_VOID, "RequestPlayerHealth", InputRequestPlayerHealth ),
DEFINE_INPUTFUNC( FIELD_VOID, "SetFlashlightSlowDrain", InputSetFlashlightSlowDrain ),
DEFINE_INPUTFUNC( FIELD_VOID, "SetFlashlightNormalDrain", InputSetFlashlightNormalDrain ),
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetPlayerHealth", InputSetPlayerHealth ),
DEFINE_INPUTFUNC( FIELD_VOID, "RequestAmmoState", InputRequestAmmoState ),
DEFINE_FIELD( m_hPlayer, FIELD_EHANDLE ),
END_DATADESC()
void CLogicPlayerProxy::Activate( void )
{
BaseClass::Activate();
if ( m_hPlayer == NULL )
{
m_hPlayer = AI_GetSinglePlayer();
}
}
void CLogicPlayerProxy::InputSetPlayerHealth( inputdata_t &inputdata )
{
if ( m_hPlayer == NULL )
return;
m_hPlayer->SetHealth( inputdata.value.Int() );
}
void CLogicPlayerProxy::InputRequestPlayerHealth( inputdata_t &inputdata )
{
if ( m_hPlayer == NULL )
return;
m_RequestedPlayerHealth.Set( m_hPlayer->GetHealth(), inputdata.pActivator, inputdata.pCaller );
}
void CLogicPlayerProxy::InputSetFlashlightSlowDrain( inputdata_t &inputdata )
{
if( m_hPlayer == NULL )
return;
CHL2_Player *pPlayer = dynamic_cast<CHL2_Player*>(m_hPlayer.Get());
if( pPlayer )
pPlayer->SetFlashlightPowerDrainScale( hl2_darkness_flashlight_factor.GetFloat() );
}
void CLogicPlayerProxy::InputSetFlashlightNormalDrain( inputdata_t &inputdata )
{
if( m_hPlayer == NULL )
return;
CHL2_Player *pPlayer = dynamic_cast<CHL2_Player*>(m_hPlayer.Get());
if( pPlayer )
pPlayer->SetFlashlightPowerDrainScale( 1.0f );
}
void CLogicPlayerProxy::InputRequestAmmoState( inputdata_t &inputdata )
{
if( m_hPlayer == NULL )
return;
CHL2_Player *pPlayer = dynamic_cast<CHL2_Player*>(m_hPlayer.Get());
for ( int i = 0 ; i < pPlayer->WeaponCount(); ++i )
{
CBaseCombatWeapon* pCheck = pPlayer->GetWeapon( i );
if ( pCheck )
{
if ( pCheck->HasAnyAmmo() && (pCheck->UsesPrimaryAmmo() || pCheck->UsesSecondaryAmmo()))
{
m_PlayerHasAmmo.FireOutput( this, this, 0 );
return;
}
}
}
m_PlayerHasNoAmmo.FireOutput( this, this, 0 );
}