Permalink
Find file Copy path
53e78c5 May 15, 2014
2 contributors

Users who have contributed to this file

@JoeLudwig @jorgenpt
9364 lines (7745 sloc) 247 KB
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Functions dealing with the player.
//
//===========================================================================//
#include "cbase.h"
#include "const.h"
#include "baseplayer_shared.h"
#include "trains.h"
#include "soundent.h"
#include "gib.h"
#include "shake.h"
#include "decals.h"
#include "gamerules.h"
#include "game.h"
#include "entityapi.h"
#include "entitylist.h"
#include "eventqueue.h"
#include "worldsize.h"
#include "isaverestore.h"
#include "globalstate.h"
#include "basecombatweapon.h"
#include "ai_basenpc.h"
#include "ai_network.h"
#include "ai_node.h"
#include "ai_networkmanager.h"
#include "ammodef.h"
#include "mathlib/mathlib.h"
#include "ndebugoverlay.h"
#include "baseviewmodel.h"
#include "in_buttons.h"
#include "client.h"
#include "team.h"
#include "particle_smokegrenade.h"
#include "IEffects.h"
#include "vstdlib/random.h"
#include "engine/IEngineSound.h"
#include "movehelper_server.h"
#include "igamemovement.h"
#include "saverestoretypes.h"
#include "iservervehicle.h"
#include "movevars_shared.h"
#include "vcollide_parse.h"
#include "player_command.h"
#include "vehicle_base.h"
#include "AI_Criteria.h"
#include "globals.h"
#include "usermessages.h"
#include "gamevars_shared.h"
#include "world.h"
#include "physobj.h"
#include "KeyValues.h"
#include "coordsize.h"
#include "vphysics/player_controller.h"
#include "saverestore_utlvector.h"
#include "hltvdirector.h"
#include "nav_mesh.h"
#include "env_zoom.h"
#include "rumble_shared.h"
#include "gamestats.h"
#include "npcevent.h"
#include "datacache/imdlcache.h"
#include "hintsystem.h"
#include "env_debughistory.h"
#include "fogcontroller.h"
#include "gameinterface.h"
#include "hl2orange.spa.h"
#include "dt_utlvector_send.h"
#include "vote_controller.h"
#include "ai_speech.h"
#if defined USES_ECON_ITEMS
#include "econ_wearable.h"
#endif
// NVNT haptic utils
#include "haptics/haptic_utils.h"
#ifdef HL2_DLL
#include "combine_mine.h"
#include "weapon_physcannon.h"
#endif
ConVar autoaim_max_dist( "autoaim_max_dist", "2160" ); // 2160 = 180 feet
ConVar autoaim_max_deflect( "autoaim_max_deflect", "0.99" );
#ifdef CSTRIKE_DLL
ConVar spec_freeze_time( "spec_freeze_time", "5.0", FCVAR_CHEAT | FCVAR_REPLICATED, "Time spend frozen in observer freeze cam." );
ConVar spec_freeze_traveltime( "spec_freeze_traveltime", "0.7", FCVAR_CHEAT | FCVAR_REPLICATED, "Time taken to zoom in to frame a target in observer freeze cam.", true, 0.01, false, 0 );
#else
ConVar spec_freeze_time( "spec_freeze_time", "4.0", FCVAR_CHEAT | FCVAR_REPLICATED, "Time spend frozen in observer freeze cam." );
ConVar spec_freeze_traveltime( "spec_freeze_traveltime", "0.4", FCVAR_CHEAT | FCVAR_REPLICATED, "Time taken to zoom in to frame a target in observer freeze cam.", true, 0.01, false, 0 );
#endif
ConVar sv_bonus_challenge( "sv_bonus_challenge", "0", FCVAR_REPLICATED, "Set to values other than 0 to select a bonus map challenge type." );
static ConVar sv_maxusrcmdprocessticks( "sv_maxusrcmdprocessticks", "24", FCVAR_NOTIFY, "Maximum number of client-issued usrcmd ticks that can be replayed in packet loss conditions, 0 to allow no restrictions" );
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
static ConVar old_armor( "player_old_armor", "0" );
static ConVar physicsshadowupdate_render( "physicsshadowupdate_render", "0" );
bool IsInCommentaryMode( void );
bool IsListeningToCommentary( void );
#if !defined( CSTRIKE_DLL )
ConVar cl_sidespeed( "cl_sidespeed", "450", FCVAR_REPLICATED | FCVAR_CHEAT );
ConVar cl_upspeed( "cl_upspeed", "320", FCVAR_REPLICATED | FCVAR_CHEAT );
ConVar cl_forwardspeed( "cl_forwardspeed", "450", FCVAR_REPLICATED | FCVAR_CHEAT );
ConVar cl_backspeed( "cl_backspeed", "450", FCVAR_REPLICATED | FCVAR_CHEAT );
#endif // CSTRIKE_DLL
// This is declared in the engine, too
ConVar sv_noclipduringpause( "sv_noclipduringpause", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "If cheats are enabled, then you can noclip with the game paused (for doing screenshots, etc.)." );
extern ConVar sv_maxunlag;
extern ConVar sv_turbophysics;
extern ConVar *sv_maxreplay;
extern CServerGameDLL g_ServerGameDLL;
// TIME BASED DAMAGE AMOUNT
// tweak these values based on gameplay feedback:
#define PARALYZE_DURATION 2 // number of 2 second intervals to take damage
#define PARALYZE_DAMAGE 1.0 // damage to take each 2 second interval
#define NERVEGAS_DURATION 2
#define NERVEGAS_DAMAGE 5.0
#define POISON_DURATION 5
#define POISON_DAMAGE 2.0
#define RADIATION_DURATION 2
#define RADIATION_DAMAGE 1.0
#define ACID_DURATION 2
#define ACID_DAMAGE 5.0
#define SLOWBURN_DURATION 2
#define SLOWBURN_DAMAGE 1.0
#define SLOWFREEZE_DURATION 2
#define SLOWFREEZE_DAMAGE 1.0
//----------------------------------------------------
// Player Physics Shadow
//----------------------------------------------------
#define VPHYS_MAX_DISTANCE 2.0
#define VPHYS_MAX_VEL 10
#define VPHYS_MAX_DISTSQR (VPHYS_MAX_DISTANCE*VPHYS_MAX_DISTANCE)
#define VPHYS_MAX_VELSQR (VPHYS_MAX_VEL*VPHYS_MAX_VEL)
extern bool g_fDrawLines;
int gEvilImpulse101;
bool gInitHUD = true;
extern void respawn(CBaseEntity *pEdict, bool fCopyCorpse);
int MapTextureTypeStepType(char chTextureType);
extern void SpawnBlood(Vector vecSpot, const Vector &vecDir, int bloodColor, float flDamage);
extern void AddMultiDamage( const CTakeDamageInfo &info, CBaseEntity *pEntity );
#define CMD_MOSTRECENT 0
//#define FLASH_DRAIN_TIME 1.2 //100 units/3 minutes
//#define FLASH_CHARGE_TIME 0.2 // 100 units/20 seconds (seconds per unit)
//#define PLAYER_MAX_SAFE_FALL_DIST 20// falling any farther than this many feet will inflict damage
//#define PLAYER_FATAL_FALL_DIST 60// 100% damage inflicted if player falls this many feet
//#define DAMAGE_PER_UNIT_FALLEN (float)( 100 ) / ( ( PLAYER_FATAL_FALL_DIST - PLAYER_MAX_SAFE_FALL_DIST ) * 12 )
//#define MAX_SAFE_FALL_UNITS ( PLAYER_MAX_SAFE_FALL_DIST * 12 )
// player damage adjusters
ConVar sk_player_head( "sk_player_head","2" );
ConVar sk_player_chest( "sk_player_chest","1" );
ConVar sk_player_stomach( "sk_player_stomach","1" );
ConVar sk_player_arm( "sk_player_arm","1" );
ConVar sk_player_leg( "sk_player_leg","1" );
//ConVar player_usercommand_timeout( "player_usercommand_timeout", "10", 0, "After this many seconds without a usercommand from a player, the client is kicked." );
#ifdef _DEBUG
ConVar sv_player_net_suppress_usercommands( "sv_player_net_suppress_usercommands", "0", FCVAR_CHEAT, "For testing usercommand hacking sideeffects. DO NOT SHIP" );
#endif // _DEBUG
ConVar sv_player_display_usercommand_errors( "sv_player_display_usercommand_errors", "0", FCVAR_CHEAT, "1 = Display warning when command values are out-of-range. 2 = Spew invalid ranges." );
ConVar player_debug_print_damage( "player_debug_print_damage", "0", FCVAR_CHEAT, "When true, print amount and type of all damage received by player to console." );
void CC_GiveCurrentAmmo( void )
{
CBasePlayer *pPlayer = UTIL_PlayerByIndex(1);
if( pPlayer )
{
CBaseCombatWeapon *pWeapon = pPlayer->GetActiveWeapon();
if( pWeapon )
{
if( pWeapon->UsesPrimaryAmmo() )
{
int ammoIndex = pWeapon->GetPrimaryAmmoType();
if( ammoIndex != -1 )
{
int giveAmount;
giveAmount = GetAmmoDef()->MaxCarry(ammoIndex);
pPlayer->GiveAmmo( giveAmount, GetAmmoDef()->GetAmmoOfIndex(ammoIndex)->pName );
}
}
if( pWeapon->UsesSecondaryAmmo() && pWeapon->HasSecondaryAmmo() )
{
// Give secondary ammo out, as long as the player already has some
// from a presumeably natural source. This prevents players on XBox
// having Combine Balls and so forth in areas of the game that
// were not tested with these items.
int ammoIndex = pWeapon->GetSecondaryAmmoType();
if( ammoIndex != -1 )
{
int giveAmount;
giveAmount = GetAmmoDef()->MaxCarry(ammoIndex);
pPlayer->GiveAmmo( giveAmount, GetAmmoDef()->GetAmmoOfIndex(ammoIndex)->pName );
}
}
}
}
}
static ConCommand givecurrentammo("givecurrentammo", CC_GiveCurrentAmmo, "Give a supply of ammo for current weapon..\n", FCVAR_CHEAT );
// pl
BEGIN_SIMPLE_DATADESC( CPlayerState )
// DEFINE_FIELD( netname, FIELD_STRING ), // Don't stomp player name with what's in save/restore
DEFINE_FIELD( v_angle, FIELD_VECTOR ),
DEFINE_FIELD( deadflag, FIELD_BOOLEAN ),
// this is always set to true on restore, don't bother saving it.
// DEFINE_FIELD( fixangle, FIELD_INTEGER ),
// DEFINE_FIELD( anglechange, FIELD_FLOAT ),
// DEFINE_FIELD( hltv, FIELD_BOOLEAN ),
// DEFINE_FIELD( replay, FIELD_BOOLEAN ),
// DEFINE_FIELD( frags, FIELD_INTEGER ),
// DEFINE_FIELD( deaths, FIELD_INTEGER ),
END_DATADESC()
// Global Savedata for player
BEGIN_DATADESC( CBasePlayer )
DEFINE_EMBEDDED( m_Local ),
#if defined USES_ECON_ITEMS
DEFINE_EMBEDDED( m_AttributeList ),
#endif
DEFINE_UTLVECTOR( m_hTriggerSoundscapeList, FIELD_EHANDLE ),
DEFINE_EMBEDDED( pl ),
DEFINE_FIELD( m_StuckLast, FIELD_INTEGER ),
DEFINE_FIELD( m_nButtons, FIELD_INTEGER ),
DEFINE_FIELD( m_afButtonLast, FIELD_INTEGER ),
DEFINE_FIELD( m_afButtonPressed, FIELD_INTEGER ),
DEFINE_FIELD( m_afButtonReleased, FIELD_INTEGER ),
DEFINE_FIELD( m_afButtonDisabled, FIELD_INTEGER ),
DEFINE_FIELD( m_afButtonForced, FIELD_INTEGER ),
DEFINE_FIELD( m_iFOV, FIELD_INTEGER ),
DEFINE_FIELD( m_iFOVStart, FIELD_INTEGER ),
DEFINE_FIELD( m_flFOVTime, FIELD_TIME ),
DEFINE_FIELD( m_iDefaultFOV,FIELD_INTEGER ),
DEFINE_FIELD( m_flVehicleViewFOV, FIELD_FLOAT ),
//DEFINE_FIELD( m_fOnTarget, FIELD_BOOLEAN ), // Don't need to restore
DEFINE_FIELD( m_iObserverMode, FIELD_INTEGER ),
DEFINE_FIELD( m_iObserverLastMode, FIELD_INTEGER ),
DEFINE_FIELD( m_hObserverTarget, FIELD_EHANDLE ),
DEFINE_FIELD( m_bForcedObserverMode, FIELD_BOOLEAN ),
DEFINE_AUTO_ARRAY( m_szAnimExtension, FIELD_CHARACTER ),
// DEFINE_CUSTOM_FIELD( m_Activity, ActivityDataOps() ),
DEFINE_FIELD( m_nUpdateRate, FIELD_INTEGER ),
DEFINE_FIELD( m_fLerpTime, FIELD_FLOAT ),
DEFINE_FIELD( m_bLagCompensation, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bPredictWeapons, FIELD_BOOLEAN ),
DEFINE_FIELD( m_vecAdditionalPVSOrigin, FIELD_POSITION_VECTOR ),
DEFINE_FIELD( m_vecCameraPVSOrigin, FIELD_POSITION_VECTOR ),
DEFINE_FIELD( m_hUseEntity, FIELD_EHANDLE ),
DEFINE_FIELD( m_iTrain, FIELD_INTEGER ),
DEFINE_FIELD( m_iRespawnFrames, FIELD_FLOAT ),
DEFINE_FIELD( m_afPhysicsFlags, FIELD_INTEGER ),
DEFINE_FIELD( m_hVehicle, FIELD_EHANDLE ),
// recreate, don't restore
// DEFINE_FIELD( m_CommandContext, CUtlVector < CCommandContext > ),
//DEFINE_FIELD( m_pPhysicsController, FIELD_POINTER ),
//DEFINE_FIELD( m_pShadowStand, FIELD_POINTER ),
//DEFINE_FIELD( m_pShadowCrouch, FIELD_POINTER ),
//DEFINE_FIELD( m_vphysicsCollisionState, FIELD_INTEGER ),
DEFINE_ARRAY( m_szNetworkIDString, FIELD_CHARACTER, MAX_NETWORKID_LENGTH ),
DEFINE_FIELD( m_oldOrigin, FIELD_POSITION_VECTOR ),
DEFINE_FIELD( m_vecSmoothedVelocity, FIELD_VECTOR ),
//DEFINE_FIELD( m_touchedPhysObject, FIELD_BOOLEAN ),
//DEFINE_FIELD( m_bPhysicsWasFrozen, FIELD_BOOLEAN ),
//DEFINE_FIELD( m_iPlayerSound, FIELD_INTEGER ), // Don't restore, set in Precache()
DEFINE_FIELD( m_iTargetVolume, FIELD_INTEGER ),
DEFINE_AUTO_ARRAY( m_rgItems, FIELD_INTEGER ),
//DEFINE_FIELD( m_fNextSuicideTime, FIELD_TIME ),
// DEFINE_FIELD( m_PlayerInfo, CPlayerInfo ),
DEFINE_FIELD( m_flSwimTime, FIELD_TIME ),
DEFINE_FIELD( m_flDuckTime, FIELD_TIME ),
DEFINE_FIELD( m_flDuckJumpTime, FIELD_TIME ),
DEFINE_FIELD( m_flSuitUpdate, FIELD_TIME ),
DEFINE_AUTO_ARRAY( m_rgSuitPlayList, FIELD_INTEGER ),
DEFINE_FIELD( m_iSuitPlayNext, FIELD_INTEGER ),
DEFINE_AUTO_ARRAY( m_rgiSuitNoRepeat, FIELD_INTEGER ),
DEFINE_AUTO_ARRAY( m_rgflSuitNoRepeatTime, FIELD_TIME ),
DEFINE_FIELD( m_bPauseBonusProgress, FIELD_BOOLEAN ),
DEFINE_FIELD( m_iBonusProgress, FIELD_INTEGER ),
DEFINE_FIELD( m_iBonusChallenge, FIELD_INTEGER ),
DEFINE_FIELD( m_lastDamageAmount, FIELD_INTEGER ),
DEFINE_FIELD( m_tbdPrev, FIELD_TIME ),
DEFINE_FIELD( m_flStepSoundTime, FIELD_FLOAT ),
DEFINE_ARRAY( m_szNetname, FIELD_CHARACTER, MAX_PLAYER_NAME_LENGTH ),
//DEFINE_FIELD( m_flgeigerRange, FIELD_FLOAT ), // Don't restore, reset in Precache()
//DEFINE_FIELD( m_flgeigerDelay, FIELD_FLOAT ), // Don't restore, reset in Precache()
//DEFINE_FIELD( m_igeigerRangePrev, FIELD_FLOAT ), // Don't restore, reset in Precache()
//DEFINE_FIELD( m_iStepLeft, FIELD_INTEGER ), // Don't need to restore
//DEFINE_FIELD( m_chTextureType, FIELD_CHARACTER ), // Don't need to restore
//DEFINE_FIELD( m_surfaceProps, FIELD_INTEGER ), // don't need to restore, reset by gamemovement
// DEFINE_FIELD( m_pSurfaceData, surfacedata_t* ),
//DEFINE_FIELD( m_surfaceFriction, FIELD_FLOAT ),
//DEFINE_FIELD( m_chPreviousTextureType, FIELD_CHARACTER ),
DEFINE_FIELD( m_idrowndmg, FIELD_INTEGER ),
DEFINE_FIELD( m_idrownrestored, FIELD_INTEGER ),
DEFINE_FIELD( m_nPoisonDmg, FIELD_INTEGER ),
DEFINE_FIELD( m_nPoisonRestored, FIELD_INTEGER ),
DEFINE_FIELD( m_bitsHUDDamage, FIELD_INTEGER ),
DEFINE_FIELD( m_fInitHUD, FIELD_BOOLEAN ),
DEFINE_FIELD( m_flDeathTime, FIELD_TIME ),
DEFINE_FIELD( m_flDeathAnimTime, FIELD_TIME ),
//DEFINE_FIELD( m_fGameHUDInitialized, FIELD_BOOLEAN ), // only used in multiplayer games
//DEFINE_FIELD( m_fWeapon, FIELD_BOOLEAN ), // Don't restore, client needs reset
//DEFINE_FIELD( m_iUpdateTime, FIELD_INTEGER ), // Don't need to restore
//DEFINE_FIELD( m_iClientBattery, FIELD_INTEGER ), // Don't restore, client needs reset
//DEFINE_FIELD( m_iClientHideHUD, FIELD_INTEGER ), // Don't restore, client needs reset
//DEFINE_FIELD( m_vecAutoAim, FIELD_VECTOR ), // Don't save/restore - this is recomputed
//DEFINE_FIELD( m_lastx, FIELD_INTEGER ),
//DEFINE_FIELD( m_lasty, FIELD_INTEGER ),
DEFINE_FIELD( m_iFrags, FIELD_INTEGER ),
DEFINE_FIELD( m_iDeaths, FIELD_INTEGER ),
DEFINE_FIELD( m_bAllowInstantSpawn, FIELD_BOOLEAN ),
DEFINE_FIELD( m_flNextDecalTime, FIELD_TIME ),
//DEFINE_AUTO_ARRAY( m_szTeamName, FIELD_STRING ), // mp
//DEFINE_FIELD( m_iConnected, FIELD_INTEGER ),
// from edict_t
DEFINE_FIELD( m_ArmorValue, FIELD_INTEGER ),
DEFINE_FIELD( m_DmgOrigin, FIELD_VECTOR ),
DEFINE_FIELD( m_DmgTake, FIELD_FLOAT ),
DEFINE_FIELD( m_DmgSave, FIELD_FLOAT ),
DEFINE_FIELD( m_AirFinished, FIELD_TIME ),
DEFINE_FIELD( m_PainFinished, FIELD_TIME ),
DEFINE_FIELD( m_iPlayerLocked, FIELD_INTEGER ),
DEFINE_AUTO_ARRAY( m_hViewModel, FIELD_EHANDLE ),
DEFINE_FIELD( m_flMaxspeed, FIELD_FLOAT ),
DEFINE_FIELD( m_flWaterJumpTime, FIELD_TIME ),
DEFINE_FIELD( m_vecWaterJumpVel, FIELD_VECTOR ),
DEFINE_FIELD( m_nImpulse, FIELD_INTEGER ),
DEFINE_FIELD( m_flSwimSoundTime, FIELD_TIME ),
DEFINE_FIELD( m_vecLadderNormal, FIELD_VECTOR ),
DEFINE_FIELD( m_flFlashTime, FIELD_TIME ),
DEFINE_FIELD( m_nDrownDmgRate, FIELD_INTEGER ),
DEFINE_FIELD( m_iSuicideCustomKillFlags, FIELD_INTEGER ),
// NOT SAVED
//DEFINE_FIELD( m_vForcedOrigin, FIELD_VECTOR ),
//DEFINE_FIELD( m_bForceOrigin, FIELD_BOOLEAN ),
//DEFINE_FIELD( m_nTickBase, FIELD_INTEGER ),
//DEFINE_FIELD( m_LastCmd, FIELD_ ),
// DEFINE_FIELD( m_pCurrentCommand, CUserCmd ),
//DEFINE_FIELD( m_bGamePaused, FIELD_BOOLEAN ),
// DEFINE_FIELD( m_iVehicleAnalogBias, FIELD_INTEGER ),
// m_flVehicleViewFOV
// m_vecVehicleViewOrigin
// m_vecVehicleViewAngles
// m_nVehicleViewSavedFrame
DEFINE_FIELD( m_bitsDamageType, FIELD_INTEGER ),
DEFINE_AUTO_ARRAY( m_rgbTimeBasedDamage, FIELD_CHARACTER ),
DEFINE_FIELD( m_fLastPlayerTalkTime, FIELD_FLOAT ),
DEFINE_FIELD( m_hLastWeapon, FIELD_EHANDLE ),
#if !defined( NO_ENTITY_PREDICTION )
// DEFINE_FIELD( m_SimulatedByThisPlayer, CUtlVector < CHandle < CBaseEntity > > ),
#endif
DEFINE_FIELD( m_flOldPlayerZ, FIELD_FLOAT ),
DEFINE_FIELD( m_flOldPlayerViewOffsetZ, FIELD_FLOAT ),
DEFINE_FIELD( m_bPlayerUnderwater, FIELD_BOOLEAN ),
DEFINE_FIELD( m_hViewEntity, FIELD_EHANDLE ),
DEFINE_FIELD( m_hConstraintEntity, FIELD_EHANDLE ),
DEFINE_FIELD( m_vecConstraintCenter, FIELD_VECTOR ),
DEFINE_FIELD( m_flConstraintRadius, FIELD_FLOAT ),
DEFINE_FIELD( m_flConstraintWidth, FIELD_FLOAT ),
DEFINE_FIELD( m_flConstraintSpeedFactor, FIELD_FLOAT ),
DEFINE_FIELD( m_hZoomOwner, FIELD_EHANDLE ),
DEFINE_FIELD( m_flLaggedMovementValue, FIELD_FLOAT ),
DEFINE_FIELD( m_vNewVPhysicsPosition, FIELD_VECTOR ),
DEFINE_FIELD( m_vNewVPhysicsVelocity, FIELD_VECTOR ),
DEFINE_FIELD( m_bSinglePlayerGameEnding, FIELD_BOOLEAN ),
DEFINE_ARRAY( m_szLastPlaceName, FIELD_CHARACTER, MAX_PLACE_NAME_LENGTH ),
DEFINE_FIELD( m_autoKickDisabled, FIELD_BOOLEAN ),
// Function Pointers
DEFINE_FUNCTION( PlayerDeathThink ),
// Inputs
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHealth", InputSetHealth ),
DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetHUDVisibility", InputSetHUDVisibility ),
DEFINE_INPUTFUNC( FIELD_STRING, "SetFogController", InputSetFogController ),
DEFINE_INPUTFUNC( FIELD_STRING, "HandleMapEvent", InputHandleMapEvent ),
DEFINE_FIELD( m_nNumCrouches, FIELD_INTEGER ),
DEFINE_FIELD( m_bDuckToggled, FIELD_BOOLEAN ),
DEFINE_FIELD( m_flForwardMove, FIELD_FLOAT ),
DEFINE_FIELD( m_flSideMove, FIELD_FLOAT ),
DEFINE_FIELD( m_vecPreviouslyPredictedOrigin, FIELD_POSITION_VECTOR ),
DEFINE_FIELD( m_nNumCrateHudHints, FIELD_INTEGER ),
// DEFINE_FIELD( m_nBodyPitchPoseParam, FIELD_INTEGER ),
// DEFINE_ARRAY( m_StepSoundCache, StepSoundCache_t, 2 ),
// DEFINE_UTLVECTOR( m_vecPlayerCmdInfo ),
// DEFINE_UTLVECTOR( m_vecPlayerSimInfo ),
END_DATADESC()
int giPrecacheGrunt = 0;
edict_t *CBasePlayer::s_PlayerEdict = NULL;
inline bool ShouldRunCommandsInContext( const CCommandContext *ctx )
{
// TODO: This should be enabled at some point. If usercmds can run while paused, then
// they can create entities which will never die and it will fill up the entity list.
#ifdef NO_USERCMDS_DURING_PAUSE
return !ctx->paused || sv_noclipduringpause.GetInt();
#else
return true;
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : CBaseViewModel
//-----------------------------------------------------------------------------
CBaseViewModel *CBasePlayer::GetViewModel( int index /*= 0*/, bool bObserverOK )
{
Assert( index >= 0 && index < MAX_VIEWMODELS );
return m_hViewModel[ index ].Get();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBasePlayer::CreateViewModel( int index /*=0*/ )
{
Assert( index >= 0 && index < MAX_VIEWMODELS );
if ( GetViewModel( index ) )
return;
CBaseViewModel *vm = ( CBaseViewModel * )CreateEntityByName( "viewmodel" );
if ( vm )
{
vm->SetAbsOrigin( GetAbsOrigin() );
vm->SetOwner( this );
vm->SetIndex( index );
DispatchSpawn( vm );
vm->FollowEntity( this );
m_hViewModel.Set( index, vm );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBasePlayer::DestroyViewModels( void )
{
int i;
for ( i = MAX_VIEWMODELS - 1; i >= 0; i-- )
{
CBaseViewModel *vm = GetViewModel( i );
if ( !vm )
continue;
UTIL_Remove( vm );
m_hViewModel.Set( i, NULL );
}
}
//-----------------------------------------------------------------------------
// Purpose: Static member function to create a player of the specified class
// Input : *className -
// *ed -
// Output : CBasePlayer
//-----------------------------------------------------------------------------
CBasePlayer *CBasePlayer::CreatePlayer( const char *className, edict_t *ed )
{
CBasePlayer *player;
CBasePlayer::s_PlayerEdict = ed;
player = ( CBasePlayer * )CreateEntityByName( className );
return player;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input :
// Output :
//-----------------------------------------------------------------------------
CBasePlayer::CBasePlayer( )
{
AddEFlags( EFL_NO_AUTO_EDICT_ATTACH );
#ifdef _DEBUG
m_vecAutoAim.Init();
m_vecAdditionalPVSOrigin.Init();
m_vecCameraPVSOrigin.Init();
m_DmgOrigin.Init();
m_vecLadderNormal.Init();
m_oldOrigin.Init();
m_vecSmoothedVelocity.Init();
#endif
if ( s_PlayerEdict )
{
// take the assigned edict_t and attach it
Assert( s_PlayerEdict != NULL );
NetworkProp()->AttachEdict( s_PlayerEdict );
s_PlayerEdict = NULL;
}
m_flFlashTime = -1;
pl.fixangle = FIXANGLE_ABSOLUTE;
pl.hltv = false;
pl.replay = false;
pl.frags = 0;
pl.deaths = 0;
m_szNetname[0] = '\0';
m_iHealth = 0;
Weapon_SetLast( NULL );
m_bitsDamageType = 0;
m_bForceOrigin = false;
m_hVehicle = NULL;
m_pCurrentCommand = NULL;
// Setup our default FOV
m_iDefaultFOV = g_pGameRules->DefaultFOV();
m_hZoomOwner = NULL;
m_nUpdateRate = 20; // cl_updaterate defualt
m_fLerpTime = 0.1f; // cl_interp default
m_bPredictWeapons = true;
m_bLagCompensation = false;
m_flLaggedMovementValue = 1.0f;
m_StuckLast = 0;
m_impactEnergyScale = 1.0f;
m_fLastPlayerTalkTime = 0.0f;
m_PlayerInfo.SetParent( this );
ResetObserverMode();
m_surfaceProps = 0;
m_pSurfaceData = NULL;
m_surfaceFriction = 1.0f;
m_chTextureType = 0;
m_chPreviousTextureType = 0;
m_iSuicideCustomKillFlags = 0;
m_fDelay = 0.0f;
m_fReplayEnd = -1;
m_iReplayEntity = 0;
m_autoKickDisabled = false;
m_nNumCrouches = 0;
m_bDuckToggled = false;
m_bPhysicsWasFrozen = false;
// Used to mask off buttons
m_afButtonDisabled = 0;
m_afButtonForced = 0;
m_nBodyPitchPoseParam = -1;
m_flForwardMove = 0;
m_flSideMove = 0;
// NVNT default to no haptics
m_bhasHaptics = false;
m_vecConstraintCenter = vec3_origin;
m_flLastUserCommandTime = 0.f;
m_flMovementTimeForUserCmdProcessingRemaining = 0.0f;
}
CBasePlayer::~CBasePlayer( )
{
VPhysicsDestroyObject();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input :
// Output :
//-----------------------------------------------------------------------------
void CBasePlayer::UpdateOnRemove( void )
{
VPhysicsDestroyObject();
// Remove him from his current team
if ( GetTeam() )
{
GetTeam()->RemovePlayer( this );
}
// Chain at end to mimic destructor unwind order
BaseClass::UpdateOnRemove();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : **pvs -
// **pas -
//-----------------------------------------------------------------------------
void CBasePlayer::SetupVisibility( CBaseEntity *pViewEntity, unsigned char *pvs, int pvssize )
{
// If we have a viewentity, we don't add the player's origin.
if ( pViewEntity )
return;
Vector org;
org = EyePosition();
engine->AddOriginToPVS( org );
}
int CBasePlayer::UpdateTransmitState()
{
// always call ShouldTransmit() for players
return SetTransmitState( FL_EDICT_FULLCHECK );
}
int CBasePlayer::ShouldTransmit( const CCheckTransmitInfo *pInfo )
{
// Allow me to introduce myself to, err, myself.
// I.e., always update the recipient player data even if it's nodraw (first person mode)
if ( pInfo->m_pClientEnt == edict() )
{
return FL_EDICT_ALWAYS;
}
// when HLTV/Replay is connected and spectators press +USE, they
// signal that they are recording a interesting scene
// so transmit these 'cameramans' to the HLTV or Replay client
if ( HLTVDirector()->GetCameraMan() == entindex() )
{
CBaseEntity *pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt );
Assert( pRecipientEntity->IsPlayer() );
CBasePlayer *pRecipientPlayer = static_cast<CBasePlayer*>( pRecipientEntity );
if ( pRecipientPlayer->IsHLTV() ||
pRecipientPlayer->IsReplay() )
{
// HACK force calling RecomputePVSInformation to update PVS data
NetworkProp()->AreaNum();
return FL_EDICT_ALWAYS;
}
}
// Transmit for a short time after death and our death anim finishes so ragdolls can access reliable player data.
// Note that if m_flDeathAnimTime is never set, as long as m_lifeState is set to LIFE_DEAD after dying, this
// test will act as if the death anim is finished.
if ( IsEffectActive( EF_NODRAW ) || ( IsObserver() && ( gpGlobals->curtime - m_flDeathTime > 0.5 ) &&
( m_lifeState == LIFE_DEAD ) && ( gpGlobals->curtime - m_flDeathAnimTime > 0.5 ) ) )
{
return FL_EDICT_DONTSEND;
}
return BaseClass::ShouldTransmit( pInfo );
}
bool CBasePlayer::WantsLagCompensationOnEntity( const CBasePlayer *pPlayer, const CUserCmd *pCmd, const CBitVec<MAX_EDICTS> *pEntityTransmitBits ) const
{
// Team members shouldn't be adjusted unless friendly fire is on.
if ( !friendlyfire.GetInt() && pPlayer->GetTeamNumber() == GetTeamNumber() )
return false;
// If this entity hasn't been transmitted to us and acked, then don't bother lag compensating it.
if ( pEntityTransmitBits && !pEntityTransmitBits->Get( pPlayer->entindex() ) )
return false;
const Vector &vMyOrigin = GetAbsOrigin();
const Vector &vHisOrigin = pPlayer->GetAbsOrigin();
// get max distance player could have moved within max lag compensation time,
// multiply by 1.5 to to avoid "dead zones" (sqrt(2) would be the exact value)
float maxDistance = 1.5 * pPlayer->MaxSpeed() * sv_maxunlag.GetFloat();
// If the player is within this distance, lag compensate them in case they're running past us.
if ( vHisOrigin.DistTo( vMyOrigin ) < maxDistance )
return true;
// If their origin is not within a 45 degree cone in front of us, no need to lag compensate.
Vector vForward;
AngleVectors( pCmd->viewangles, &vForward );
Vector vDiff = vHisOrigin - vMyOrigin;
VectorNormalize( vDiff );
float flCosAngle = 0.707107f; // 45 degree angle
if ( vForward.Dot( vDiff ) < flCosAngle )
return false;
return true;
}
void CBasePlayer::PauseBonusProgress( bool bPause )
{
m_bPauseBonusProgress = bPause;
}
void CBasePlayer::SetBonusProgress( int iBonusProgress )
{
if ( !m_bPauseBonusProgress )
m_iBonusProgress = iBonusProgress;
}
void CBasePlayer::SetBonusChallenge( int iBonusChallenge )
{
m_iBonusChallenge = iBonusChallenge;
}
//-----------------------------------------------------------------------------
// Sets the view angles
//-----------------------------------------------------------------------------
void CBasePlayer::SnapEyeAngles( const QAngle &viewAngles )
{
pl.v_angle = viewAngles;
pl.fixangle = FIXANGLE_ABSOLUTE;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : iSpeed -
// iMax -
// Output : int
//-----------------------------------------------------------------------------
int TrainSpeed(int iSpeed, int iMax)
{
float fSpeed, fMax;
int iRet = 0;
fMax = (float)iMax;
fSpeed = iSpeed;
fSpeed = fSpeed/fMax;
if (iSpeed < 0)
iRet = TRAIN_BACK;
else if (iSpeed == 0)
iRet = TRAIN_NEUTRAL;
else if (fSpeed < 0.33)
iRet = TRAIN_SLOW;
else if (fSpeed < 0.66)
iRet = TRAIN_MEDIUM;
else
iRet = TRAIN_FAST;
return iRet;
}
void CBasePlayer::DeathSound( const CTakeDamageInfo &info )
{
// temporarily using pain sounds for death sounds
// Did we die from falling?
if ( m_bitsDamageType & DMG_FALL )
{
// They died in the fall. Play a splat sound.
EmitSound( "Player.FallGib" );
}
else
{
EmitSound( "Player.Death" );
}
// play one of the suit death alarms
if ( IsSuitEquipped() )
{
UTIL_EmitGroupnameSuit(edict(), "HEV_DEAD");
}
}
// override takehealth
// bitsDamageType indicates type of damage healed.
int CBasePlayer::TakeHealth( float flHealth, int bitsDamageType )
{
// clear out any damage types we healed.
// UNDONE: generic health should not heal any
// UNDONE: time-based damage
if (m_takedamage)
{
int bitsDmgTimeBased = g_pGameRules->Damage_GetTimeBased();
m_bitsDamageType &= ~( bitsDamageType & ~bitsDmgTimeBased );
}
// I disabled reporting history into the dbghist because it was super spammy.
// But, if you need to reenable it, the code is below in the "else" clause.
#if 1 // #ifdef DISABLE_DEBUG_HISTORY
return BaseClass::TakeHealth (flHealth, bitsDamageType);
#else
const int healingTaken = BaseClass::TakeHealth(flHealth,bitsDamageType);
char buf[256];
Q_snprintf(buf, 256, "[%f] Player %s healed for %d with damagetype %X\n", gpGlobals->curtime, GetDebugName(), healingTaken, bitsDamageType);
ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, buf );
return healingTaken;
#endif
}
//-----------------------------------------------------------------------------
// Purpose: Draw all overlays (should be implemented in cascade by subclass to add
// any additional non-text overlays)
// Input :
// Output : Current text offset from the top
//-----------------------------------------------------------------------------
void CBasePlayer::DrawDebugGeometryOverlays(void)
{
// --------------------------------------------------------
// If in buddha mode and dead draw lines to indicate death
// --------------------------------------------------------
if ((m_debugOverlays & OVERLAY_BUDDHA_MODE) && m_iHealth == 1)
{
Vector vBodyDir = BodyDirection2D( );
Vector eyePos = EyePosition() + vBodyDir*10.0;
Vector vUp = Vector(0,0,8);
Vector vSide;
CrossProduct( vBodyDir, vUp, vSide);
NDebugOverlay::Line(eyePos+vSide+vUp, eyePos-vSide-vUp, 255,0,0, false, 0);
NDebugOverlay::Line(eyePos+vSide-vUp, eyePos-vSide+vUp, 255,0,0, false, 0);
}
BaseClass::DrawDebugGeometryOverlays();
}
//=========================================================
// TraceAttack
//=========================================================
void CBasePlayer::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
{
if ( m_takedamage )
{
CTakeDamageInfo info = inputInfo;
if ( info.GetAttacker() )
{
// --------------------------------------------------
// If an NPC check if friendly fire is disallowed
// --------------------------------------------------
CAI_BaseNPC *pNPC = info.GetAttacker()->MyNPCPointer();
if ( pNPC && (pNPC->CapabilitiesGet() & bits_CAP_NO_HIT_PLAYER) && pNPC->IRelationType( this ) != D_HT )
return;
// Prevent team damage here so blood doesn't appear
if ( info.GetAttacker()->IsPlayer() )
{
if ( !g_pGameRules->FPlayerCanTakeDamage( this, info.GetAttacker(), info ) )
return;
}
}
SetLastHitGroup( ptr->hitgroup );
switch ( ptr->hitgroup )
{
case HITGROUP_GENERIC:
break;
case HITGROUP_HEAD:
info.ScaleDamage( sk_player_head.GetFloat() );
break;
case HITGROUP_CHEST:
info.ScaleDamage( sk_player_chest.GetFloat() );
break;
case HITGROUP_STOMACH:
info.ScaleDamage( sk_player_stomach.GetFloat() );
break;
case HITGROUP_LEFTARM:
case HITGROUP_RIGHTARM:
info.ScaleDamage( sk_player_arm.GetFloat() );
break;
case HITGROUP_LEFTLEG:
case HITGROUP_RIGHTLEG:
info.ScaleDamage( sk_player_leg.GetFloat() );
break;
default:
break;
}
#ifdef HL2_EPISODIC
// If this damage type makes us bleed, then do so
bool bShouldBleed = !g_pGameRules->Damage_ShouldNotBleed( info.GetDamageType() );
if ( bShouldBleed )
#endif
{
SpawnBlood(ptr->endpos, vecDir, BloodColor(), info.GetDamage());// a little surface blood.
TraceBleed( info.GetDamage(), vecDir, ptr, info.GetDamageType() );
}
AddMultiDamage( info, this );
}
}
//------------------------------------------------------------------------------
// Purpose : Do some kind of damage effect for the type of damage
// Input :
// Output :
//------------------------------------------------------------------------------
void CBasePlayer::DamageEffect(float flDamage, int fDamageType)
{
if (fDamageType & DMG_CRUSH)
{
//Red damage indicator
color32 red = {128,0,0,128};
UTIL_ScreenFade( this, red, 1.0f, 0.1f, FFADE_IN );
}
else if (fDamageType & DMG_DROWN)
{
//Red damage indicator
color32 blue = {0,0,128,128};
UTIL_ScreenFade( this, blue, 1.0f, 0.1f, FFADE_IN );
}
else if (fDamageType & DMG_SLASH)
{
// If slash damage shoot some blood
SpawnBlood(EyePosition(), g_vecAttackDir, BloodColor(), flDamage);
}
else if (fDamageType & DMG_PLASMA)
{
// Blue screen fade
color32 blue = {0,0,255,100};
UTIL_ScreenFade( this, blue, 0.2, 0.4, FFADE_MODULATE );
// Very small screen shake
// Both -0.1 and 0.1 map to 0 when converted to integer, so all of these RandomInt
// calls are just expensive ways of returning zero. This code has always been this
// way and has never had any value. clang complains about the conversion from a
// literal floating-point number to an integer.
//ViewPunch(QAngle(random->RandomInt(-0.1,0.1), random->RandomInt(-0.1,0.1), random->RandomInt(-0.1,0.1)));
// Burn sound
EmitSound( "Player.PlasmaDamage" );
}
else if (fDamageType & DMG_SONIC)
{
// Sonic damage sound
EmitSound( "Player.SonicDamage" );
}
else if ( fDamageType & DMG_BULLET )
{
EmitSound( "Flesh.BulletImpact" );
}
}
/*
Take some damage.
NOTE: each call to OnTakeDamage with bitsDamageType set to a time-based damage
type will cause the damage time countdown to be reset. Thus the ongoing effects of poison, radiation
etc are implemented with subsequent calls to OnTakeDamage using DMG_GENERIC.
*/
// Old values
#define OLD_ARMOR_RATIO 0.2 // Armor Takes 80% of the damage
#define OLD_ARMOR_BONUS 0.5 // Each Point of Armor is work 1/x points of health
// New values
#define ARMOR_RATIO 0.2
#define ARMOR_BONUS 1.0
//---------------------------------------------------------
//---------------------------------------------------------
bool CBasePlayer::ShouldTakeDamageInCommentaryMode( const CTakeDamageInfo &inputInfo )
{
// Only ignore damage when we're listening to a commentary node
if ( !IsListeningToCommentary() )
return true;
// Allow SetHealth inputs to kill player.
if ( inputInfo.GetInflictor() == this && inputInfo.GetAttacker() == this )
return true;
#ifdef PORTAL
if ( inputInfo.GetDamageType() & DMG_ACID )
return true;
#endif
// In commentary, ignore all damage except for falling and leeches
if ( !(inputInfo.GetDamageType() & (DMG_BURN | DMG_PLASMA | DMG_FALL | DMG_CRUSH)) && inputInfo.GetDamageType() != DMG_GENERIC )
return false;
// We let DMG_CRUSH pass the check above so that we can check here for stress damage. Deny the CRUSH damage if there is no attacker,
// or if the attacker isn't a BSP model. Therefore, we're allowing any CRUSH damage done by a BSP model.
if ( (inputInfo.GetDamageType() & DMG_CRUSH) && ( inputInfo.GetAttacker() == NULL || !inputInfo.GetAttacker()->IsBSPModel() ) )
return false;
return true;
}
int CBasePlayer::OnTakeDamage( const CTakeDamageInfo &inputInfo )
{
// have suit diagnose the problem - ie: report damage type
int bitsDamage = inputInfo.GetDamageType();
int ffound = true;
int fmajor;
int fcritical;
int fTookDamage;
int ftrivial;
float flRatio;
float flBonus;
float flHealthPrev = m_iHealth;
CTakeDamageInfo info = inputInfo;
IServerVehicle *pVehicle = GetVehicle();
if ( pVehicle )
{
// Let the vehicle decide if we should take this damage or not
if ( pVehicle->PassengerShouldReceiveDamage( info ) == false )
return 0;
}
if ( IsInCommentaryMode() )
{
if( !ShouldTakeDamageInCommentaryMode( info ) )
return 0;
}
if ( GetFlags() & FL_GODMODE )
return 0;
if ( m_debugOverlays & OVERLAY_BUDDHA_MODE )
{
if ((m_iHealth - info.GetDamage()) <= 0)
{
m_iHealth = 1;
return 0;
}
}
// Early out if there's no damage
if ( !info.GetDamage() )
return 0;
if( old_armor.GetBool() )
{
flBonus = OLD_ARMOR_BONUS;
flRatio = OLD_ARMOR_RATIO;
}
else
{
flBonus = ARMOR_BONUS;
flRatio = ARMOR_RATIO;
}
if ( ( info.GetDamageType() & DMG_BLAST ) && g_pGameRules->IsMultiplayer() )
{
// blasts damage armor more.
flBonus *= 2;
}
// Already dead
if ( !IsAlive() )
return 0;
// go take the damage first
if ( !g_pGameRules->FPlayerCanTakeDamage( this, info.GetAttacker(), inputInfo ) )
{
// Refuse the damage
return 0;
}
// print to console if the appropriate cvar is set
#ifdef DISABLE_DEBUG_HISTORY
if (player_debug_print_damage.GetBool() && info.GetDamage() > 0)
#endif
{
char dmgtype[64];
CTakeDamageInfo::DebugGetDamageTypeString( info.GetDamageType(), dmgtype, 512 );
char outputString[256];
Q_snprintf( outputString, 256, "%f: Player %s at [%0.2f %0.2f %0.2f] took %f damage from %s, type %s\n", gpGlobals->curtime, GetDebugName(),
GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z, info.GetDamage(), info.GetInflictor()->GetDebugName(), dmgtype );
//Msg( "%f: Player %s at [%0.2f %0.2f %0.2f] took %f damage from %s, type %s\n", gpGlobals->curtime, GetDebugName(),
// GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z, info.GetDamage(), info.GetInflictor()->GetDebugName(), dmgtype );
ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, outputString );
#ifndef DISABLE_DEBUG_HISTORY
if ( player_debug_print_damage.GetBool() ) // if we're not in here just for the debug history
#endif
{
Msg( "%s", outputString);
}
}
// keep track of amount of damage last sustained
m_lastDamageAmount = info.GetDamage();
// Armor.
if (m_ArmorValue && !(info.GetDamageType() & (DMG_FALL | DMG_DROWN | DMG_POISON | DMG_RADIATION)) )// armor doesn't protect against fall or drown damage!
{
float flNew = info.GetDamage() * flRatio;
float flArmor;
flArmor = (info.GetDamage() - flNew) * flBonus;
if( !old_armor.GetBool() )
{
if( flArmor < 1.0 )
{
flArmor = 1.0;
}
}
// Does this use more armor than we have?
if (flArmor > m_ArmorValue)
{
flArmor = m_ArmorValue;
flArmor *= (1/flBonus);
flNew = info.GetDamage() - flArmor;
m_DmgSave = m_ArmorValue;
m_ArmorValue = 0;
}
else
{
m_DmgSave = flArmor;
m_ArmorValue -= flArmor;
}
info.SetDamage( flNew );
}
#if defined( WIN32 ) && !defined( _X360 )
// NVNT if player's client has a haptic device send them a user message with the damage.
if(HasHaptics())
HapticsDamage(this,info);
#endif
// this cast to INT is critical!!! If a player ends up with 0.5 health, the engine will get that
// as an int (zero) and think the player is dead! (this will incite a clientside screentilt, etc)
// NOTENOTE: jdw - We are now capable of retaining the mantissa of this damage value and deferring its application
// info.SetDamage( (int)info.GetDamage() );
// Call up to the base class
fTookDamage = BaseClass::OnTakeDamage( info );
// Early out if the base class took no damage
if ( !fTookDamage )
return 0;
// add to the damage total for clients, which will be sent as a single
// message at the end of the frame
// todo: remove after combining shotgun blasts?
if ( info.GetInflictor() && info.GetInflictor()->edict() )
m_DmgOrigin = info.GetInflictor()->GetAbsOrigin();
m_DmgTake += (int)info.GetDamage();
// Reset damage time countdown for each type of time based damage player just sustained
for (int i = 0; i < CDMG_TIMEBASED; i++)
{
// Make sure the damage type is really time-based.
// This is kind of hacky but necessary until we setup DamageType as an enum.
int iDamage = ( DMG_PARALYZE << i );
if ( ( info.GetDamageType() & iDamage ) && g_pGameRules->Damage_IsTimeBased( iDamage ) )
{
m_rgbTimeBasedDamage[i] = 0;
}
}
// Display any effect associate with this damage type
DamageEffect(info.GetDamage(),bitsDamage);
// how bad is it, doc?
ftrivial = (m_iHealth > 75 || m_lastDamageAmount < 5);
fmajor = (m_lastDamageAmount > 25);
fcritical = (m_iHealth < 30);
// handle all bits set in this damage message,
// let the suit give player the diagnosis
// UNDONE: add sounds for types of damage sustained (ie: burn, shock, slash )
// UNDONE: still need to record damage and heal messages for the following types
// DMG_BURN
// DMG_FREEZE
// DMG_BLAST
// DMG_SHOCK
m_bitsDamageType |= bitsDamage; // Save this so we can report it to the client
m_bitsHUDDamage = -1; // make sure the damage bits get resent
while (fTookDamage && (!ftrivial || g_pGameRules->Damage_IsTimeBased( bitsDamage ) ) && ffound && bitsDamage)
{
ffound = false;
if (bitsDamage & DMG_CLUB)
{
if (fmajor)
SetSuitUpdate("!HEV_DMG4", false, SUIT_NEXT_IN_30SEC); // minor fracture
bitsDamage &= ~DMG_CLUB;
ffound = true;
}
if (bitsDamage & (DMG_FALL | DMG_CRUSH))
{
if (fmajor)
SetSuitUpdate("!HEV_DMG5", false, SUIT_NEXT_IN_30SEC); // major fracture
else
SetSuitUpdate("!HEV_DMG4", false, SUIT_NEXT_IN_30SEC); // minor fracture
bitsDamage &= ~(DMG_FALL | DMG_CRUSH);
ffound = true;
}
if (bitsDamage & DMG_BULLET)
{
if (m_lastDamageAmount > 5)
SetSuitUpdate("!HEV_DMG6", false, SUIT_NEXT_IN_30SEC); // blood loss detected
//else
// SetSuitUpdate("!HEV_DMG0", false, SUIT_NEXT_IN_30SEC); // minor laceration
bitsDamage &= ~DMG_BULLET;
ffound = true;
}
if (bitsDamage & DMG_SLASH)
{
if (fmajor)
SetSuitUpdate("!HEV_DMG1", false, SUIT_NEXT_IN_30SEC); // major laceration
else
SetSuitUpdate("!HEV_DMG0", false, SUIT_NEXT_IN_30SEC); // minor laceration
bitsDamage &= ~DMG_SLASH;
ffound = true;
}
if (bitsDamage & DMG_SONIC)
{
if (fmajor)
SetSuitUpdate("!HEV_DMG2", false, SUIT_NEXT_IN_1MIN); // internal bleeding
bitsDamage &= ~DMG_SONIC;
ffound = true;
}
if (bitsDamage & (DMG_POISON | DMG_PARALYZE))
{
if (bitsDamage & DMG_POISON)
{
m_nPoisonDmg += info.GetDamage();
m_tbdPrev = gpGlobals->curtime;
m_rgbTimeBasedDamage[itbd_PoisonRecover] = 0;
}
SetSuitUpdate("!HEV_DMG3", false, SUIT_NEXT_IN_1MIN); // blood toxins detected
bitsDamage &= ~( DMG_POISON | DMG_PARALYZE );
ffound = true;
}
if (bitsDamage & DMG_ACID)
{
SetSuitUpdate("!HEV_DET1", false, SUIT_NEXT_IN_1MIN); // hazardous chemicals detected
bitsDamage &= ~DMG_ACID;
ffound = true;
}
if (bitsDamage & DMG_NERVEGAS)
{
SetSuitUpdate("!HEV_DET0", false, SUIT_NEXT_IN_1MIN); // biohazard detected
bitsDamage &= ~DMG_NERVEGAS;
ffound = true;
}
if (bitsDamage & DMG_RADIATION)
{
SetSuitUpdate("!HEV_DET2", false, SUIT_NEXT_IN_1MIN); // radiation detected
bitsDamage &= ~DMG_RADIATION;
ffound = true;
}
if (bitsDamage & DMG_SHOCK)
{
bitsDamage &= ~DMG_SHOCK;
ffound = true;
}
}
float flPunch = -2;
if( hl2_episodic.GetBool() && info.GetAttacker() && !FInViewCone( info.GetAttacker() ) )
{
if( info.GetDamage() > 10.0f )
flPunch = -10;
else
flPunch = RandomFloat( -5, -7 );
}
m_Local.m_vecPunchAngle.SetX( flPunch );
if (fTookDamage && !ftrivial && fmajor && flHealthPrev >= 75)
{
// first time we take major damage...
// turn automedic on if not on
SetSuitUpdate("!HEV_MED1", false, SUIT_NEXT_IN_30MIN); // automedic on
// give morphine shot if not given recently
SetSuitUpdate("!HEV_HEAL7", false, SUIT_NEXT_IN_30MIN); // morphine shot
}
if (fTookDamage && !ftrivial && fcritical && flHealthPrev < 75)
{
// already took major damage, now it's critical...
if (m_iHealth < 6)
SetSuitUpdate("!HEV_HLTH3", false, SUIT_NEXT_IN_10MIN); // near death
else if (m_iHealth < 20)
SetSuitUpdate("!HEV_HLTH2", false, SUIT_NEXT_IN_10MIN); // health critical
// give critical health warnings
if (!random->RandomInt(0,3) && flHealthPrev < 50)
SetSuitUpdate("!HEV_DMG7", false, SUIT_NEXT_IN_5MIN); //seek medical attention
}
// if we're taking time based damage, warn about its continuing effects
if (fTookDamage && g_pGameRules->Damage_IsTimeBased( info.GetDamageType() ) && flHealthPrev < 75)
{
if (flHealthPrev < 50)
{
if (!random->RandomInt(0,3))
SetSuitUpdate("!HEV_DMG7", false, SUIT_NEXT_IN_5MIN); //seek medical attention
}
else
SetSuitUpdate("!HEV_HLTH1", false, SUIT_NEXT_IN_10MIN); // health dropping
}
// Do special explosion damage effect
if ( bitsDamage & DMG_BLAST )
{
OnDamagedByExplosion( info );
}
return fTookDamage;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &info -
// damageAmount -
//-----------------------------------------------------------------------------
#define MIN_SHOCK_AND_CONFUSION_DAMAGE 30.0f
#define MIN_EAR_RINGING_DISTANCE 240.0f // 20 feet
//-----------------------------------------------------------------------------
// Purpose:
// Input : &info -
//-----------------------------------------------------------------------------
void CBasePlayer::OnDamagedByExplosion( const CTakeDamageInfo &info )
{
float lastDamage = info.GetDamage();
float distanceFromPlayer = 9999.0f;
CBaseEntity *inflictor = info.GetInflictor();
if ( inflictor )
{
Vector delta = GetAbsOrigin() - inflictor->GetAbsOrigin();
distanceFromPlayer = delta.Length();
}
bool ear_ringing = distanceFromPlayer < MIN_EAR_RINGING_DISTANCE ? true : false;
bool shock = lastDamage >= MIN_SHOCK_AND_CONFUSION_DAMAGE;
if ( !shock && !ear_ringing )
return;
int effect = shock ?
random->RandomInt( 35, 37 ) :
random->RandomInt( 32, 34 );
CSingleUserRecipientFilter user( this );
enginesound->SetPlayerDSP( user, effect, false );
}
//=========================================================
// PackDeadPlayerItems - call this when a player dies to
// pack up the appropriate weapons and ammo items, and to
// destroy anything that shouldn't be packed.
//
// This is pretty brute force :(
//=========================================================
void CBasePlayer::PackDeadPlayerItems( void )
{
int iWeaponRules;
int iAmmoRules;
int i;
CBaseCombatWeapon *rgpPackWeapons[ 20 ];// 20 hardcoded for now. How to determine exactly how many weapons we have?
int iPackAmmo[ MAX_AMMO_SLOTS + 1];
int iPW = 0;// index into packweapons array
int iPA = 0;// index into packammo array
memset(rgpPackWeapons, NULL, sizeof(rgpPackWeapons) );
memset(iPackAmmo, -1, sizeof(iPackAmmo) );
// get the game rules
iWeaponRules = g_pGameRules->DeadPlayerWeapons( this );
iAmmoRules = g_pGameRules->DeadPlayerAmmo( this );
if ( iWeaponRules == GR_PLR_DROP_GUN_NO && iAmmoRules == GR_PLR_DROP_AMMO_NO )
{
// nothing to pack. Remove the weapons and return. Don't call create on the box!
RemoveAllItems( true );
return;
}
// go through all of the weapons and make a list of the ones to pack
for ( i = 0 ; i < WeaponCount() ; i++ )
{
// there's a weapon here. Should I pack it?
CBaseCombatWeapon *pPlayerItem = GetWeapon( i );
if ( pPlayerItem )
{
switch( iWeaponRules )
{
case GR_PLR_DROP_GUN_ACTIVE:
if ( GetActiveWeapon() && pPlayerItem == GetActiveWeapon() )
{
// this is the active item. Pack it.
rgpPackWeapons[ iPW++ ] = pPlayerItem;
}
break;
case GR_PLR_DROP_GUN_ALL:
rgpPackWeapons[ iPW++ ] = pPlayerItem;
break;
default:
break;
}
}
}
// now go through ammo and make a list of which types to pack.
if ( iAmmoRules != GR_PLR_DROP_AMMO_NO )
{
for ( i = 0 ; i < MAX_AMMO_SLOTS ; i++ )
{
if ( GetAmmoCount( i ) > 0 )
{
// player has some ammo of this type.
switch ( iAmmoRules )
{
case GR_PLR_DROP_AMMO_ALL:
iPackAmmo[ iPA++ ] = i;
break;
case GR_PLR_DROP_AMMO_ACTIVE:
// WEAPONTODO: Make this work
/*
if ( GetActiveWeapon() && i == GetActiveWeapon()->m_iPrimaryAmmoType )
{
// this is the primary ammo type for the active weapon
iPackAmmo[ iPA++ ] = i;
}
else if ( GetActiveWeapon() && i == GetActiveWeapon()->m_iSecondaryAmmoType )
{
// this is the secondary ammo type for the active weapon
iPackAmmo[ iPA++ ] = i;
}
*/
break;
default:
break;
}
}
}
}
RemoveAllItems( true );// now strip off everything that wasn't handled by the code above.
}
void CBasePlayer::RemoveAllItems( bool removeSuit )
{
if (GetActiveWeapon())
{
ResetAutoaim( );
GetActiveWeapon()->Holster( );
}
Weapon_SetLast( NULL );
RemoveAllWeapons();
RemoveAllAmmo();
if ( removeSuit )
{
RemoveSuit();
}
UpdateClientData();
}
bool CBasePlayer::IsDead() const
{
return m_lifeState == LIFE_DEAD;
}
static float DamageForce( const Vector &size, float damage )
{
float force = damage * ((32 * 32 * 72.0) / (size.x * size.y * size.z)) * 5;
if ( force > 1000.0)
{
force = 1000.0;
}
return force;
}
const impactdamagetable_t &CBasePlayer::GetPhysicsImpactDamageTable()
{
return gDefaultPlayerImpactDamageTable;
}
int CBasePlayer::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
// set damage type sustained
m_bitsDamageType |= info.GetDamageType();
if ( !BaseClass::OnTakeDamage_Alive( info ) )
return 0;
CBaseEntity * attacker = info.GetAttacker();
if ( !attacker )
return 0;
Vector vecDir = vec3_origin;
if ( info.GetInflictor() )
{
vecDir = info.GetInflictor()->WorldSpaceCenter() - Vector ( 0, 0, 10 ) - WorldSpaceCenter();
VectorNormalize( vecDir );
}
if ( info.GetInflictor() && (GetMoveType() == MOVETYPE_WALK) &&
( !attacker->IsSolidFlagSet(FSOLID_TRIGGER)) )
{
Vector force = vecDir * -DamageForce( WorldAlignSize(), info.GetBaseDamage() );
if ( force.z > 250.0f )
{
force.z = 250.0f;
}
ApplyAbsVelocityImpulse( force );
}
// fire global game event
IGameEvent * event = gameeventmanager->CreateEvent( "player_hurt" );
if ( event )
{
event->SetInt("userid", GetUserID() );
event->SetInt("health", MAX(0, m_iHealth) );
event->SetInt("priority", 5 ); // HLTV event priority, not transmitted
if ( attacker->IsPlayer() )
{
CBasePlayer *player = ToBasePlayer( attacker );
event->SetInt("attacker", player->GetUserID() ); // hurt by other player
}
else
{
event->SetInt("attacker", 0 ); // hurt by "world"
}
gameeventmanager->FireEvent( event );
}
// Insert a combat sound so that nearby NPCs hear battle
if ( attacker->IsNPC() )
{
CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 512, 0.5, this );//<<TODO>>//magic number
}
return 1;
}
void CBasePlayer::Event_Killed( const CTakeDamageInfo &info )
{
CSound *pSound;
if ( Hints() )
{
Hints()->ResetHintTimers();
}
g_pGameRules->PlayerKilled( this, info );
gamestats->Event_PlayerKilled( this, info );
RumbleEffect( RUMBLE_STOP_ALL, 0, RUMBLE_FLAGS_NONE );
#if defined( WIN32 ) && !defined( _X360 )
// NVNT set the drag to zero in the case of underwater death.
HapticSetDrag(this,0);
#endif
ClearUseEntity();
// this client isn't going to be thinking for a while, so reset the sound until they respawn
pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt::ClientSoundIndex( edict() ) );
{
if ( pSound )
{
pSound->Reset();
}
}
// don't let the status bar glitch for players with <0 health.
if (m_iHealth < -99)
{
m_iHealth = 0;
}
// holster the current weapon
if ( GetActiveWeapon() )
{
GetActiveWeapon()->Holster();
}
SetAnimation( PLAYER_DIE );
if ( !IsObserver() )
{
SetViewOffset( VEC_DEAD_VIEWHEIGHT_SCALED( this ) );
}
m_lifeState = LIFE_DYING;
pl.deadflag = true;
AddSolidFlags( FSOLID_NOT_SOLID );
// force contact points to get flushed if no longer valid
// UNDONE: Always do this on RecheckCollisionFilter() ?
IPhysicsObject *pObject = VPhysicsGetObject();
if ( pObject )
{
pObject->RecheckContactPoints();
}
SetMoveType( MOVETYPE_FLYGRAVITY );
SetGroundEntity( NULL );
// clear out the suit message cache so we don't keep chattering
SetSuitUpdate(NULL, false, 0);
// reset FOV
SetFOV( this, 0 );
if ( FlashlightIsOn() )
{
FlashlightTurnOff();
}
m_flDeathTime = gpGlobals->curtime;
ClearLastKnownArea();
BaseClass::Event_Killed( info );
}
void CBasePlayer::Event_Dying( const CTakeDamageInfo& info )
{
// NOT GIBBED, RUN THIS CODE
DeathSound( info );
// The dead body rolls out of the vehicle.
if ( IsInAVehicle() )
{
LeaveVehicle();
}
QAngle angles = GetLocalAngles();
angles.x = 0;
angles.z = 0;
SetLocalAngles( angles );
SetThink(&CBasePlayer::PlayerDeathThink);
SetNextThink( gpGlobals->curtime + 0.1f );
BaseClass::Event_Dying( info );
}
// Set the activity based on an event or current state
void CBasePlayer::SetAnimation( PLAYER_ANIM playerAnim )
{
int animDesired;
char szAnim[64];
float speed;
speed = GetAbsVelocity().Length2D();
if (GetFlags() & (FL_FROZEN|FL_ATCONTROLS))
{
speed = 0;
playerAnim = PLAYER_IDLE;
}
Activity idealActivity = ACT_WALK;// TEMP!!!!!
// This could stand to be redone. Why is playerAnim abstracted from activity? (sjb)
if (playerAnim == PLAYER_JUMP)
{
idealActivity = ACT_HOP;
}
else if (playerAnim == PLAYER_SUPERJUMP)
{
idealActivity = ACT_LEAP;
}
else if (playerAnim == PLAYER_DIE)
{
if ( m_lifeState == LIFE_ALIVE )
{
idealActivity = GetDeathActivity();
}
}
else if (playerAnim == PLAYER_ATTACK1)
{
if ( m_Activity == ACT_HOVER ||
m_Activity == ACT_SWIM ||
m_Activity == ACT_HOP ||
m_Activity == ACT_LEAP ||
m_Activity == ACT_DIESIMPLE )
{
idealActivity = m_Activity;
}
else
{
idealActivity = ACT_RANGE_ATTACK1;
}
}
else if (playerAnim == PLAYER_IDLE || playerAnim == PLAYER_WALK)
{
if ( !( GetFlags() & FL_ONGROUND ) && (m_Activity == ACT_HOP || m_Activity == ACT_LEAP) ) // Still jumping
{
idealActivity = m_Activity;
}
else if ( GetWaterLevel() > 1 )
{
if ( speed == 0 )
idealActivity = ACT_HOVER;
else
idealActivity = ACT_SWIM;
}
else
{
idealActivity = ACT_WALK;
}
}
if (idealActivity == ACT_RANGE_ATTACK1)
{
if ( GetFlags() & FL_DUCKING ) // crouching
{
Q_strncpy( szAnim, "crouch_shoot_" ,sizeof(szAnim));
}
else
{
Q_strncpy( szAnim, "ref_shoot_" ,sizeof(szAnim));
}
Q_strncat( szAnim, m_szAnimExtension ,sizeof(szAnim), COPY_ALL_CHARACTERS );
animDesired = LookupSequence( szAnim );
if (animDesired == -1)
animDesired = 0;
if ( GetSequence() != animDesired || !SequenceLoops() )
{
SetCycle( 0 );
}
// Tracker 24588: In single player when firing own weapon this causes eye and punchangle to jitter
//if (!SequenceLoops())
//{
// IncrementInterpolationFrame();
//}
SetActivity( idealActivity );
ResetSequence( animDesired );
}
else if (idealActivity == ACT_WALK)
{
if (GetActivity() != ACT_RANGE_ATTACK1 || IsActivityFinished())
{
if ( GetFlags() & FL_DUCKING ) // crouching
{
Q_strncpy( szAnim, "crouch_aim_" ,sizeof(szAnim));
}
else
{
Q_strncpy( szAnim, "ref_aim_" ,sizeof(szAnim));
}
Q_strncat( szAnim, m_szAnimExtension,sizeof(szAnim), COPY_ALL_CHARACTERS );
animDesired = LookupSequence( szAnim );
if (animDesired == -1)
animDesired = 0;
SetActivity( ACT_WALK );
}
else
{
animDesired = GetSequence();
}
}
else
{
if ( GetActivity() == idealActivity)
return;
SetActivity( idealActivity );
animDesired = SelectWeightedSequence( m_Activity );
// Already using the desired animation?
if (GetSequence() == animDesired)
return;
ResetSequence( animDesired );
SetCycle( 0 );
return;
}
// Already using the desired animation?
if (GetSequence() == animDesired)
return;
//Msg( "Set animation to %d\n", animDesired );
// Reset to first frame of desired animation
ResetSequence( animDesired );
SetCycle( 0 );
}
/*
===========
WaterMove
============
*/
#ifdef HL2_DLL
// test for HL2 drowning damage increase (aux power used instead)
#define AIRTIME 7 // lung full of air lasts this many seconds
#define DROWNING_DAMAGE_INITIAL 10
#define DROWNING_DAMAGE_MAX 10
#else
#define AIRTIME 12 // lung full of air lasts this many seconds
#define DROWNING_DAMAGE_INITIAL 2
#define DROWNING_DAMAGE_MAX 5
#endif
void CBasePlayer::WaterMove()
{
if ( ( GetMoveType() == MOVETYPE_NOCLIP ) && !GetMoveParent() )
{
m_AirFinished = gpGlobals->curtime + AIRTIME;
return;
}
if ( m_iHealth < 0 || !IsAlive() )
{
UpdateUnderwaterState();
return;
}
// waterlevel 0 - not in water (WL_NotInWater)
// waterlevel 1 - feet in water (WL_Feet)
// waterlevel 2 - waist in water (WL_Waist)
// waterlevel 3 - head in water (WL_Eyes)
if (GetWaterLevel() != WL_Eyes || CanBreatheUnderwater())
{
// not underwater
// play 'up for air' sound
if (m_AirFinished < gpGlobals->curtime)
{
EmitSound( "Player.DrownStart" );
}
m_AirFinished = gpGlobals->curtime + AIRTIME;
m_nDrownDmgRate = DROWNING_DAMAGE_INITIAL;
// if we took drowning damage, give it back slowly
if (m_idrowndmg > m_idrownrestored)
{
// set drowning damage bit. hack - dmg_drownrecover actually
// makes the time based damage code 'give back' health over time.
// make sure counter is cleared so we start count correctly.
// NOTE: this actually causes the count to continue restarting
// until all drowning damage is healed.
m_bitsDamageType |= DMG_DROWNRECOVER;
m_bitsDamageType &= ~DMG_DROWN;
m_rgbTimeBasedDamage[itbd_DrownRecover] = 0;
}
}
else
{ // fully under water
// stop restoring damage while underwater
m_bitsDamageType &= ~DMG_DROWNRECOVER;
m_rgbTimeBasedDamage[itbd_DrownRecover] = 0;
if (m_AirFinished < gpGlobals->curtime && !(GetFlags() & FL_GODMODE) ) // drown!
{
if (m_PainFinished < gpGlobals->curtime)
{
// take drowning damage
m_nDrownDmgRate += 1;
if (m_nDrownDmgRate > DROWNING_DAMAGE_MAX)
{
m_nDrownDmgRate = DROWNING_DAMAGE_MAX;
}
OnTakeDamage( CTakeDamageInfo( GetContainingEntity(INDEXENT(0)), GetContainingEntity(INDEXENT(0)), m_nDrownDmgRate, DMG_DROWN ) );
m_PainFinished = gpGlobals->curtime + 1;
// track drowning damage, give it back when
// player finally takes a breath
m_idrowndmg += m_nDrownDmgRate;
}
}
else
{
m_bitsDamageType &= ~DMG_DROWN;
}
}
UpdateUnderwaterState();
}
// true if the player is attached to a ladder
bool CBasePlayer::IsOnLadder( void )
{
return (GetMoveType() == MOVETYPE_LADDER);
}
float CBasePlayer::GetWaterJumpTime() const
{
return m_flWaterJumpTime;
}
void CBasePlayer::SetWaterJumpTime( float flWaterJumpTime )
{
m_flWaterJumpTime = flWaterJumpTime;
}
float CBasePlayer::GetSwimSoundTime( void ) const
{
return m_flSwimSoundTime;
}
void CBasePlayer::SetSwimSoundTime( float flSwimSoundTime )
{
m_flSwimSoundTime = flSwimSoundTime;
}
void CBasePlayer::ShowViewPortPanel( const char * name, bool bShow, KeyValues *data )
{
CSingleUserRecipientFilter filter( this );
filter.MakeReliable();
int count = 0;
KeyValues *subkey = NULL;
if ( data )
{
subkey = data->GetFirstSubKey();
while ( subkey )
{
count++; subkey = subkey->GetNextKey();
}
subkey = data->GetFirstSubKey(); // reset
}
UserMessageBegin( filter, "VGUIMenu" );
WRITE_STRING( name ); // menu name
WRITE_BYTE( bShow?1:0 );
WRITE_BYTE( count );
// write additional data (be careful not more than 192 bytes!)
while ( subkey )
{
WRITE_STRING( subkey->GetName() );
WRITE_STRING( subkey->GetString() );
subkey = subkey->GetNextKey();
}
MessageEnd();
}
void CBasePlayer::PlayerDeathThink(void)
{
float flForward;
SetNextThink( gpGlobals->curtime + 0.1f );
if (GetFlags() & FL_ONGROUND)
{
flForward = GetAbsVelocity().Length() - 20;
if (flForward <= 0)
{
SetAbsVelocity( vec3_origin );
}
else
{
Vector vecNewVelocity = GetAbsVelocity();
VectorNormalize( vecNewVelocity );
vecNewVelocity *= flForward;
SetAbsVelocity( vecNewVelocity );
}
}
if ( HasWeapons() )
{
// we drop the guns here because weapons that have an area effect and can kill their user
// will sometimes crash coming back from CBasePlayer::Killed() if they kill their owner because the
// player class sometimes is freed. It's safer to manipulate the weapons once we know
// we aren't calling into any of their code anymore through the player pointer.
PackDeadPlayerItems();
}
if (GetModelIndex() && (!IsSequenceFinished()) && (m_lifeState == LIFE_DYING))
{
StudioFrameAdvance( );
m_iRespawnFrames++;
if ( m_iRespawnFrames < 60 ) // animations should be no longer than this
return;
}
if (m_lifeState == LIFE_DYING)
{
m_lifeState = LIFE_DEAD;
m_flDeathAnimTime = gpGlobals->curtime;
}
StopAnimation();
IncrementInterpolationFrame();
m_flPlaybackRate = 0.0;
int fAnyButtonDown = (m_nButtons & ~IN_SCORE);
// Strip out the duck key from this check if it's toggled
if ( (fAnyButtonDown & IN_DUCK) && GetToggledDuckState())
{
fAnyButtonDown &= ~IN_DUCK;
}
// wait for all buttons released
if (m_lifeState == LIFE_DEAD)
{
if (fAnyButtonDown)
return;
if ( g_pGameRules->FPlayerCanRespawn( this ) )
{
m_lifeState = LIFE_RESPAWNABLE;
}
return;
}
// if the player has been dead for one second longer than allowed by forcerespawn,
// forcerespawn isn't on. Send the player off to an intermission camera until they
// choose to respawn.
if ( g_pGameRules->IsMultiplayer() && ( gpGlobals->curtime > (m_flDeathTime + DEATH_ANIMATION_TIME) ) && !IsObserver() )
{
// go to dead camera.
StartObserverMode( m_iObserverLastMode );
}
// wait for any button down, or mp_forcerespawn is set and the respawn time is up
if (!fAnyButtonDown
&& !( g_pGameRules->IsMultiplayer() && forcerespawn.GetInt() > 0 && (gpGlobals->curtime > (m_flDeathTime + 5))) )
return;
m_nButtons = 0;
m_iRespawnFrames = 0;
//Msg( "Respawn\n");
respawn( this, !IsObserver() );// don't copy a corpse if we're in deathcam.
SetNextThink( TICK_NEVER_THINK );
}
/*
//=========================================================
// StartDeathCam - find an intermission spot and send the
// player off into observer mode
//=========================================================
void CBasePlayer::StartDeathCam( void )
{
CBaseEntity *pSpot, *pNewSpot;
int iRand;
if ( GetViewOffset() == vec3_origin )
{
// don't accept subsequent attempts to StartDeathCam()
return;
}
pSpot = gEntList.FindEntityByClassname( NULL, "info_intermission");
if ( pSpot )
{
// at least one intermission spot in the world.
iRand = random->RandomInt( 0, 3 );
while ( iRand > 0 )
{
pNewSpot = gEntList.FindEntityByClassname( pSpot, "info_intermission");
if ( pNewSpot )
{
pSpot = pNewSpot;
}
iRand--;
}
CreateCorpse();
StartObserverMode( pSpot->GetAbsOrigin(), pSpot->GetAbsAngles() );
}
else
{
// no intermission spot. Push them up in the air, looking down at their corpse
trace_t tr;
CreateCorpse();
UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, 128 ),
MASK_PLAYERSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
QAngle angles;
VectorAngles( GetAbsOrigin() - tr.endpos, angles );
StartObserverMode( tr.endpos, angles );
return;
}
} */
void CBasePlayer::StopObserverMode()
{
m_bForcedObserverMode = false;
m_afPhysicsFlags &= ~PFLAG_OBSERVER;
if ( m_iObserverMode == OBS_MODE_NONE )
return;
if ( m_iObserverMode > OBS_MODE_DEATHCAM )
{
m_iObserverLastMode = m_iObserverMode;
}
m_iObserverMode.Set( OBS_MODE_NONE );
ShowViewPortPanel( "specmenu", false );
ShowViewPortPanel( "specgui", false );
ShowViewPortPanel( "overview", false );
}
bool CBasePlayer::StartObserverMode(int mode)
{
if ( !IsObserver() )
{
// set position to last view offset
SetAbsOrigin( GetAbsOrigin() + GetViewOffset() );
SetViewOffset( vec3_origin );
}
Assert( mode > OBS_MODE_NONE );
m_afPhysicsFlags |= PFLAG_OBSERVER;
// Holster weapon immediately, to allow it to cleanup
if ( GetActiveWeapon() )
GetActiveWeapon()->Holster();
// clear out the suit message cache so we don't keep chattering
SetSuitUpdate(NULL, FALSE, 0);
SetGroundEntity( (CBaseEntity *)NULL );
RemoveFlag( FL_DUCKING );
AddSolidFlags( FSOLID_NOT_SOLID );
SetObserverMode( mode );
if ( gpGlobals->eLoadType != MapLoad_Background )
{
ShowViewPortPanel( "specgui" , ModeWantsSpectatorGUI(mode) );
}
// Setup flags
m_Local.m_iHideHUD = HIDEHUD_HEALTH;
m_takedamage = DAMAGE_NO;
// Become invisible
AddEffects( EF_NODRAW );
m_iHealth = 1;
m_lifeState = LIFE_DEAD; // Can't be dead, otherwise movement doesn't work right.
m_flDeathAnimTime = gpGlobals->curtime;
pl.deadflag = true;
return true;
}
bool CBasePlayer::SetObserverMode(int mode )
{
if ( mode < OBS_MODE_NONE || mode >= NUM_OBSERVER_MODES )
return false;
// check mp_forcecamera settings for dead players
if ( mode > OBS_MODE_FIXED && GetTeamNumber() > TEAM_SPECTATOR )
{
switch ( mp_forcecamera.GetInt() )
{
case OBS_ALLOW_ALL : break; // no restrictions
case OBS_ALLOW_TEAM : mode = OBS_MODE_IN_EYE; break;
case OBS_ALLOW_NONE : mode = OBS_MODE_FIXED; break; // don't allow anything
}
}
if ( m_iObserverMode > OBS_MODE_DEATHCAM )
{
// remember mode if we were really spectating before
m_iObserverLastMode = m_iObserverMode;
}
m_iObserverMode = mode;
switch ( mode )
{
case OBS_MODE_NONE:
case OBS_MODE_FIXED :
case OBS_MODE_DEATHCAM :
SetFOV( this, 0 ); // Reset FOV
SetViewOffset( vec3_origin );
SetMoveType( MOVETYPE_NONE );
break;
case OBS_MODE_CHASE :
case OBS_MODE_IN_EYE :
// udpate FOV and viewmodels
SetObserverTarget( m_hObserverTarget );
SetMoveType( MOVETYPE_OBSERVER );
break;
//=============================================================================
// HPE_BEGIN:
// [menglish] Added freeze cam to the setter. Uses same setup as the roaming mode
//=============================================================================
case OBS_MODE_ROAMING :
case OBS_MODE_FREEZECAM :
SetFOV( this, 0 ); // Reset FOV
SetObserverTarget( m_hObserverTarget );
SetViewOffset( vec3_origin );
SetMoveType( MOVETYPE_OBSERVER );
break;
//=============================================================================
// HPE_END
//=============================================================================
}
CheckObserverSettings();
return true;
}
int CBasePlayer::GetObserverMode()
{
return m_iObserverMode;
}
void CBasePlayer::ForceObserverMode(int mode)
{
int tempMode = OBS_MODE_ROAMING;
if ( m_iObserverMode == mode )
return;
// don't change last mode if already in forced mode
if ( m_bForcedObserverMode )
{
tempMode = m_iObserverLastMode;
}
SetObserverMode( mode );
if ( m_bForcedObserverMode )
{
m_iObserverLastMode = tempMode;
}
m_bForcedObserverMode = true;
}
void CBasePlayer::CheckObserverSettings()
{
// check if we are in forced mode and may go back to old mode
if ( m_bForcedObserverMode )
{
CBaseEntity * target = m_hObserverTarget;
if ( !IsValidObserverTarget(target) )
{
// if old target is still invalid, try to find valid one
target = FindNextObserverTarget( false );
}
if ( target )
{
// we found a valid target
m_bForcedObserverMode = false; // disable force mode
SetObserverMode( m_iObserverLastMode ); // switch to last mode
SetObserverTarget( target ); // goto target
// TODO check for HUD icons
return;
}
else
{
// else stay in forced mode, no changes
return;
}
}
// make sure our last mode is valid
if ( m_iObserverLastMode < OBS_MODE_FIXED )
{
m_iObserverLastMode = OBS_MODE_ROAMING;
}
// check if our spectating target is still a valid one
if ( m_iObserverMode == OBS_MODE_IN_EYE || m_iObserverMode == OBS_MODE_CHASE || m_iObserverMode == OBS_MODE_FIXED )
{
ValidateCurrentObserverTarget();
CBasePlayer *target = ToBasePlayer( m_hObserverTarget.Get() );
// for ineye mode we have to copy several data to see exactly the same
if ( target && m_iObserverMode == OBS_MODE_IN_EYE )
{
int flagMask = FL_ONGROUND | FL_DUCKING ;
int flags = target->GetFlags() & flagMask;
if ( (GetFlags() & flagMask) != flags )
{
flags |= GetFlags() & (~flagMask); // keep other flags
ClearFlags();
AddFlag( flags );
}
if ( target->GetViewOffset() != GetViewOffset() )
{
SetViewOffset( target->GetViewOffset() );
}
}
// Update the fog.
if ( target )
{
if ( target->m_Local.m_PlayerFog.m_hCtrl.Get() != m_Local.m_PlayerFog.m_hCtrl.Get() )
{
m_Local.m_PlayerFog.m_hCtrl.Set( target->m_Local.m_PlayerFog.m_hCtrl.Get() );
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBasePlayer::ValidateCurrentObserverTarget( void )
{
if ( !IsValidObserverTarget( m_hObserverTarget.Get() ) )
{
// our target is not valid, try to find new target
CBaseEntity * target = FindNextObserverTarget( false );
if ( target )
{
// switch to new valid target
SetObserverTarget( target );
}
else
{
// couldn't find new target, switch to temporary mode
if ( mp_forcecamera.GetInt() == OBS_ALLOW_ALL )
{
// let player roam around
ForceObserverMode( OBS_MODE_ROAMING );
}
else
{
// fix player view right where it is
ForceObserverMode( OBS_MODE_FIXED );
m_hObserverTarget.Set( NULL ); // no traget to follow
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBasePlayer::AttemptToExitFreezeCam( void )
{
StartObserverMode( OBS_MODE_DEATHCAM );
}
bool CBasePlayer::StartReplayMode( float fDelay, float fDuration, int iEntity )
{
if ( ( sv_maxreplay == NULL ) || ( sv_maxreplay->GetFloat() <= 0 ) )
return false;
m_fDelay = fDelay;
m_fReplayEnd = gpGlobals->curtime + fDuration;
m_iReplayEntity = iEntity;
return true;
}
void CBasePlayer::StopReplayMode()
{
m_fDelay = 0.0f;
m_fReplayEnd = -1;
m_iReplayEntity = 0;
}
int CBasePlayer::GetDelayTicks()
{
if ( m_fReplayEnd > gpGlobals->curtime )
{
return TIME_TO_TICKS( m_fDelay );
}
else
{
if ( m_fDelay > 0.0f )
StopReplayMode();
return 0;
}
}
int CBasePlayer::GetReplayEntity()
{
return m_iReplayEntity;
}
CBaseEntity * CBasePlayer::GetObserverTarget()
{
return m_hObserverTarget.Get();
}
void CBasePlayer::ObserverUse( bool bIsPressed )
{
#ifndef _XBOX
if ( !HLTVDirector()->IsActive() )
return;
if ( GetTeamNumber() != TEAM_SPECTATOR )
return; // only pure spectators can play cameraman
if ( !bIsPressed )
return;
bool bIsHLTV = HLTVDirector()->IsActive();
if ( bIsHLTV )
{
int iCameraManIndex = HLTVDirector()->GetCameraMan();
if ( iCameraManIndex == 0 )
{
// turn camera on
HLTVDirector()->SetCameraMan( entindex() );
}
else if ( iCameraManIndex == entindex() )
{
// turn camera off
HLTVDirector()->SetCameraMan( 0 );
}
else
{
ClientPrint( this, HUD_PRINTTALK, "Camera in use by other player." );
}
}
/* UTIL_SayText( "Spectator can not USE anything", this );
Vector dir,end;
Vector start = GetAbsOrigin();
AngleVectors( GetAbsAngles(), &dir );
VectorNormalize( dir );
VectorMA( start, 32.0f, dir, end );
trace_t tr;
UTIL_TraceLine( start, end, MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr );
if ( tr.fraction == 1.0f )
return; // no obstacles in spectators way
VectorMA( start, 128.0f, dir, end );
Ray_t ray;
ray.Init( end, start, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX );
UTIL_TraceRay( ray, MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr );
if ( tr.startsolid || tr.allsolid )
return;
SetAbsOrigin( tr.endpos ); */
#endif
}
void CBasePlayer::JumptoPosition(const Vector &origin, const QAngle &angles)
{
SetAbsOrigin( origin );
SetAbsVelocity( vec3_origin ); // stop movement
SetLocalAngles( angles );
SnapEyeAngles( angles );
}
bool CBasePlayer::SetObserverTarget(CBaseEntity *target)
{
if ( !IsValidObserverTarget( target ) )
return false;
// set new target
m_hObserverTarget.Set( target );
// reset fov to default
SetFOV( this, 0 );
if ( m_iObserverMode == OBS_MODE_ROAMING )
{
Vector dir, end;
Vector start = target->EyePosition();
AngleVectors( target->EyeAngles(), &dir );
VectorNormalize( dir );
VectorMA( start, -64.0f, dir, end );
Ray_t ray;
ray.Init( start, end, VEC_DUCK_HULL_MIN , VEC_DUCK_HULL_MAX );
trace_t tr;
UTIL_TraceRay( ray, MASK_PLAYERSOLID, target, COLLISION_GROUP_PLAYER_MOVEMENT, &tr );
JumptoPosition( tr.endpos, target->EyeAngles() );
}
return true;
}
bool CBasePlayer::IsValidObserverTarget(CBaseEntity * target)
{
if ( target == NULL )
return false;
// MOD AUTHORS: Add checks on target here or in derived method
if ( !target->IsPlayer() ) // only track players
return false;
CBasePlayer * player = ToBasePlayer( target );
/* Don't spec observers or players who haven't picked a class yet
if ( player->IsObserver() )
return false; */
if( player == this )
return false; // We can't observe ourselves.
if ( player->IsEffectActive( EF_NODRAW ) ) // don't watch invisible players
return false;
if ( player->m_lifeState == LIFE_RESPAWNABLE ) // target is dead, waiting for respawn
return false;
if ( player->m_lifeState == LIFE_DEAD || player->m_lifeState == LIFE_DYING )
{
if ( (player->m_flDeathTime + DEATH_ANIMATION_TIME ) < gpGlobals->curtime )
{
return false; // allow watching until 3 seconds after death to see death animation
}
}
// check forcecamera settings for active players
if ( GetTeamNumber() != TEAM_SPECTATOR )
{
switch ( mp_forcecamera.GetInt() )
{
case OBS_ALLOW_ALL : break;
case OBS_ALLOW_TEAM : if ( GetTeamNumber() != target->GetTeamNumber() )
return false;
break;
case OBS_ALLOW_NONE : return false;
}
}
return true; // passed all test
}
int CBasePlayer::GetNextObserverSearchStartPoint( bool bReverse )
{
int iDir = bReverse ? -1 : 1;
int startIndex;
if ( m_hObserverTarget )
{
// start using last followed player
startIndex = m_hObserverTarget->entindex();
}
else
{
// start using own player index
startIndex = this->entindex();
}
startIndex += iDir;
if (startIndex > gpGlobals->maxClients)
startIndex = 1;
else if (startIndex < 1)
startIndex = gpGlobals->maxClients;
return startIndex;
}
CBaseEntity * CBasePlayer::FindNextObserverTarget(bool bReverse)
{
// MOD AUTHORS: Modify the logic of this function if you want to restrict the observer to watching
// only a subset of the players. e.g. Make it check the target's team.
/* if ( m_flNextFollowTime && m_flNextFollowTime > gpGlobals->time )
{
return;
}
m_flNextFollowTime = gpGlobals->time + 0.25;
*/ // TODO move outside this function
int startIndex = GetNextObserverSearchStartPoint( bReverse );
int currentIndex = startIndex;
int iDir = bReverse ? -1 : 1;
do
{
CBaseEntity * nextTarget = UTIL_PlayerByIndex( currentIndex );
if ( IsValidObserverTarget( nextTarget ) )
{
return nextTarget; // found next valid player
}
currentIndex += iDir;
// Loop through the clients
if (currentIndex > gpGlobals->maxClients)
currentIndex = 1;
else if (currentIndex < 1)
currentIndex = gpGlobals->maxClients;
} while ( currentIndex != startIndex );
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose: Return true if this object can be +used by the player
//-----------------------------------------------------------------------------
bool CBasePlayer::IsUseableEntity( CBaseEntity *pEntity, unsigned int requiredCaps )
{
if ( pEntity )
{
int caps = pEntity->ObjectCaps();
if ( caps & (FCAP_IMPULSE_USE|FCAP_CONTINUOUS_USE|FCAP_ONOFF_USE|FCAP_DIRECTIONAL_USE) )
{
if ( (caps & requiredCaps) == requiredCaps )
{
return true;
}
}
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CBasePlayer::CanPickupObject( CBaseEntity *pObject, float massLimit, float sizeLimit )
{
// UNDONE: Make this virtual and move to HL2 player
#ifdef HL2_DLL
//Must be valid
if ( pObject == NULL )
return false;
//Must move with physics
if ( pObject->GetMoveType() != MOVETYPE_VPHYSICS )
return false;
IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
int count = pObject->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
//Must have a physics object
if (!count)
return false;
float objectMass = 0;
bool checkEnable = false;
for ( int i = 0; i < count; i++ )
{
objectMass += pList[i]->GetMass();
if ( !pList[i]->IsMoveable() )
{
checkEnable = true;
}
if ( pList[i]->GetGameFlags() & FVPHYSICS_NO_PLAYER_PICKUP )
return false;
if ( pList[i]->IsHinged() )
return false;
}
//Msg( "Target mass: %f\n", pPhys->GetMass() );
//Must be under our threshold weight
if ( massLimit > 0 && objectMass > massLimit )
return false;
if ( checkEnable )
{
// Allowing picking up of bouncebombs.
CBounceBomb *pBomb = dynamic_cast<CBounceBomb*>(pObject);
if( pBomb )
return true;
// Allow pickup of phys props that are motion enabled on player pickup
CPhysicsProp *pProp = dynamic_cast<CPhysicsProp*>(pObject);
CPhysBox *pBox = dynamic_cast<CPhysBox*>(pObject);
if ( !pProp && !pBox )
return false;
if ( pProp && !(pProp->HasSpawnFlags( SF_PHYSPROP_ENABLE_ON_PHYSCANNON )) )
return false;
if ( pBox && !(pBox->HasSpawnFlags( SF_PHYSBOX_ENABLE_ON_PHYSCANNON )) )
return false;
}
if ( sizeLimit > 0 )
{
const Vector &size = pObject->CollisionProp()->OBBSize();
if ( size.x > sizeLimit || size.y > sizeLimit || size.z > sizeLimit )
return false;
}
return true;
#else
return false;
#endif
}
float CBasePlayer::GetHeldObjectMass( IPhysicsObject *pHeldObject )
{
return 0;
}
//-----------------------------------------------------------------------------
// Purpose: Server side of jumping rules. Most jumping logic is already
// handled in shared gamemovement code. Put stuff here that should
// only be done server side.
//-----------------------------------------------------------------------------
void CBasePlayer::Jump()
{
}
void CBasePlayer::Duck( )
{
if (m_nButtons & IN_DUCK)
{
if ( m_Activity != ACT_LEAP )
{
SetAnimation( PLAYER_WALK );
}
}
}
//
// ID's player as such.
//
Class_T CBasePlayer::Classify ( void )
{
return CLASS_PLAYER;
}
void CBasePlayer::ResetFragCount()
{
m_iFrags = 0;
pl.frags = m_iFrags;
}
void CBasePlayer::IncrementFragCount( int nCount )
{
m_iFrags += nCount;
pl.frags = m_iFrags;
}
void CBasePlayer::ResetDeathCount()
{
m_iDeaths = 0;
pl.deaths = m_iDeaths;
}
void CBasePlayer::IncrementDeathCount( int nCount )
{
m_iDeaths += nCount;
pl.deaths = m_iDeaths;
}
void CBasePlayer::AddPoints( int score, bool bAllowNegativeScore )
{
// Positive score always adds
if ( score < 0 )
{
if ( !bAllowNegativeScore )
{
if ( m_iFrags < 0 ) // Can't go more negative
return;
if ( -score > m_iFrags ) // Will this go negative?
{
score = -m_iFrags; // Sum will be 0
}
}
}
m_iFrags += score;
pl.frags = m_iFrags;
}
void CBasePlayer::AddPointsToTeam( int score, bool bAllowNegativeScore )
{
if ( GetTeam() )
{
GetTeam()->AddScore( score );
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : int
//-----------------------------------------------------------------------------
int CBasePlayer::GetCommandContextCount( void ) const
{
return m_CommandContext.Count();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : index -
// Output : CCommandContext
//-----------------------------------------------------------------------------
CCommandContext *CBasePlayer::GetCommandContext( int index )
{
if ( index < 0 || index >= m_CommandContext.Count() )
return NULL;
return &m_CommandContext[ index ];
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CCommandContext *CBasePlayer::AllocCommandContext( void )
{
int idx = m_CommandContext.AddToTail();
if ( m_CommandContext.Count() > 1000 )
{
Assert( 0 );
}
return &m_CommandContext[ idx ];
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : index -
//-----------------------------------------------------------------------------
void CBasePlayer::RemoveCommandContext( int index )
{
m_CommandContext.Remove( index );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBasePlayer::RemoveAllCommandContexts()
{
m_CommandContext.RemoveAll();
}
//-----------------------------------------------------------------------------
// Purpose: Removes all existing contexts, but leaves the last one around ( or creates it if it doesn't exist -- which would be a bug )
//-----------------------------------------------------------------------------
CCommandContext *CBasePlayer::RemoveAllCommandContextsExceptNewest( void )
{
int count = m_CommandContext.Count();
int toRemove = count - 1;
if ( toRemove > 0 )
{
m_CommandContext.RemoveMultiple( 0, toRemove );
}
if ( !m_CommandContext.Count() )
{
Assert( 0 );
CCommandContext *ctx = AllocCommandContext();
Q_memset( ctx, 0, sizeof( *ctx ) );
}
return &m_CommandContext[ 0 ];
}
//-----------------------------------------------------------------------------
// Purpose: Replaces the first nCommands CUserCmds in the context with the ones passed in -- this is used to help meter out CUserCmds over the number of simulation ticks on the server
//-----------------------------------------------------------------------------
void CBasePlayer::ReplaceContextCommands( CCommandContext *ctx, CUserCmd *pCommands, int nCommands )
{
// Blow away all of the commands
ctx->cmds.RemoveAll();
ctx->numcmds = nCommands;
ctx->totalcmds = nCommands;
ctx->dropped_packets = 0; // meaningless in this context
// Add them in so the most recent is at slot 0
for ( int i = nCommands - 1; i >= 0; --i )
{
ctx->cmds.AddToTail( pCommands[ i ] );
}
}
//-----------------------------------------------------------------------------
// Purpose: Determine how much time we will be running this frame
// Output : float
//-----------------------------------------------------------------------------
int CBasePlayer::DetermineSimulationTicks( void )
{
int command_context_count = GetCommandContextCount();
int context_number;
int simulation_ticks = 0;
// Determine how much time we will be running this frame and fixup player clock as needed
for ( context_number = 0; context_number < command_context_count; context_number++ )
{
CCommandContext const *ctx = GetCommandContext( context_number );
Assert( ctx );
Assert( ctx->numcmds > 0 );
Assert( ctx->dropped_packets >= 0 );
// Determine how long it will take to run those packets
simulation_ticks += ctx->numcmds + ctx->dropped_packets;
}
return simulation_ticks;
}
// 2 ticks ahead or behind current clock means we need to fix clock on client
static ConVar sv_clockcorrection_msecs( "sv_clockcorrection_msecs", "60", 0, "The server tries to keep each player's m_nTickBase withing this many msecs of the server absolute tickcount" );
static ConVar sv_playerperfhistorycount( "sv_playerperfhistorycount", "60", 0, "Number of samples to maintain in player perf history", true, 1.0f, true, 128.0 );
//-----------------------------------------------------------------------------
// Purpose: Based upon amount of time in simulation time, adjust m_nTickBase so that
// we just end at the end of the current frame (so the player is basically on clock
// with the server)
// Input : simulation_ticks -
//-----------------------------------------------------------------------------
void CBasePlayer::AdjustPlayerTimeBase( int simulation_ticks )
{
Assert( simulation_ticks >= 0 );
if ( simulation_ticks < 0 )
return;
CPlayerSimInfo *pi = NULL;
if ( sv_playerperfhistorycount.GetInt() > 0 )
{
while ( m_vecPlayerSimInfo.Count() > sv_playerperfhistorycount.GetInt() )
{
m_vecPlayerSimInfo.Remove( m_vecPlayerSimInfo.Head() );
}
pi = &m_vecPlayerSimInfo[ m_vecPlayerSimInfo.AddToTail() ];
}
// Start in the past so that we get to the sv.time that we'll hit at the end of the
// frame, just as we process the final command
if ( gpGlobals->maxClients == 1 )
{
// set TickBase so that player simulation tick matches gpGlobals->tickcount after
// all commands have been executed
m_nTickBase = gpGlobals->tickcount - simulation_ticks + gpGlobals->simTicksThisFrame;
}
else // multiplayer
{
float flCorrectionSeconds = clamp( sv_clockcorrection_msecs.GetFloat() / 1000.0f, 0.0f, 1.0f );
int nCorrectionTicks = TIME_TO_TICKS( flCorrectionSeconds );
// Set the target tick flCorrectionSeconds (rounded to ticks) ahead in the future. this way the client can
// alternate around this target tick without getting smaller than gpGlobals->tickcount.
// After running the commands simulation time should be equal or after current gpGlobals->tickcount,
// otherwise the simulation time drops out of the client side interpolated var history window.
int nIdealFinalTick = gpGlobals->tickcount + nCorrectionTicks;
int nEstimatedFinalTick = m_nTickBase + simulation_ticks;
// If client gets ahead of this, we'll need to correct
int too_fast_limit = nIdealFinalTick + nCorrectionTicks;
// If client falls behind this, we'll also need to correct
int too_slow_limit = nIdealFinalTick - nCorrectionTicks;
// See if we are too fast
if ( nEstimatedFinalTick > too_fast_limit ||
nEstimatedFinalTick < too_slow_limit )
{
int nCorrectedTick = nIdealFinalTick - simulation_ticks + gpGlobals->simTicksThisFrame;
if ( pi )
{
pi->m_nTicksCorrected = nCorrectionTicks;
}
m_nTickBase = nCorrectedTick;
}
}
if ( pi )
{
pi->m_flFinalSimulationTime = TICKS_TO_TIME( m_nTickBase + simulation_ticks + gpGlobals->simTicksThisFrame );
}
}
void CBasePlayer::RunNullCommand( void )
{
CUserCmd cmd; // NULL command
// Store off the globals.. they're gonna get whacked
float flOldFrametime = gpGlobals->frametime;
float flOldCurtime = gpGlobals->curtime;
pl.fixangle = FIXANGLE_NONE;
if ( IsReplay() )
{
cmd.viewangles = QAngle( 0, 0, 0 );
}
else
{
cmd.viewangles = EyeAngles();
}
float flTimeBase = gpGlobals->curtime;
SetTimeBase( flTimeBase );
MoveHelperServer()->SetHost( this );
PlayerRunCommand( &cmd, MoveHelperServer() );
// save off the last good usercmd
SetLastUserCommand( cmd );
// Restore the globals..
gpGlobals->frametime = flOldFrametime;
gpGlobals->curtime = flOldCurtime;
MoveHelperServer()->SetHost( NULL );
}
//-----------------------------------------------------------------------------
// Purpose: Note, don't chain to BaseClass::PhysicsSimulate
//-----------------------------------------------------------------------------
void CBasePlayer::PhysicsSimulate( void )
{
VPROF_BUDGET( "CBasePlayer::PhysicsSimulate", VPROF_BUDGETGROUP_PLAYER );
// If we've got a moveparent, we must simulate that first.
CBaseEntity *pMoveParent = GetMoveParent();
if (pMoveParent)
{
pMoveParent->PhysicsSimulate();
}
// Make sure not to simulate this guy twice per frame
if ( m_nSimulationTick == gpGlobals->tickcount )
{
return;
}
m_nSimulationTick = gpGlobals->tickcount;
// See how many CUserCmds are queued up for running
int simulation_ticks = DetermineSimulationTicks();
// If some time will elapse, make sure our clock (m_nTickBase) starts at the correct time
if ( simulation_ticks > 0 )
{
AdjustPlayerTimeBase( simulation_ticks );
}
if ( IsHLTV() || IsReplay() )
{
// just run a single, empty command to make sure
// all PreThink/PostThink functions are called as usual
Assert ( GetCommandContextCount() == 0 );
RunNullCommand();
RemoveAllCommandContexts();
return;
}
// Store off true server timestamps
float savetime = gpGlobals->curtime;
float saveframetime = gpGlobals->frametime;
int command_context_count = GetCommandContextCount();
// Build a list of all available commands
CUtlVector< CUserCmd > vecAvailCommands;
// Contexts go from oldest to newest
for ( int context_number = 0; context_number < command_context_count; context_number++ )
{
// Get oldest ( newer are added to tail )
CCommandContext *ctx = GetCommandContext( context_number );
if ( !ShouldRunCommandsInContext( ctx ) )
continue;
if ( !ctx->cmds.Count() )
continue;
int numbackup = ctx->totalcmds - ctx->numcmds;
// If we haven't dropped too many packets, then run some commands
if ( ctx->dropped_packets < 24 )
{
int droppedcmds = ctx->dropped_packets;
// run the last known cmd for each dropped cmd we don't have a backup for
while ( droppedcmds > numbackup )
{
m_LastCmd.tick_count++;
vecAvailCommands.AddToTail( m_LastCmd );
droppedcmds--;
}
// Now run the "history" commands if we still have dropped packets
while ( droppedcmds > 0 )
{
int cmdnum = ctx->numcmds + droppedcmds - 1;
vecAvailCommands.AddToTail( ctx->cmds[cmdnum] );
droppedcmds--;
}
}
// Now run any new command(s). Go backward because the most recent command is at index 0.
for ( int i = ctx->numcmds - 1; i >= 0; i-- )
{
vecAvailCommands.AddToTail( ctx->cmds[i] );
}
// Save off the last good command in case we drop > numbackup packets and need to rerun them
// we'll use this to "guess" at what was in the missing packets
m_LastCmd = ctx->cmds[ CMD_MOSTRECENT ];
}
// gpGlobals->simTicksThisFrame == number of ticks remaining to be run, so we should take the last N CUserCmds and postpone them until the next frame
// If we're running multiple ticks this frame, don't peel off all of the commands, spread them out over
// the server ticks. Use blocks of two in alternate ticks
int commandLimit = CBaseEntity::IsSimulatingOnAlternateTicks() ? 2 : 1;
int commandsToRun = vecAvailCommands.Count();
if ( gpGlobals->simTicksThisFrame >= commandLimit && vecAvailCommands.Count() > commandLimit )
{
int commandsToRollOver = MIN( vecAvailCommands.Count(), ( gpGlobals->simTicksThisFrame - 1 ) );
commandsToRun = vecAvailCommands.Count() - commandsToRollOver;
Assert( commandsToRun >= 0 );
// Clear all contexts except the last one
if ( commandsToRollOver > 0 )
{
CCommandContext *ctx = RemoveAllCommandContextsExceptNewest();
ReplaceContextCommands( ctx, &vecAvailCommands[ commandsToRun ], commandsToRollOver );
}
else
{
// Clear all contexts
RemoveAllCommandContexts();
}
}
else
{
// Clear all contexts
RemoveAllCommandContexts();
}
float vphysicsArrivalTime = TICK_INTERVAL;
#ifdef _DEBUG
if ( sv_player_net_suppress_usercommands.GetBool() )
{
commandsToRun = 0;
}
#endif // _DEBUG
int numUsrCmdProcessTicksMax = sv_maxusrcmdprocessticks.GetInt();
if ( gpGlobals->maxClients != 1 && numUsrCmdProcessTicksMax ) // don't apply this filter in SP games
{
// Grant the client some time buffer to execute user commands
m_flMovementTimeForUserCmdProcessingRemaining += TICK_INTERVAL;
// but never accumulate more than N ticks
if ( m_flMovementTimeForUserCmdProcessingRemaining > numUsrCmdProcessTicksMax * TICK_INTERVAL )
m_flMovementTimeForUserCmdProcessingRemaining = numUsrCmdProcessTicksMax * TICK_INTERVAL;
}
else
{
// Otherwise we don't care to track time
m_flMovementTimeForUserCmdProcessingRemaining = FLT_MAX;
}
// Now run the commands
if ( commandsToRun > 0 )
{
m_flLastUserCommandTime = savetime;
MoveHelperServer()->SetHost( this );
// Suppress predicted events, etc.
if ( IsPredictingWeapons() )
{
IPredictionSystem::SuppressHostEvents( this );
}
for ( int i = 0; i < commandsToRun; ++i )
{
PlayerRunCommand( &vecAvailCommands[ i ], MoveHelperServer() );
// Update our vphysics object.
if ( m_pPhysicsController )
{
VPROF( "CBasePlayer::PhysicsSimulate-UpdateVPhysicsPosition" );
// If simulating at 2 * TICK_INTERVAL, add an extra TICK_INTERVAL to position arrival computation
UpdateVPhysicsPosition( m_vNewVPhysicsPosition, m_vNewVPhysicsVelocity, vphysicsArrivalTime );
vphysicsArrivalTime += TICK_INTERVAL;
}
}
// Always reset after running commands
IPredictionSystem::SuppressHostEvents( NULL );
MoveHelperServer()->SetHost( NULL );
// Copy in final origin from simulation
CPlayerSimInfo *pi = NULL;
if ( m_vecPlayerSimInfo.Count() > 0 )
{
pi = &m_vecPlayerSimInfo[ m_vecPlayerSimInfo.Tail() ];
pi->m_flTime = Plat_FloatTime();
pi->m_vecAbsOrigin = GetAbsOrigin();
pi->m_flGameSimulationTime = gpGlobals->curtime;
pi->m_nNumCmds = commandsToRun;
}
}
// Restore the true server clock
// FIXME: Should this occur after simulation of children so
// that they are in the timespace of the player?
gpGlobals->curtime = savetime;
gpGlobals->frametime = saveframetime;
// // Kick the player if they haven't sent a user command in awhile in order to prevent clients
// // from using packet-level manipulation to mess with gamestate. Not sending usercommands seems
// // to have all kinds of bad effects, such as stalling a bunch of Think()'s and gamestate handling.
// // An example from TF: A medic stops sending commands after deploying an uber on another player.
// // As a result, invuln is permanently on the heal target because the maintenance code is stalled.
// if ( GetTimeSinceLastUserCommand() > player_usercommand_timeout.GetFloat() )
// {
// // If they have an active netchan, they're almost certainly messing with usercommands?
// INetChannelInfo *pNetChanInfo = engine->GetPlayerNetInfo( entindex() );
// if ( pNetChanInfo && pNetChanInfo->GetTimeSinceLastReceived() < 5.f )
// {
// engine->ServerCommand( UTIL_VarArgs( "kickid %d %s\n", GetUserID(), "UserCommand Timeout" ) );
// }
// }
}
unsigned int CBasePlayer::PhysicsSolidMaskForEntity() const
{
return MASK_PLAYERSOLID;
}
//-----------------------------------------------------------------------------
// Purpose: This will force usercmd processing to actually consume commands even if the global tick counter isn't incrementing
//-----------------------------------------------------------------------------
void CBasePlayer::ForceSimulation()
{
m_nSimulationTick = -1;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *buf -
// totalcmds -
// dropped_packets -
// ignore -
// paused -
// Output : float -- Time in seconds of last movement command
//-----------------------------------------------------------------------------
void CBasePlayer::ProcessUsercmds( CUserCmd *cmds, int numcmds, int totalcmds,
int dropped_packets, bool paused )
{
CCommandContext *ctx = AllocCommandContext();
Assert( ctx );
int i;
for ( i = totalcmds - 1; i >= 0; i-- )
{
CUserCmd *pCmd = &cmds[totalcmds - 1 - i];
// Validate values
if ( !IsUserCmdDataValid( pCmd ) )
{
pCmd->MakeInert();
}
ctx->cmds.AddToTail( *pCmd );
}
ctx->numcmds = numcmds;
ctx->totalcmds = totalcmds,
ctx->dropped_packets = dropped_packets;
ctx->paused = paused;
// If the server is paused, zero out motion,buttons,view changes
if ( ctx->paused )
{
bool clear_angles = true;
// If no clipping and cheats enabled and sv_noclipduringpause enabled, then don't zero out movement part of CUserCmd
if ( GetMoveType() == MOVETYPE_NOCLIP &&
sv_cheats->GetBool() &&
sv_noclipduringpause.GetBool() )
{
clear_angles = false;
}
for ( i = 0; i < ctx->numcmds; i++ )
{
ctx->cmds[ i ].buttons = 0;
if ( clear_angles )
{
ctx->cmds[ i ].forwardmove = 0;
ctx->cmds[ i ].sidemove = 0;
ctx->cmds[ i ].upmove = 0;
VectorCopy ( pl.v_angle, ctx->cmds[ i ].viewangles );
}
}
ctx->dropped_packets = 0;
}
// Set global pause state for this player
m_bGamePaused = paused;
if ( paused )
{
ForceSimulation();
// Just run the commands right away if paused
PhysicsSimulate();
}
if ( sv_playerperfhistorycount.GetInt() > 0 )
{
CPlayerCmdInfo pi;
pi.m_flTime = Plat_FloatTime();
pi.m_nDroppedPackets = dropped_packets;
pi.m_nNumCmds = numcmds;
while ( m_vecPlayerCmdInfo.Count() >= sv_playerperfhistorycount.GetInt() )
{
m_vecPlayerCmdInfo.Remove( m_vecPlayerCmdInfo.Head() );
}
m_vecPlayerCmdInfo.AddToTail( pi );
}
}
//-----------------------------------------------------------------------------
// Purpose: Check that command values are reasonable
//-----------------------------------------------------------------------------
bool CBasePlayer::IsUserCmdDataValid( CUserCmd *pCmd )
{
if ( IsBot() || IsFakeClient() )
return true;
// Maximum difference between client's and server's tick_count
const int nCmdMaxTickDelta = ( 1.f / gpGlobals->interval_per_tick ) * 2.5f;
const int nMinDelta = Max( 0, gpGlobals->tickcount - nCmdMaxTickDelta );
const int nMaxDelta = gpGlobals->tickcount + nCmdMaxTickDelta;
bool bValid = ( pCmd->tick_count >= nMinDelta && pCmd->tick_count < nMaxDelta ) &&
// Prevent clients from sending invalid view angles to try to get leaf server code to crash
( pCmd->viewangles.IsValid() && IsEntityQAngleReasonable( pCmd->viewangles ) ) &&
// Movement ranges
( IsFinite( pCmd->forwardmove ) && IsEntityCoordinateReasonable( pCmd->forwardmove ) ) &&
( IsFinite( pCmd->sidemove ) && IsEntityCoordinateReasonable( pCmd->sidemove ) ) &&
( IsFinite( pCmd->upmove ) && IsEntityCoordinateReasonable( pCmd->upmove ) );
int nWarningLevel = sv_player_display_usercommand_errors.GetInt();
if ( !bValid && nWarningLevel > 0 )
{
DevMsg( "UserCommand out-of-range for userid %i\n", GetUserID() );
if ( nWarningLevel == 2 )
{
DevMsg( " tick_count: %i\n viewangles: %5.2f %5.2f %5.2f \n forward: %5.2f \n side: \t%5.2f \n up: \t%5.2f\n",
pCmd->tick_count,
pCmd->viewangles.x,
pCmd->viewangles.y,
pCmd->viewangles.x,
pCmd->forwardmove,
pCmd->sidemove,
pCmd->upmove );
}
}
return bValid;
}
void CBasePlayer::DumpPerfToRecipient( CBasePlayer *pRecipient, int nMaxRecords )
{
if ( !pRecipient )
return;
char buf[ 256 ] = { 0 };
int curpos = 0;
int nDumped = 0;
Vector prevo( 0, 0, 0 );
float prevt = 0.0f;
for ( int i = m_vecPlayerSimInfo.Tail(); i != m_vecPlayerSimInfo.InvalidIndex() ; i = m_vecPlayerSimInfo.Previous( i ) )
{
const CPlayerSimInfo *pi = &m_vecPlayerSimInfo[ i ];
float vel = 0.0f;
// Note we're walking from newest backward
float dt = prevt - pi->m_flFinalSimulationTime;
if ( nDumped > 0 && dt > 0.0f )
{
Vector d = pi->m_vecAbsOrigin - prevo;
vel = d.Length() / dt;
}
char line[ 128 ];
int len = Q_snprintf( line, sizeof( line ), "%.3f %d %d %.3f %.3f vel %.2f\n",
pi->m_flTime,
pi->m_nNumCmds,
pi->m_nTicksCorrected,
pi->m_flFinalSimulationTime,
pi->m_flGameSimulationTime,
vel );
if ( curpos + len > 200 )
{
ClientPrint( pRecipient, HUD_PRINTCONSOLE, (char const *)buf );
buf[ 0 ] = 0;
curpos = 0;
}
Q_strncpy( &buf[ curpos ], line, sizeof( buf ) - curpos );
curpos += len;
++nDumped;
if ( nMaxRecords != -1 && nDumped >= nMaxRecords )
break;
prevo = pi->m_vecAbsOrigin;
prevt = pi->m_flFinalSimulationTime;
}
if ( curpos > 0 )
{
ClientPrint( pRecipient, HUD_PRINTCONSOLE, buf );
}
nDumped = 0;
curpos = 0;
for ( int i = m_vecPlayerCmdInfo.Tail(); i != m_vecPlayerCmdInfo.InvalidIndex() ; i = m_vecPlayerCmdInfo.Previous( i ) )
{
const CPlayerCmdInfo *pi = &m_vecPlayerCmdInfo[ i ];
char line[ 128 ];
int len = Q_snprintf( line, sizeof( line ), "%.3f %d %d\n",
pi->m_flTime,
pi->m_nNumCmds,
pi->m_nDroppedPackets );
if ( curpos + len > 200 )
{
ClientPrint( pRecipient, HUD_PRINTCONSOLE, (char const *)buf );
buf[ 0 ] = 0;
curpos = 0;
}
Q_strncpy( &buf[ curpos ], line, sizeof( buf ) - curpos );
curpos += len;
++nDumped;
if ( nMaxRecords != -1 && nDumped >= nMaxRecords )
break;
}
if ( curpos > 0 )
{
ClientPrint( pRecipient, HUD_PRINTCONSOLE, buf );
}
}
// Duck debouncing code to stop menu changes from disallowing crouch/uncrouch
ConVar xc_crouch_debounce( "xc_crouch_debounce", "0", FCVAR_NONE );
//-----------------------------------------------------------------------------
// Purpose:
// Input : *ucmd -
// *moveHelper -
//-----------------------------------------------------------------------------
void CBasePlayer::PlayerRunCommand(CUserCmd *ucmd, IMoveHelper *moveHelper)
{
m_touchedPhysObject = false;
if ( pl.fixangle == FIXANGLE_NONE)
{
VectorCopy ( ucmd->viewangles, pl.v_angle );
}
// Handle FL_FROZEN.
// Prevent player moving for some seconds after New Game, so that they pick up everything
if( GetFlags() & FL_FROZEN ||
(developer.GetInt() == 0 && gpGlobals->eLoadType == MapLoad_NewGame && gpGlobals->curtime < 3.0 ) )
{
ucmd->forwardmove = 0;
ucmd->sidemove = 0;
ucmd->upmove = 0;
ucmd->buttons = 0;
ucmd->impulse = 0;
VectorCopy ( pl.v_angle, ucmd->viewangles );
}
else
{
// Force a duck if we're toggled
if ( GetToggledDuckState() )
{
// If this is set, we've altered our menu options and need to debounce the duck
if ( xc_crouch_debounce.GetBool() )
{
ToggleDuck();
// Mark it as handled
xc_crouch_debounce.SetValue( 0 );
}
else
{
ucmd->buttons |= IN_DUCK;
}
}
}
PlayerMove()->RunCommand(this, ucmd, moveHelper);
}
//-----------------------------------------------------------------------------
// Purpose: Strips off IN_xxx flags from the player's input
//-----------------------------------------------------------------------------
void CBasePlayer::DisableButtons( int nButtons )
{
m_afButtonDisabled |= nButtons;
}
//-----------------------------------------------------------------------------
// Purpose: Re-enables stripped IN_xxx flags to the player's input
//-----------------------------------------------------------------------------
void CBasePlayer::EnableButtons( int nButtons )
{
m_afButtonDisabled &= ~nButtons;
}
//-----------------------------------------------------------------------------
// Purpose: Strips off IN_xxx flags from the player's input
//-----------------------------------------------------------------------------
void CBasePlayer::ForceButtons( int nButtons )
{
m_afButtonForced |= nButtons;
}
//-----------------------------------------------------------------------------
// Purpose: Re-enables stripped IN_xxx flags to the player's input
//-----------------------------------------------------------------------------
void CBasePlayer::UnforceButtons( int nButtons )
{
m_afButtonForced &= ~nButtons;
}
void CBasePlayer::HandleFuncTrain(void)
{
if ( m_afPhysicsFlags & PFLAG_DIROVERRIDE )
AddFlag( FL_ONTRAIN );
else
RemoveFlag( FL_ONTRAIN );
// Train speed control
if (( m_afPhysicsFlags & PFLAG_DIROVERRIDE ) == 0)
{
if (m_iTrain & TRAIN_ACTIVE)
{
m_iTrain = TRAIN_NEW; // turn off train
}
return;
}
CBaseEntity *pTrain = GetGroundEntity();
float vel;
if ( pTrain )
{
if ( !(pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) )
pTrain = NULL;
}
if ( !pTrain )
{
if ( 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) )
{
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;
}
}
void CBasePlayer::PreThink(void)
{
if ( g_fGameOver || m_iPlayerLocked )
return; // intermission or finale
if ( Hints() )
{
Hints()->Update();
}
ItemPreFrame( );
WaterMove();
if ( g_pGameRules && g_pGameRules->FAllowFlashlight() )
m_Local.m_iHideHUD &= ~HIDEHUD_FLASHLIGHT;
else
m_Local.m_iHideHUD |= HIDEHUD_FLASHLIGHT;
// checks if new client data (for HUD and view control) needs to be sent to the client
UpdateClientData();
CheckTimeBasedDamage();
CheckSuitUpdate();
if ( GetObserverMode() > OBS_MODE_FREEZECAM )
{
CheckObserverSettings(); // do this each frame
}
if ( m_lifeState >= LIFE_DYING )
{
// track where we are in the nav mesh even when dead
UpdateLastKnownArea();
return;
}
HandleFuncTrain();
if (m_nButtons & IN_JUMP)
{
// If on a ladder, jump off the ladder
// else Jump
Jump();
}
// If trying to duck, already ducked, or in the process of ducking
if ((m_nButtons & IN_DUCK) || (GetFlags() & FL_DUCKING) || (m_afPhysicsFlags & PFLAG_DUCKING) )
Duck();
//
// If we're not on the ground, we're falling. Update our falling velocity.
//
if ( !( GetFlags() & FL_ONGROUND ) )
{
m_Local.m_flFallVelocity = -GetAbsVelocity().z;
}
// track where we are in the nav mesh
UpdateLastKnownArea();
// StudioFrameAdvance( );//!!!HACKHACK!!! Can't be hit by traceline when not animating?
}
/* Time based Damage works as follows:
1) There are several types of timebased damage:
#define DMG_PARALYZE (1 << 14) // slows affected creature down
#define DMG_NERVEGAS (1 << 15) // nerve toxins, very bad
#define DMG_POISON (1 << 16) // blood poisioning
#define DMG_RADIATION (1 << 17) // radiation exposure
#define DMG_DROWNRECOVER (1 << 18) // drown recovery
#define DMG_ACID (1 << 19) // toxic chemicals or acid burns
#define DMG_SLOWBURN (1 << 20) // in an oven
2) A new hit inflicting tbd restarts the tbd counter - each NPC has an 8bit counter,
per damage type. The counter is decremented every second, so the maximum time
an effect will last is 255/60 = 4.25 minutes. Of course, staying within the radius
of a damaging effect like fire, nervegas, radiation will continually reset the counter to max.
3) Every second that a tbd counter is running, the player takes damage. The damage
is determined by the type of tdb.
Paralyze - 1/2 movement rate, 30 second duration.
Nervegas - 5 points per second, 16 second duration = 80 points max dose.
Poison - 2 points per second, 25 second duration = 50 points max dose.
Radiation - 1 point per second, 50 second duration = 50 points max dose.
Drown - 5 points per second, 2 second duration.
Acid/Chemical - 5 points per second, 10 second duration = 50 points max.
Burn - 10 points per second, 2 second duration.
Freeze - 3 points per second, 10 second duration = 30 points max.
4) Certain actions or countermeasures counteract the damaging effects of tbds:
Armor/Heater/Cooler - Chemical(acid),burn, freeze all do damage to armor power, then to body
- recharged by suit recharger
Air In Lungs - drowning damage is done to air in lungs first, then to body
- recharged by poking head out of water
- 10 seconds if swiming fast
Air In SCUBA - drowning damage is done to air in tanks first, then to body
- 2 minutes in tanks. Need new tank once empty.
Radiation Syringe - Each syringe full provides protection vs one radiation dosage
Antitoxin Syringe - Each syringe full provides protection vs one poisoning (nervegas or poison).
Health kit - Immediate stop to acid/chemical, fire or freeze damage.
Radiation Shower - Immediate stop to radiation damage, acid/chemical or fire damage.
*/
// If player is taking time based damage, continue doing damage to player -
// this simulates the effect of being poisoned, gassed, dosed with radiation etc -
// anything that continues to do damage even after the initial contact stops.
// Update all time based damage counters, and shut off any that are done.
// The m_bitsDamageType bit MUST be set if any damage is to be taken.
// This routine will detect the initial on value of the m_bitsDamageType
// and init the appropriate counter. Only processes damage every second.
//#define PARALYZE_DURATION 30 // number of 2 second intervals to take damage
//#define PARALYZE_DAMAGE 0.0 // damage to take each 2 second interval
//#define NERVEGAS_DURATION 16
//#define NERVEGAS_DAMAGE 5.0
//#define POISON_DURATION 25
//#define POISON_DAMAGE 2.0
//#define RADIATION_DURATION 50
//#define RADIATION_DAMAGE 1.0
//#define ACID_DURATION 10
//#define ACID_DAMAGE 5.0
//#define SLOWBURN_DURATION 2
//#define SLOWBURN_DAMAGE 1.0
//#define SLOWFREEZE_DURATION 1.0
//#define SLOWFREEZE_DAMAGE 3.0
/* */
void CBasePlayer::CheckTimeBasedDamage()
{
int i;
byte bDuration = 0;
static float gtbdPrev = 0.0;
// If we don't have any time based damage return.
if ( !g_pGameRules->Damage_IsTimeBased( m_bitsDamageType ) )
return;
// only check for time based damage approx. every 2 seconds
if ( abs( gpGlobals->curtime - m_tbdPrev ) < 2.0 )
return;
m_tbdPrev = gpGlobals->curtime;
for (i = 0; i < CDMG_TIMEBASED; i++)
{
// Make sure the damage type is really time-based.
// This is kind of hacky but necessary until we setup DamageType as an enum.
int iDamage = ( DMG_PARALYZE << i );
if ( !g_pGameRules->Damage_IsTimeBased( iDamage ) )
continue;
// make sure bit is set for damage type
if ( m_bitsDamageType & iDamage )
{
switch (i)
{
case itbd_Paralyze:
// UNDONE - flag movement as half-speed
bDuration = PARALYZE_DURATION;
break;
case itbd_NerveGas:
// OnTakeDamage(pev, pev, NERVEGAS_DAMAGE, DMG_GENERIC);
bDuration = NERVEGAS_DURATION;
break;
// case itbd_Poison:
// OnTakeDamage( CTakeDamageInfo( this, this, POISON_DAMAGE, DMG_GENERIC ) );
// bDuration = POISON_DURATION;
// break;
case itbd_Radiation:
// OnTakeDamage(pev, pev, RADIATION_DAMAGE, DMG_GENERIC);
bDuration = RADIATION_DURATION;
break;
case itbd_DrownRecover:
// NOTE: this hack is actually used to RESTORE health
// after the player has been drowning and finally takes a breath
if (m_idrowndmg > m_idrownrestored)
{
int idif = MIN(m_idrowndmg - m_idrownrestored, 10);
TakeHealth(idif, DMG_GENERIC);
m_idrownrestored += idif;
}
bDuration = 4; // get up to 5*10 = 50 points back
break;
case itbd_PoisonRecover:
{
// NOTE: this hack is actually used to RESTORE health
// after the player has been poisoned.
if (m_nPoisonDmg > m_nPoisonRestored)
{
int nDif = MIN(m_nPoisonDmg - m_nPoisonRestored, 10);
TakeHealth(nDif, DMG_GENERIC);
m_nPoisonRestored += nDif;
}
bDuration = 9; // get up to 10*10 = 100 points back
break;
}
case itbd_Acid:
// OnTakeDamage(pev, pev, ACID_DAMAGE, DMG_GENERIC);
bDuration = ACID_DURATION;
break;
case itbd_SlowBurn:
// OnTakeDamage(pev, pev, SLOWBURN_DAMAGE, DMG_GENERIC);
bDuration = SLOWBURN_DURATION;
break;
case itbd_SlowFreeze:
// OnTakeDamage(pev, pev, SLOWFREEZE_DAMAGE, DMG_GENERIC);
bDuration = SLOWFREEZE_DURATION;
break;
default:
bDuration = 0;
}
if (m_rgbTimeBasedDamage[i])
{
// decrement damage duration, detect when done.
if (!m_rgbTimeBasedDamage[i] || --m_rgbTimeBasedDamage[i] == 0)
{
m_rgbTimeBasedDamage[i] = 0;
// if we're done, clear damage bits
m_bitsDamageType &= ~(DMG_PARALYZE << i);
}
}
else
// first time taking this damage type - init damage duration
m_rgbTimeBasedDamage[i] = bDuration;
}
}
}
/*
THE POWER SUIT
The Suit provides 3 main functions: Protection, Notification and Augmentation.
Some functions are automatic, some require power.
The player gets the suit shortly after getting off the train in C1A0 and it stays
with him for the entire game.
Protection
Heat/Cold
When the player enters a hot/cold area, the heating/cooling indicator on the suit
will come on and the battery will drain while the player stays in the area.
After the battery is dead, the player starts to take damage.
This feature is built into the suit and is automatically engaged.
Radiation Syringe