summaryrefslogtreecommitdiff
path: root/game/server/dod/dod_player.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/dod/dod_player.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/server/dod/dod_player.cpp')
-rw-r--r--game/server/dod/dod_player.cpp4936
1 files changed, 4936 insertions, 0 deletions
diff --git a/game/server/dod/dod_player.cpp b/game/server/dod/dod_player.cpp
new file mode 100644
index 0000000..9d09a18
--- /dev/null
+++ b/game/server/dod/dod_player.cpp
@@ -0,0 +1,4936 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Player for HL1.
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "dod_player.h"
+#include "dod_gamerules.h"
+#include "team.h" //GetGlobalTeam
+#include "in_buttons.h"
+#include "dod_shareddefs.h"
+#include "dod_cvars.h"
+#include "weapon_dodbase.h"
+#include "weapon_dodbasegrenade.h"
+#include "weapon_dodbaserpg.h"
+#include "weapon_dodbipodgun.h"
+#include "dod_basegrenade.h"
+#include "dod_ammo_box.h"
+#include "effect_dispatch_data.h"
+#include "movehelper_server.h"
+#include "tier0/vprof.h"
+#include "te_effect_dispatch.h"
+#include "vphysics/player_controller.h"
+#include <KeyValues.h>
+#include "engine/IEngineSound.h"
+#include "studio.h"
+#include "dod_viewmodel.h"
+#include "info_camera_link.h"
+#include "dod_team.h"
+#include "dod_control_point.h"
+#include "dod_location.h"
+#include "viewport_panel_names.h"
+#include "obstacle_pushaway.h"
+#include "datacache/imdlcache.h"
+#include "bone_setup.h"
+#include "dod_baserocket.h"
+#include "dod_basegrenade.h"
+#include "dod_bombtarget.h"
+#include "collisionutils.h"
+#include "gamestats.h"
+#include "gameinterface.h"
+#include "holiday_gift.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+#pragma warning( disable: 4355 ) // disables ' 'this' : used in base member initializer list'
+
+ConVar sv_max_usercmd_future_ticks( "sv_max_usercmd_future_ticks", "8", 0, "Prevents clients from running usercmds too far in the future. Prevents speed hacks." );
+ConVar sv_motd_unload_on_dismissal( "sv_motd_unload_on_dismissal", "0", 0, "If enabled, the MOTD contents will be unloaded when the player closes the MOTD." );
+
+EHANDLE g_pLastAlliesSpawn;
+EHANDLE g_pLastAxisSpawn;
+
+int g_iLastAlliesSpawnIndex = 0;
+int g_iLastAxisSpawnIndex = 0;
+
+ConVar dod_playerstatetransitions( "dod_playerstatetransitions", "-2", FCVAR_CHEAT, "dod_playerstatetransitions <ent index or -1 for all>. Show player state transitions." );
+ConVar dod_debugdamage( "dod_debugdamage", "0", FCVAR_CHEAT );
+
+ConVar mp_bandage_heal_amount( "mp_bandage_heal_amount",
+ "40",
+ FCVAR_GAMEDLL,
+ "How much health to give after a successful bandage\n",
+ true, 0, false, 0 );
+
+ConVar dod_freezecam( "dod_freezecam", "1", FCVAR_REPLICATED | FCVAR_ARCHIVE, "show players a freezecam shot of their killer on death" );
+
+extern ConVar spec_freeze_time;
+extern ConVar spec_freeze_traveltime;
+
+#define DOD_PUSHAWAY_THINK_CONTEXT "DODPushawayThink"
+#define DOD_DEAFEN_CONTEXT "DeafenContext"
+
+int g_iCurrentLifeID = 0;
+
+int GetNextLifeID( void )
+{
+ return ++g_iCurrentLifeID;
+}
+
+// -------------------------------------------------------------------------------- //
+// Classes
+// -------------------------------------------------------------------------------- //
+
+class CPhysicsPlayerCallback : public IPhysicsPlayerControllerEvent
+{
+public:
+ int ShouldMoveTo( IPhysicsObject *pObject, const Vector &position )
+ {
+ CDODPlayer *pPlayer = (CDODPlayer *)pObject->GetGameData();
+ if ( pPlayer )
+ {
+ if ( pPlayer->TouchedPhysics() )
+ {
+ return 0;
+ }
+ }
+ return 1;
+ }
+};
+
+static CPhysicsPlayerCallback playerCallback;
+
+// -------------------------------------------------------------------------------- //
+// Ragdoll entities.
+// -------------------------------------------------------------------------------- //
+
+class CDODRagdoll : public CBaseAnimatingOverlay
+{
+public:
+ DECLARE_CLASS( CDODRagdoll, CBaseAnimatingOverlay );
+ DECLARE_SERVERCLASS();
+
+ // Transmit ragdolls to everyone.
+ virtual int UpdateTransmitState()
+ {
+ return SetTransmitState( FL_EDICT_ALWAYS );
+ }
+
+public:
+ // In case the client has the player entity, we transmit the player index.
+ // In case the client doesn't have it, we transmit the player's model index, origin, and angles
+ // so they can create a ragdoll in the right place.
+ CNetworkHandle( CBaseEntity, m_hPlayer ); // networked entity handle
+ CNetworkVector( m_vecRagdollVelocity );
+ CNetworkVector( m_vecRagdollOrigin );
+};
+
+LINK_ENTITY_TO_CLASS( dod_ragdoll, CDODRagdoll );
+
+IMPLEMENT_SERVERCLASS_ST_NOBASE( CDODRagdoll, DT_DODRagdoll )
+ SendPropVector( SENDINFO(m_vecRagdollOrigin), -1, SPROP_COORD ),
+ SendPropEHandle( SENDINFO( m_hPlayer ) ),
+ SendPropModelIndex( SENDINFO( m_nModelIndex ) ),
+ SendPropInt ( SENDINFO(m_nForceBone), 8, 0 ),
+ SendPropVector ( SENDINFO(m_vecForce), -1, SPROP_NOSCALE ),
+ SendPropVector( SENDINFO( m_vecRagdollVelocity ) )
+END_SEND_TABLE()
+
+
+// -------------------------------------------------------------------------------- //
+// Player animation event. Sent to the client when a player fires, jumps, reloads, etc..
+// -------------------------------------------------------------------------------- //
+
+class CTEPlayerAnimEvent : public CBaseTempEntity
+{
+public:
+ DECLARE_CLASS( CTEPlayerAnimEvent, CBaseTempEntity );
+ DECLARE_SERVERCLASS();
+
+ CTEPlayerAnimEvent( const char *name ) : CBaseTempEntity( name )
+ {
+ }
+
+ CNetworkHandle( CBasePlayer, m_hPlayer );
+ CNetworkVar( int, m_iEvent );
+ CNetworkVar( int, m_nData );
+};
+
+IMPLEMENT_SERVERCLASS_ST_NOBASE( CTEPlayerAnimEvent, DT_TEPlayerAnimEvent )
+ SendPropEHandle( SENDINFO( m_hPlayer ) ),
+ SendPropInt( SENDINFO( m_iEvent ), Q_log2( PLAYERANIMEVENT_COUNT ) + 1, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_nData ), 32 )
+END_SEND_TABLE()
+
+static CTEPlayerAnimEvent g_TEPlayerAnimEvent( "PlayerAnimEvent" );
+
+void TE_PlayerAnimEvent( CBasePlayer *pPlayer, PlayerAnimEvent_t event, int nData )
+{
+ CPVSFilter filter( (const Vector&)pPlayer->EyePosition() );
+
+ g_TEPlayerAnimEvent.m_hPlayer = pPlayer;
+ g_TEPlayerAnimEvent.m_iEvent = event;
+ g_TEPlayerAnimEvent.m_nData = nData;
+ g_TEPlayerAnimEvent.Create( filter, 0 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Filters updates to a variable so that only non-local players see
+// the changes. This is so we can send a low-res origin to non-local players
+// while sending a hi-res one to the local player.
+// Input : *pVarData -
+// *pOut -
+// objectID -
+//-----------------------------------------------------------------------------
+
+void* SendProxy_SendNonLocalDataTable( const SendProp *pProp, const void *pStruct, const void *pVarData, CSendProxyRecipients *pRecipients, int objectID )
+{
+ pRecipients->SetAllRecipients();
+ pRecipients->ClearRecipient( objectID - 1 );
+ return ( void * )pVarData;
+}
+
+// -------------------------------------------------------------------------------- //
+// Tables.
+// -------------------------------------------------------------------------------- //
+
+LINK_ENTITY_TO_CLASS( player, CDODPlayer );
+PRECACHE_REGISTER(player);
+
+// CDODPlayerShared Data Tables
+//=============================
+
+// specific to the local player
+BEGIN_SEND_TABLE_NOBASE( CDODPlayerShared, DT_DODSharedLocalPlayerExclusive )
+ SendPropInt( SENDINFO( m_iPlayerClass), 4 ),
+ SendPropInt( SENDINFO( m_iDesiredPlayerClass ), 4 ),
+ SendPropFloat( SENDINFO( m_flDeployedYawLimitLeft ) ),
+ SendPropFloat( SENDINFO( m_flDeployedYawLimitRight ) ),
+ SendPropInt( SENDINFO( m_iCPIndex ), 4 ),
+ SendPropArray3( SENDINFO_ARRAY3( m_bPlayerDominated ), SendPropBool( SENDINFO_ARRAY( m_bPlayerDominated ) ) ),
+ SendPropArray3( SENDINFO_ARRAY3( m_bPlayerDominatingMe ), SendPropBool( SENDINFO_ARRAY( m_bPlayerDominatingMe ) ) ),
+END_SEND_TABLE()
+
+// main table
+BEGIN_SEND_TABLE_NOBASE( CDODPlayerShared, DT_DODPlayerShared )
+ SendPropFloat( SENDINFO( m_flStamina ), 0, SPROP_NOSCALE | SPROP_CHANGES_OFTEN ),
+ SendPropTime( SENDINFO( m_flSlowedUntilTime ) ),
+ SendPropBool( SENDINFO( m_bProne ) ),
+ SendPropBool( SENDINFO( m_bIsSprinting ) ),
+ SendPropTime( SENDINFO( m_flGoProneTime ) ),
+ SendPropTime( SENDINFO( m_flUnProneTime ) ),
+ SendPropTime( SENDINFO( m_flDeployChangeTime ) ),
+ SendPropTime( SENDINFO( m_flDeployedHeight ) ),
+ SendPropBool( SENDINFO( m_bPlanting ) ),
+ SendPropBool( SENDINFO( m_bDefusing ) ),
+ SendPropDataTable( "dodsharedlocaldata", 0, &REFERENCE_SEND_TABLE(DT_DODSharedLocalPlayerExclusive), SendProxy_SendLocalDataTable ),
+END_SEND_TABLE()
+
+
+// CDODPlayer Data Tables
+//=========================
+extern void SendProxy_Origin( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID );
+
+// specific to the local player
+BEGIN_SEND_TABLE_NOBASE( CDODPlayer, DT_DODLocalPlayerExclusive )
+ // send a hi-res origin to the local player for use in prediction
+ SendPropVector (SENDINFO(m_vecOrigin), -1, SPROP_NOSCALE|SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, SendProxy_Origin ),
+ SendPropFloat( SENDINFO(m_flStunDuration), 0, SPROP_NOSCALE ),
+ SendPropFloat( SENDINFO(m_flStunMaxAlpha), 0, SPROP_NOSCALE ),
+ SendPropInt( SENDINFO( m_iProgressBarDuration ), 4, SPROP_UNSIGNED ),
+ SendPropFloat( SENDINFO( m_flProgressBarStartTime ), 0, SPROP_NOSCALE ),
+END_SEND_TABLE()
+
+// all players except the local player
+BEGIN_SEND_TABLE_NOBASE( CDODPlayer, DT_DODNonLocalPlayerExclusive )
+ // send a lo-res origin to other players
+ SendPropVector (SENDINFO(m_vecOrigin), -1, SPROP_COORD|SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, SendProxy_Origin ),
+END_SEND_TABLE()
+
+// main table
+IMPLEMENT_SERVERCLASS_ST( CDODPlayer, DT_DODPlayer )
+ SendPropExclude( "DT_BaseAnimating", "m_flPoseParameter" ),
+ SendPropExclude( "DT_BaseAnimating", "m_flPlaybackRate" ),
+ SendPropExclude( "DT_BaseAnimating", "m_nSequence" ),
+ SendPropExclude( "DT_BaseAnimating", "m_nNewSequenceParity" ),
+ SendPropExclude( "DT_BaseAnimating", "m_nResetEventsParity" ),
+ SendPropExclude( "DT_BaseEntity", "m_angRotation" ),
+ SendPropExclude( "DT_BaseAnimatingOverlay", "overlay_vars" ),
+
+ // dod_playeranimstate and clientside animation takes care of these on the client
+ SendPropExclude( "DT_ServerAnimationData" , "m_flCycle" ),
+ SendPropExclude( "DT_AnimTimeMustBeFirst" , "m_flAnimTime" ),
+
+ // We need to send a hi-res origin to the local player to avoid prediction errors sliding along walls
+ SendPropExclude( "DT_BaseEntity", "m_vecOrigin" ),
+
+ // Data that only gets sent to the local player.
+ SendPropDataTable( SENDINFO_DT( m_Shared ), &REFERENCE_SEND_TABLE( DT_DODPlayerShared ) ),
+
+ // Data that only gets sent to the local player.
+ SendPropDataTable( "dodlocaldata", 0, &REFERENCE_SEND_TABLE(DT_DODLocalPlayerExclusive), SendProxy_SendLocalDataTable ),
+ SendPropDataTable( "dodnonlocaldata", 0, &REFERENCE_SEND_TABLE(DT_DODNonLocalPlayerExclusive), SendProxy_SendNonLocalDataTable ),
+
+ SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 0), 13, SPROP_CHANGES_OFTEN ),
+ SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 1), 13, SPROP_CHANGES_OFTEN ),
+ SendPropEHandle( SENDINFO( m_hRagdoll ) ),
+ SendPropBool( SENDINFO( m_bSpawnInterpCounter ) ),
+ SendPropInt( SENDINFO( m_iAchievementAwardsMask ), NUM_ACHIEVEMENT_AWARDS, SPROP_UNSIGNED ),
+END_SEND_TABLE()
+
+BEGIN_DATADESC( CDODPlayer )
+ DEFINE_THINKFUNC( PushawayThink ),
+ DEFINE_THINKFUNC( DeafenThink )
+END_DATADESC()
+
+// -------------------------------------------------------------------------------- //
+
+void cc_CreatePredictionError_f()
+{
+ CBaseEntity *pEnt = CBaseEntity::Instance( 1 );
+ pEnt->SetAbsOrigin( pEnt->GetAbsOrigin() + Vector( 63, 0, 0 ) );
+}
+
+ConCommand cc_CreatePredictionError( "CreatePredictionError", cc_CreatePredictionError_f, "Create a prediction error", FCVAR_CHEAT );
+
+bool PlayerStatLessFunc( const int &lhs, const int &rhs )
+{
+ return lhs < rhs;
+}
+
+// -------------------------------------------------------------------------------- //
+// CDODPlayer implementation.
+// -------------------------------------------------------------------------------- //
+CDODPlayer::CDODPlayer()
+#if !defined(NO_STEAM)
+: m_CallbackGSStatsReceived( this, &CDODPlayer::OnGSStatsReceived )
+#endif
+{
+ m_PlayerAnimState = CreatePlayerAnimState( this );
+ m_Shared.Init( this );
+
+ UseClientSideAnimation();
+ m_angEyeAngles.Init();
+
+ SetViewOffset( DOD_PLAYER_VIEW_OFFSET );
+
+ m_pCurStateInfo = NULL; // no state yet
+ m_lifeState = LIFE_DEAD; // Start "dead".
+
+ m_Shared.SetPlayerClass( PLAYERCLASS_UNDEFINED );
+
+ m_bTeamChanged = false;
+
+ // Clear the signals
+ m_signals.Update();
+ m_fHandleSignalsTime = 0.0f;
+
+ // autokick
+ m_flLastMovement = gpGlobals->curtime;
+
+ m_flNextVoice = gpGlobals->curtime;
+ m_flNextHandSignal = gpGlobals->curtime;
+
+ m_flNextTimeCheck = gpGlobals->curtime;
+
+ m_bAutoReload = true;
+
+ // Init our hint system
+ Hints()->Init( this, NUM_HINTS, g_pszHintMessages );
+ Hints()->RegisterHintTimer( HINT_USE_MELEE, 60 );
+ Hints()->RegisterHintTimer( HINT_USE_ZOOM, 30 );
+ Hints()->RegisterHintTimer( HINT_USE_IRON_SIGHTS, 60 );
+ Hints()->RegisterHintTimer( HINT_USE_SEMI_AUTO, 60 );
+ Hints()->RegisterHintTimer( HINT_USE_SPRINT, 120 );
+ Hints()->RegisterHintTimer( HINT_USE_DEPLOY, 30 );
+ Hints()->RegisterHintTimer( HINT_USE_PRIME, 2 );
+
+ memset( &m_WeaponStats, 0, sizeof(weaponstat_t) );
+
+ m_KilledPlayers.SetLessFunc( PlayerStatLessFunc );
+ m_KilledByPlayers.SetLessFunc( PlayerStatLessFunc );
+
+ m_iNumAreaDefenses = 0;
+ m_iNumAreaCaptures = 0;
+ m_iNumBonusRoundKills = 0;
+
+ memset( &m_flTimePlayedPerClass_Allies, 0, sizeof(m_flTimePlayedPerClass_Allies) );
+ memset( &m_flTimePlayedPerClass_Axis, 0, sizeof(m_flTimePlayedPerClass_Axis) );
+ m_flLastClassChangeTime = gpGlobals->curtime;
+
+ Q_memset( iNumKilledByUnanswered, 0, sizeof( iNumKilledByUnanswered ) );
+
+ m_iAchievementAwardsMask = 0;
+
+ m_hLastDroppedWeapon = NULL;
+ m_hLastDroppedAmmoBox = NULL;
+
+ m_flTimeAsClassAccumulator = 0;
+}
+
+
+CDODPlayer::~CDODPlayer()
+{
+ m_PlayerAnimState->Release();
+}
+
+
+CDODPlayer *CDODPlayer::CreatePlayer( const char *className, edict_t *ed )
+{
+ CDODPlayer::s_PlayerEdict = ed;
+ return (CDODPlayer*)CreateEntityByName( className );
+}
+
+void CDODPlayer::PrecachePlayerModel( const char *szPlayerModel )
+{
+ PrecacheModel( szPlayerModel );
+
+ // Strange numbers, but gotten from the actual max bounds of our
+ // player models
+ const static Vector mins( -13.9, -30.1, -12.1 );
+ const static Vector maxs( 30.9, 30.1, 73.1 );
+
+// Moved to pure_server_minimal.txt
+// engine->ForceModelBounds( szPlayerModel, mins, maxs );
+}
+
+void CDODPlayer::Precache()
+{
+ PrecachePlayerModel( DOD_PLAYERMODEL_AXIS_RIFLEMAN );
+ PrecachePlayerModel( DOD_PLAYERMODEL_AXIS_ASSAULT );
+ PrecachePlayerModel( DOD_PLAYERMODEL_AXIS_SUPPORT );
+ PrecachePlayerModel( DOD_PLAYERMODEL_AXIS_SNIPER );
+ PrecachePlayerModel( DOD_PLAYERMODEL_AXIS_MG );
+ PrecachePlayerModel( DOD_PLAYERMODEL_AXIS_ROCKET );
+
+ PrecachePlayerModel( DOD_PLAYERMODEL_US_RIFLEMAN );
+ PrecachePlayerModel( DOD_PLAYERMODEL_US_ASSAULT );
+ PrecachePlayerModel( DOD_PLAYERMODEL_US_SUPPORT );
+ PrecachePlayerModel( DOD_PLAYERMODEL_US_SNIPER );
+ PrecachePlayerModel( DOD_PLAYERMODEL_US_MG );
+ PrecachePlayerModel( DOD_PLAYERMODEL_US_ROCKET );
+
+/// Moved to pure_server_minimal.txt
+// engine->ForceSimpleMaterial( "materials/models/player/american/american_body.vmt" );
+// engine->ForceSimpleMaterial( "materials/models/player/american/american_gear.vmt" );
+// engine->ForceSimpleMaterial( "materials/models/player/german/german_body.vmt" );
+// engine->ForceSimpleMaterial( "materials/models/player/german/german_gear.vmt" );
+
+ UTIL_PrecacheOther( "dod_ammo_box" );
+
+ PrecacheScriptSound( "Player.FlashlightOn" );
+ PrecacheScriptSound( "Player.FlashlightOff" );
+
+ PrecacheScriptSound( "Player.FreezeCam" );
+ PrecacheScriptSound( "Camera.SnapShot" );
+ PrecacheScriptSound( "Achievement.Earned" );
+ PrecacheScriptSound( "Game.Revenge" );
+ PrecacheScriptSound( "Game.Domination" );
+ PrecacheScriptSound( "Game.Nemesis" );
+
+ PrecacheModel ( "sprites/glow01.vmt" );
+
+ BaseClass::Precache();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Allow pre-frame adjustments on the player
+//-----------------------------------------------------------------------------
+ConVar sv_runcmds( "sv_runcmds", "1" );
+void CDODPlayer::PlayerRunCommand( CUserCmd *ucmd, IMoveHelper *moveHelper )
+{
+ VPROF( "CDODPlayer::PlayerRunCommand" );
+
+ if ( !sv_runcmds.GetInt() )
+ return;
+
+ // don't run commands in the future
+ if ( !IsEngineThreaded() &&
+ ( ucmd->tick_count > (gpGlobals->tickcount + sv_max_usercmd_future_ticks.GetInt()) ) )
+ {
+ DevMsg( "Client cmd out of sync (delta %i).\n", ucmd->tick_count - gpGlobals->tickcount );
+ return;
+ }
+
+ BaseClass::PlayerRunCommand( ucmd, moveHelper );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Simulates a single frame of movement for a player
+//-----------------------------------------------------------------------------
+void CDODPlayer::RunPlayerMove( const QAngle& viewangles, float forwardmove, float sidemove, float upmove, unsigned short buttons, byte impulse, float frametime )
+{
+ CUserCmd cmd;
+
+ // Store off the globals.. they're gonna get whacked
+ float flOldFrametime = gpGlobals->frametime;
+ float flOldCurtime = gpGlobals->curtime;
+
+ float flTimeBase = gpGlobals->curtime + gpGlobals->frametime - frametime;
+ this->SetTimeBase( flTimeBase );
+
+ CUserCmd lastUserCmd = *GetLastUserCommand();
+ Q_memset( &cmd, 0, sizeof( cmd ) );
+ MoveHelperServer()->SetHost( this );
+ this->PlayerRunCommand( &cmd, MoveHelperServer() );
+ this->SetLastUserCommand( cmd );
+
+ // Clear out any fixangle that has been set
+ this->pl.fixangle = FIXANGLE_NONE;
+
+ // Restore the globals..
+ gpGlobals->frametime = flOldFrametime;
+ gpGlobals->curtime = flOldCurtime;
+}
+
+
+void CDODPlayer::InitialSpawn( void )
+{
+ BaseClass::InitialSpawn();
+
+ State_Enter( STATE_WELCOME );
+}
+
+void CDODPlayer::Spawn()
+{
+ // Get rid of the progress bar...
+ SetProgressBarTime( 0 );
+
+ SetModel( DOD_PLAYERMODEL_US_RIFLEMAN ); //temporarily
+ BaseClass::Spawn();
+
+ AddFlag( FL_ONGROUND ); // set the player on the ground at the start of the round.
+
+ m_Shared.SetStamina( 100 );
+ m_bTeamChanged = false;
+
+ ClearCapAreaIndex();
+
+ m_bHasGenericAmmo = true;
+
+ m_bSlowedByHit = false;
+
+ m_flIdleTime = gpGlobals->curtime + random->RandomFloat( 20, 30 );
+
+ InitProne();
+ InitSprinting();
+
+ // update this counter, used to not interp players when they spawn
+ m_bSpawnInterpCounter = !m_bSpawnInterpCounter;
+
+ EmitSound( "Player.Spawn" );
+
+ SetContextThink( &CDODPlayer::PushawayThink, gpGlobals->curtime + PUSHAWAY_THINK_INTERVAL, DOD_PUSHAWAY_THINK_CONTEXT );
+
+ m_Shared.SetCPIndex( -1 );
+
+ ResetBleeding();
+
+ // Our own version of remove decals, as we can't pass entity messages to
+ // the target's base class.
+
+ EntityMessageBegin( this );
+ WRITE_BYTE( DOD_PLAYER_REMOVE_DECALS );
+ MessageEnd();
+
+ if ( m_Shared.PlayerClass() != PLAYERCLASS_UNDEFINED )
+ {
+ Hints()->StartHintTimer( HINT_USE_SPRINT );
+ }
+
+ // Get a unique "life id" used for tracking stats
+ m_iLifeID = GetNextLifeID();
+
+ SetDefusing( NULL );
+ SetPlanting( NULL );
+
+ m_iLastBlockAreaIndex = -1;
+ m_iLastBlockCapAttempt = -1;
+
+ if ( DODGameRules()->IsBombingTeam( GetTeamNumber() ) )
+ {
+ Hints()->HintMessage( HINT_BOMB_PLANT_MAP );
+ }
+
+ //CollisionProp()->SetSurroundingBoundsType( USE_GAME_CODE );
+
+ // Reset per-life achievement counts
+ HandleHeadshotAchievement( 0 );
+ HandleDeployedMGKillCount( 0 );
+ HandleEnemyWeaponsAchievement( 0 );
+ ResetComboWeaponKill();
+
+ RecalculateAchievementAwardsMask();
+
+ // If we're spawning as a non-player team, flush the stats
+ int iTeam = GetTeamNumber();
+ if ( iTeam != TEAM_ALLIES && iTeam != TEAM_AXIS )
+ {
+ StatEvent_UploadStats();
+ }
+
+ m_StatProperty.SetClassAndTeamForThisLife( m_Shared.PlayerClass(), GetTeamNumber() );
+}
+
+void CDODPlayer::PlayerDeathThink()
+{
+ //overridden, do nothing
+}
+
+void CDODPlayer::CreateRagdollEntity()
+{
+ // If we already have a ragdoll, don't make another one.
+ CDODRagdoll *pRagdoll = dynamic_cast< CDODRagdoll* >( m_hRagdoll.Get() );
+
+ if( pRagdoll )
+ {
+ // MATTTODO: put ragdolls in a queue to disappear
+ // for now remove the old one ..
+ UTIL_Remove( pRagdoll );
+ pRagdoll = NULL;
+ }
+
+ if ( !pRagdoll )
+ {
+ // and create a new one
+ pRagdoll = dynamic_cast< CDODRagdoll* >( CreateEntityByName( "dod_ragdoll" ) );
+ }
+
+ if ( pRagdoll )
+ {
+ pRagdoll->m_hPlayer = this;
+ pRagdoll->m_vecRagdollOrigin = GetAbsOrigin();
+ pRagdoll->m_vecRagdollVelocity = GetAbsVelocity();
+ pRagdoll->m_nModelIndex = m_nModelIndex;
+ pRagdoll->m_nForceBone = m_nForceBone;
+ pRagdoll->m_vecForce = m_vecTotalBulletForce;
+ }
+
+ // ragdolls will be removed on round restart automatically
+ m_hRagdoll = pRagdoll;
+}
+
+// Called when a player is disconnecting
+void CDODPlayer::DestroyRagdoll( void )
+{
+ CDODRagdoll *pRagdoll = dynamic_cast< CDODRagdoll* >( m_hRagdoll.Get() );
+
+ if( pRagdoll )
+ {
+ UTIL_Remove( pRagdoll );
+ }
+}
+
+void CDODPlayer::Event_Killed( const CTakeDamageInfo &info )
+{
+ // allow bots to react
+// TheBots->OnEvent( EVENT_PLAYER_DIED, this, info.GetAttacker() );
+
+ CBaseEntity *pInflictor = info.GetInflictor();
+ CBaseEntity *pKiller = info.GetAttacker();
+ CDODPlayer *pScorer = ToDODPlayer( DODGameRules()->GetDeathScorer( pKiller, pInflictor ) );
+
+ // Do this before we drop the victim's weapons to check the streak
+ if ( GetDeployedKillStreak() >= ACHIEVEMENT_MG_STREAK_IS_DOMINATING )
+ {
+ if ( pScorer && pScorer->GetTeamNumber() != GetTeamNumber() && DODGameRules()->State_Get() == STATE_RND_RUNNING )
+ {
+ pScorer->AwardAchievement( ACHIEVEMENT_DOD_KILL_DOMINATING_MG );
+ }
+ }
+
+ // see if this was a long range kill
+ // distance from scorer to inflictor > 1200
+ if ( pScorer && GetTeamNumber() != pScorer->GetTeamNumber() && pInflictor != pScorer )
+ {
+ const char *killer_weapon_name = STRING( pInflictor->m_iClassname );
+
+ if ( strncmp( killer_weapon_name, "rocket_", 7 ) == 0 )
+ {
+ float flDist = ( pScorer->GetAbsOrigin() - pInflictor->GetAbsOrigin() ).Length();
+
+ if ( flDist > ACHIEVEMENT_LONG_RANGE_ROCKET_DIST && DODGameRules()->State_Get() == STATE_RND_RUNNING )
+ {
+ bool bIsSniperZoomed = false;
+
+ CWeaponDODBase *pWeapon = GetActiveDODWeapon();
+ if( pWeapon && ( pWeapon->GetDODWpnData().m_WeaponType == WPN_TYPE_SNIPER ) )
+ {
+ CWeaponDODBaseGun *pGun = static_cast<CWeaponDODBaseGun *>( pWeapon );
+ bIsSniperZoomed = pGun->IsSniperZoomed();
+ }
+
+ // victim must be deployed sniper or deployed mg
+ if ( bIsSniperZoomed || m_Shared.IsInMGDeploy() )
+ {
+ pScorer->AwardAchievement( ACHIEVEMENT_DOD_LONG_RANGE_ROCKET );
+ }
+ }
+ }
+ }
+
+ DropPrimaryWeapon();
+
+ if ( DODGameRules()->GetPresentDropChance() >= RandomFloat() )
+ {
+ Vector vForward, vRight, vUp;
+ AngleVectors( EyeAngles(), &vForward, &vRight, &vUp );
+
+ CHolidayGift::Create( WorldSpaceCenter(), GetAbsAngles(), EyeAngles(), GetAbsVelocity(), this );
+ }
+
+ FlashlightTurnOff();
+
+ // Just in case the progress bar is on screen, kill it.
+ SetProgressBarTime( 0 );
+
+ // turn off our cap area index
+ HandleSignals();
+
+ //if we have a primed grenade, drop it
+ CWeaponDODBase *pWpn = GetActiveDODWeapon();
+
+ if( pWpn && pWpn->GetDODWpnData().m_WeaponType == WPN_TYPE_GRENADE )
+ {
+ CWeaponDODBaseGrenade *pGrenade = dynamic_cast<CWeaponDODBaseGrenade *>(pWpn);
+
+ if( pGrenade && pGrenade->IsArmed() )
+ {
+ pGrenade->DropGrenade();
+ }
+ }
+
+ if ( pScorer && pScorer != this && pScorer->GetTeamNumber() != GetTeamNumber() )
+ {
+ if ( m_bIsPlanting && m_pPlantTarget )
+ {
+ CDODBombTarget *pTarget = m_pPlantTarget;
+
+ Assert( pTarget );
+
+ if ( pTarget )
+ pTarget->PlantBlocked( pScorer );
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "dod_kill_planter" );
+ if ( event )
+ {
+ event->SetInt( "userid", pScorer->GetUserID() );
+ event->SetInt( "victimid", GetUserID() );
+
+ gameeventmanager->FireEvent( event );
+ }
+ }
+
+ if ( m_bIsDefusing && m_pDefuseTarget && pScorer->GetTeamNumber() != GetTeamNumber() )
+ {
+ // Re-enable if we want to give a defend point to someone who kills a defuser
+
+ //CDODBombTarget *pTarget = m_pDefuseTarget;
+ //pTarget->DefuseBlocked( pScorer );
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "dod_kill_defuser" );
+ if ( event )
+ {
+ event->SetInt( "userid", pScorer->GetUserID() );
+ event->SetInt( "victimid", GetUserID() );
+
+ gameeventmanager->FireEvent( event );
+ }
+ }
+ }
+
+ SetDefusing( NULL );
+ SetPlanting( NULL );
+
+ // show killer in death cam mode
+ // chopped down version of SetObserverTarget without the team check
+ if( info.GetAttacker() && info.GetAttacker()->IsPlayer() )
+ {
+ // set new target
+ m_hObserverTarget.Set( info.GetAttacker() );
+
+ // reset fov to default
+ SetFOV( this, 0 );
+ }
+ else
+ m_hObserverTarget.Set( NULL );
+
+ //update damage info with our accumulated physics force
+ CTakeDamageInfo subinfo = info;
+ subinfo.SetDamageForce( m_vecTotalBulletForce );
+
+ // Note: since we're dead, it won't draw us on the client, but we don't set EF_NODRAW
+ // because we still want to transmit to the clients in our PVS.
+ CreateRagdollEntity();
+
+ State_Transition( STATE_DEATH_ANIM ); // Transition into the dying state.
+ BaseClass::Event_Killed( subinfo );
+
+ OutputDamageGiven();
+ OutputDamageTaken();
+ ResetDamageCounters();
+
+ // stop any voice command or pain sounds that we may be making
+ CPASFilter filter( WorldSpaceCenter() );
+ EmitSound( filter, entindex(), "Voice.StopSounds" );
+
+ ResetBleeding();
+
+ m_flStunDuration = 0.0f;
+ m_flStunMaxAlpha = 0;
+
+ // cancel deafen think
+ SetContextThink( NULL, 0.0, DOD_DEAFEN_CONTEXT );
+
+ // cancel deafen dsp
+ CSingleUserRecipientFilter user( this );
+ enginesound->SetPlayerDSP( user, 0, false );
+
+ CDODPlayer *pAttacker = ToDODPlayer( info.GetAttacker() );
+
+ if( pAttacker && pAttacker->IsPlayer() )
+ {
+ // find the weapon id of the weapon
+
+ DODWeaponID weaponID = WEAPON_NONE;
+
+ CBaseEntity *pInflictor = info.GetInflictor();
+
+ if ( pInflictor == pAttacker )
+ {
+ CWeaponDODBase *pWpn = pAttacker->GetActiveDODWeapon();
+
+ if ( pWpn )
+ {
+ if ( info.GetDamageCustom() & MELEE_DMG_SECONDARYATTACK )
+ weaponID = pWpn->GetAltWeaponID();
+ else
+ weaponID = pWpn->GetStatsWeaponID();
+ }
+ }
+ else
+ {
+ Assert( pInflictor );
+
+ const char *weaponName = STRING( pInflictor->m_iClassname );
+
+ if ( Q_strncmp( weaponName, "rocket_", 7 ) == 0 )
+ {
+ CDODBaseRocket *pRocket = dynamic_cast< CDODBaseRocket *>( pInflictor );
+ if ( pRocket )
+ {
+ weaponID = pRocket->GetEmitterWeaponID();
+ }
+ }
+ else if ( Q_strncmp( weaponName, "grenade_", 8 ) == 0 )
+ {
+ CDODBaseGrenade *pGren = dynamic_cast< CDODBaseGrenade *>( pInflictor );
+ if ( pGren )
+ {
+ weaponID = pGren->GetEmitterWeaponID();
+ }
+ }
+ }
+
+ IGameEvent * event = gameeventmanager->CreateEvent( "dod_stats_player_killed" );
+ if ( event )
+ {
+ event->SetInt( "attacker", pAttacker->GetUserID() );
+ event->SetInt( "victim", GetUserID() );
+ event->SetInt( "weapon", weaponID );
+
+ gameeventmanager->FireEvent( event );
+ }
+
+ if ( DODGameRules()->IsInBonusRound() )
+ {
+ pAttacker->Stats_BonusRoundKill();
+ }
+ }
+
+ m_bIsDefusing = false;
+ m_bIsPlanting = false;
+
+ if ( pScorer != this )
+ {
+ StatEvent_WasKilled();
+ }
+
+ StatEvent_UploadStats();
+}
+
+bool CDODPlayer::ShouldInstantRespawn( void )
+{
+ CUtlVector<EHANDLE> *pSpawnPoints = DODGameRules()->GetSpawnPointListForTeam( GetTeamNumber() );
+
+ if ( !pSpawnPoints )
+ return false;
+
+ const float flMaxDistSqr = ( 500*500 );
+ Vector vecOrigin = GetAbsOrigin();
+ int i;
+ int count = pSpawnPoints->Count();
+ for ( i=0;i<count;i++ )
+ {
+ EHANDLE handle = pSpawnPoints->Element(i);
+ if ( handle )
+ {
+ CSpawnPoint *point = dynamic_cast<CSpawnPoint *>( handle.Get() );
+ if ( point && !point->IsDisabled() )
+ {
+ // range
+ Vector vecDist = point->GetAbsOrigin() - vecOrigin;
+ if ( vecDist.LengthSqr() > flMaxDistSqr )
+ {
+ continue;
+ }
+
+ // los
+ if ( FVisible( point ) )
+ {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CDODPlayer::InitVCollision( const Vector &vecAbsOrigin, const Vector &vecAbsVelocity )
+{
+ if ( 0 ) //if ( !sv_turbophysics.GetBool() )
+ {
+ BaseClass::InitVCollision( vecAbsOrigin, vecAbsVelocity );
+
+ // Setup the HL2 specific callback.
+ GetPhysicsController()->SetEventHandler( &playerCallback );
+ }
+}
+
+void CDODPlayer::VPhysicsShadowUpdate( IPhysicsObject *pPhysics )
+{
+ if ( 0 ) //if ( !sv_turbophysics.GetBool() )
+ {
+ if ( !CanMove() )
+ return;
+
+ BaseClass::VPhysicsShadowUpdate( pPhysics );
+ }
+}
+
+void CDODPlayer::PushawayThink()
+{
+ // Push physics props out of our way.
+ PerformObstaclePushaway( this );
+ SetNextThink( gpGlobals->curtime + PUSHAWAY_THINK_INTERVAL, DOD_PUSHAWAY_THINK_CONTEXT );
+}
+
+void CDODPlayer::CheatImpulseCommands( int iImpulse )
+{
+ switch( iImpulse )
+ {
+ case 101:
+ {
+ if( sv_cheats->GetBool() )
+ {
+ extern int gEvilImpulse101;
+ gEvilImpulse101 = true;
+
+ GiveAmmo( 1000, DOD_AMMO_COLT );
+ GiveAmmo( 1000, DOD_AMMO_P38 );
+ GiveAmmo( 1000, DOD_AMMO_C96 );
+ GiveAmmo( 1000, DOD_AMMO_GARAND );
+ GiveAmmo( 1000, DOD_AMMO_K98 );
+ GiveAmmo( 1000, DOD_AMMO_M1CARBINE );
+ GiveAmmo( 1000, DOD_AMMO_SPRING );
+ GiveAmmo( 1000, DOD_AMMO_SUBMG );
+ GiveAmmo( 1000, DOD_AMMO_BAR );
+ GiveAmmo( 1000, DOD_AMMO_30CAL );
+ GiveAmmo( 1000, DOD_AMMO_MG42 );
+ GiveAmmo( 1000, DOD_AMMO_ROCKET );
+
+ gEvilImpulse101 = false;
+ }
+ }
+ break;
+
+ default:
+ {
+ BaseClass::CheatImpulseCommands( iImpulse );
+ }
+ }
+}
+
+void CDODPlayer::SetupVisibility( CBaseEntity *pViewEntity, unsigned char *pvs, int pvssize )
+{
+ BaseClass::SetupVisibility( pViewEntity, pvs, pvssize );
+
+ int area = pViewEntity ? pViewEntity->NetworkProp()->AreaNum() : NetworkProp()->AreaNum();
+ PointCameraSetupVisibility( this, area, pvs, pvssize );
+}
+
+void CDODPlayer::DoAnimationEvent( PlayerAnimEvent_t event, int nData )
+{
+ m_PlayerAnimState->DoAnimationEvent( event, nData );
+ TE_PlayerAnimEvent( this, event, nData ); // Send to any clients who can see this guy.
+}
+
+CBaseEntity *CDODPlayer::GiveNamedItem( const char *pszName, int iSubType )
+{
+ EHANDLE pent;
+
+ pent = CreateEntityByName(pszName);
+ if ( pent == NULL )
+ {
+ Msg( "NULL Ent in GiveNamedItem!\n" );
+ return NULL;
+ }
+
+ pent->SetLocalOrigin( GetLocalOrigin() );
+ pent->AddSpawnFlags( SF_NORESPAWN );
+
+ if ( iSubType )
+ {
+ CBaseCombatWeapon *pWeapon = dynamic_cast<CBaseCombatWeapon*>( (CBaseEntity*)pent );
+ if ( pWeapon )
+ {
+ pWeapon->SetSubType( iSubType );
+ }
+ }
+
+ DispatchSpawn( pent );
+
+ if ( pent != NULL && !(pent->IsMarkedForDeletion()) )
+ {
+ pent->Touch( this );
+ }
+
+ return pent;
+}
+
+extern ConVar flashlight;
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CDODPlayer::FlashlightIsOn( void )
+{
+ return IsEffectActive( EF_DIMLIGHT );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CDODPlayer::FlashlightTurnOn( void )
+{
+ if( flashlight.GetInt() > 0 && IsAlive() )
+ {
+ AddEffects( EF_DIMLIGHT );
+ EmitSound( "Player.FlashlightOn" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CDODPlayer::FlashlightTurnOff( void )
+{
+ if( IsEffectActive(EF_DIMLIGHT) )
+ {
+ RemoveEffects( EF_DIMLIGHT );
+
+ if( m_iHealth > 0 )
+ {
+ EmitSound( "Player.FlashlightOff" );
+ }
+ }
+}
+
+
+void CDODPlayer::PostThink()
+{
+ BaseClass::PostThink();
+
+ if( m_Shared.IsProne() )
+ {
+ SetCollisionBounds( VEC_PRONE_HULL_MIN, VEC_PRONE_HULL_MAX );
+ }
+
+ if ( gpGlobals->curtime > m_fHandleSignalsTime )
+ {
+ m_fHandleSignalsTime = gpGlobals->curtime + 0.1;
+ HandleSignals();
+ }
+ QAngle angles = GetLocalAngles();
+ angles[PITCH] = 0;
+ SetLocalAngles( angles );
+
+ // Store the eye angles pitch so the client can compute its animation state correctly.
+ m_angEyeAngles = EyeAngles();
+
+ m_PlayerAnimState->Update( m_angEyeAngles[YAW], m_angEyeAngles[PITCH] );
+}
+
+void CDODPlayer::HandleCommand_Voice( const char *pcmd )
+{
+ // string should be formatted as "voice_gogogo"
+
+ // go through our list of voice commands and if we find it,
+ // emit a voice command
+
+ int i = 0;
+ while( g_VoiceCommands[i].pszCommandName != NULL )
+ {
+ if( Q_strcmp( pcmd, g_VoiceCommands[i].pszCommandName ) == 0 )
+ {
+ VoiceCommand( i );
+ break;
+ }
+ i++;
+ }
+}
+
+void CDODPlayer::VoiceCommand( int iVoiceCommand )
+{
+ //no voices in observer or when dead
+ if ( !IsAlive() || IsObserver() )
+ return;
+
+ //no voices when game is over
+ if (g_fGameOver)
+ return;
+
+ if (m_flNextVoice > gpGlobals->curtime )
+ return;
+
+ // Emit the voice sound
+ CPASFilter filter( WorldSpaceCenter() );
+
+ // Get the country text to fill into the sound name
+ char *pszCountry = "US";
+
+ if( GetTeamNumber() == TEAM_AXIS )
+ {
+ pszCountry = "German";
+ }
+
+ char szSound[128];
+ Q_snprintf( szSound, sizeof(szSound), "Voice.%s_%s", pszCountry, g_VoiceCommands[iVoiceCommand].pszSoundName );
+
+ EmitSound( filter, entindex(), szSound );
+
+ // Don't show the subtitle to the other team
+ int oppositeTeam = ( GetTeamNumber() == TEAM_ALLIES ) ? TEAM_AXIS : TEAM_ALLIES;
+ filter.RemoveRecipientsByTeam( GetGlobalDODTeam(oppositeTeam) );
+
+ // further reduce the voice command subtitle radius
+ float flDist;
+ float flMaxDist = 1900;
+ Vector vecEmitOrigin = GetAbsOrigin();
+
+ int i;
+ for ( i=1; i<= MAX_PLAYERS; i++ )
+ {
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
+
+ if ( !pPlayer )
+ continue;
+
+ flDist = ( pPlayer->GetAbsOrigin() - vecEmitOrigin ).Length2D();
+
+ if ( flDist > flMaxDist )
+ filter.RemoveRecipient( pPlayer );
+ }
+
+ // Send a subtitle to anyone in the PAS
+ UserMessageBegin( filter, "VoiceSubtitle" );
+ WRITE_BYTE( entindex() );
+ WRITE_BYTE( GetTeamNumber() );
+ WRITE_BYTE( iVoiceCommand );
+ MessageEnd();
+
+ PlayerAnimEvent_t iHandSignal = g_VoiceCommands[iVoiceCommand].iHandSignal;
+
+ if( iHandSignal != PLAYERANIMEVENT_HS_NONE )
+ DoAnimationEvent( iHandSignal );
+
+ m_flNextVoice = gpGlobals->curtime + 1.0;
+}
+
+void CDODPlayer::HandleCommand_HandSignal( const char *pcmd )
+{
+ // string should be formatted as "signal_yes"
+
+ int i = 0;
+ while( g_HandSignals[i].pszCommandName != NULL )
+ {
+ if( Q_strcmp( pcmd, g_HandSignals[i].pszCommandName ) == 0 )
+ {
+ HandSignal( i );
+ break;
+ }
+ i++;
+ }
+}
+
+void CDODPlayer::HandSignal( int iSignal )
+{
+ //no hand signals in observer or when dead
+ if ( !IsAlive() || IsObserver() )
+ return;
+
+ //or when game is over
+ if (g_fGameOver)
+ return;
+
+ if (m_flNextHandSignal > gpGlobals->curtime )
+ return;
+
+ int oppositeTeam = ( GetTeamNumber() == TEAM_ALLIES ) ? TEAM_AXIS : TEAM_ALLIES;
+
+ // Emit the voice sound
+ CRecipientFilter filter;
+ filter.AddRecipientsByPVS( WorldSpaceCenter() );
+ filter.RemoveRecipientsByTeam( GetGlobalTeam(oppositeTeam) );
+
+ // Send a subtitle to anyone in the PAS
+ UserMessageBegin( filter, "HandSignalSubtitle" );
+ WRITE_BYTE( entindex() );
+ WRITE_BYTE( iSignal );
+ MessageEnd();
+
+ DoAnimationEvent( g_HandSignals[iSignal].iHandSignal );
+
+ m_flNextHandSignal = gpGlobals->curtime + 1.0;
+}
+
+void CDODPlayer::DropGenericAmmo( void )
+{
+ if ( !IsAlive() )
+ return;
+
+ if( m_bHasGenericAmmo == false )
+ return;
+
+ Vector vForward, vRight, vUp;
+ AngleVectors( EyeAngles(), &vForward, &vRight, &vUp );
+
+ CAmmoBox *pAmmo = CAmmoBox::Create( WorldSpaceCenter(), vec3_angle, this, GetTeamNumber() );
+
+ pAmmo->ApplyAbsVelocityImpulse( vForward * 300 + vUp * 100 );
+
+ m_hLastDroppedAmmoBox = pAmmo;
+
+ m_bHasGenericAmmo = false;
+}
+
+void CDODPlayer::ReturnGenericAmmo( void )
+{
+ //play pickup sound
+ CPASFilter filter( WorldSpaceCenter() );
+ EmitSound( filter, entindex(), "BaseCombatCharacter.AmmoPickup" );
+
+ //allow them to drop generic ammo again
+ m_bHasGenericAmmo = true;
+}
+
+bool CDODPlayer::GiveGenericAmmo( void )
+{
+ //give primary weapon ammo
+ CWeaponDODBase *pWpn = (CWeaponDODBase *)Weapon_GetSlot( WPN_SLOT_PRIMARY );
+
+ if( pWpn )
+ {
+ int nAmmoType = pWpn->GetPrimaryAmmoType();
+
+ int iClipSize = pWpn->GetDODWpnData().iMaxClip1;
+
+ int iAmmoPickupClips = pWpn->GetDODWpnData().m_iAmmoPickupClips;
+
+ if( GiveAmmo( iAmmoPickupClips * iClipSize, nAmmoType, false ) > 0 )
+ {
+ //some ammo was picked up, consume the ammo box
+
+ //play pickup sound
+ CPASFilter filter( WorldSpaceCenter() );
+ EmitSound( filter, entindex(), "BaseCombatCharacter.AmmoPickup" );
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+ConVar dod_friendlyfiresafezone( "dod_friendlyfiresafezone",
+ "100",
+ FCVAR_ARCHIVE,
+ "Units around a player where they will not damage teammates, even if FF is on",
+ true, 0, false, 0 );
+
+void CDODPlayer::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ bool bTakeDamage = true;
+
+ CBasePlayer *pAttacker = (CBasePlayer*)ToBasePlayer( info.GetAttacker() );
+
+ bool bFriendlyFire = DODGameRules()->IsFriendlyFireOn();
+
+ if ( pAttacker && ( GetTeamNumber() == pAttacker->GetTeamNumber() ) )
+ {
+ bTakeDamage = bFriendlyFire;
+
+ // Create a FF-free zone around players for melee and bullets
+ if ( bFriendlyFire && ( info.GetDamageType() & (DMG_SLASH | DMG_BULLET | DMG_CLUB) ) )
+ {
+ float flDist = dod_friendlyfiresafezone.GetFloat();
+
+ Vector vecDist = pAttacker->WorldSpaceCenter() - WorldSpaceCenter();
+
+ if ( vecDist.LengthSqr() < ( flDist*flDist ) )
+ {
+ bTakeDamage = false;
+ }
+ }
+ }
+
+ if ( m_takedamage != DAMAGE_YES )
+ return;
+
+ m_LastHitGroup = ptr->hitgroup;
+ m_LastDamageType = info.GetDamageType();
+
+ Assert( ptr->hitgroup != HITGROUP_GENERIC );
+
+ m_nForceBone = ptr->physicsbone; //Save this bone for ragdoll
+
+ float flDamage = info.GetDamage();
+ float flOriginalDmg = flDamage;
+
+ bool bHeadShot = false;
+
+ //if its an enemy OR ff is on.
+ if( bTakeDamage )
+ {
+ m_Shared.SetSlowedTime( 0.5f );
+ }
+
+ char *szHitbox = NULL;
+
+ if( info.GetDamageType() & DMG_BLAST )
+ {
+ // don't do hitgroup specific grenade damage
+ flDamage *= 1.0;
+ szHitbox = "dmg_blast";
+ }
+ else
+ {
+ switch ( ptr->hitgroup )
+ {
+ case HITGROUP_HEAD:
+ {
+ flDamage *= 2.5; //regular head shot multiplier
+
+ if( bTakeDamage )
+ {
+ Vector dir = vecDir;
+ VectorNormalize(dir);
+
+ if ( info.GetDamageType() & DMG_CLUB )
+ dir *= 800;
+ else
+ dir *= 400;
+
+ dir.z += 100; // add some lift so it pops better.
+
+ PopHelmet( dir, ptr->endpos );
+ }
+
+ szHitbox = "head";
+
+ bHeadShot = true;
+ }
+ break;
+
+ case HITGROUP_CHEST:
+ szHitbox = "chest";
+ break;
+
+ case HITGROUP_STOMACH:
+ szHitbox = "stomach";
+ break;
+
+ case HITGROUP_LEFTARM:
+ flDamage *= 0.75;
+ szHitbox = "left arm";
+ break;
+
+ case HITGROUP_RIGHTARM:
+ flDamage *= 0.75;
+ szHitbox = "right arm";
+ break;
+
+ case HITGROUP_LEFTLEG:
+ case HITGROUP_RIGHTLEG:
+ {
+ //we are slowed for 2 full seconds if we get a leg hit
+ if( bTakeDamage )
+ {
+ //m_bSlowedByHit = true;
+ //m_flUnslowTime = gpGlobals->curtime + 2;
+ m_Shared.SetSlowedTime( 2.0f );
+ }
+
+ flDamage *= 0.75;
+
+ szHitbox = "leg";
+ }
+ break;
+
+ case HITGROUP_GENERIC:
+ szHitbox = "(error - hit generic)";
+ break;
+
+ default:
+ szHitbox = "(error - hit default)";
+ break;
+ }
+ }
+
+ if ( bTakeDamage )
+ {
+ if( dod_debugdamage.GetInt() )
+ {
+ char buf[256];
+
+ Q_snprintf( buf, sizeof(buf), "%s hit %s in the %s hitgroup for %f damage ( %f base damage ) ( %s now has %d health )\n",
+ pAttacker->GetPlayerName(),
+ GetPlayerName(),
+ szHitbox,
+ flDamage,
+ flOriginalDmg,
+ GetPlayerName(),
+ GetHealth() - (int)flDamage );
+
+ // print to server
+ UTIL_LogPrintf( "%s", buf );
+
+ // print to injured
+ ClientPrint( this, HUD_PRINTCONSOLE, buf );
+
+ // print to shooter
+ if ( pAttacker )
+ ClientPrint( pAttacker, HUD_PRINTCONSOLE, buf );
+ }
+
+ // Since this code only runs on the server, make sure it shows the tempents it creates.
+ CDisablePredictionFiltering disabler;
+
+ // This does smaller splotches on the guy and splats blood on the world.
+ TraceBleed( flDamage, vecDir, ptr, info.GetDamageType() );
+
+ CEffectData data;
+ data.m_vOrigin = ptr->endpos;
+ data.m_vNormal = vecDir * -1;
+ data.m_flScale = 4;
+ data.m_fFlags = FX_BLOODSPRAY_ALL;
+ data.m_nEntIndex = ptr->m_pEnt ? ptr->m_pEnt->entindex() : 0;
+ data.m_flMagnitude = flDamage;
+
+ DispatchEffect( "dodblood", data );
+
+ CTakeDamageInfo subInfo = info;
+
+ subInfo.SetDamage( flDamage );
+
+ AddMultiDamage( subInfo, this );
+ }
+}
+
+
+bool CDODPlayer::DropActiveWeapon( void )
+{
+ CWeaponDODBase *pWeapon = GetActiveDODWeapon();
+
+ if( pWeapon )
+ {
+ if( pWeapon->CanDrop() )
+ {
+ DODWeaponDrop( pWeapon, true );
+ return true;
+ }
+ else
+ {
+ int iWeaponType = pWeapon->GetDODWpnData().m_WeaponType;
+ if( iWeaponType == WPN_TYPE_BAZOOKA || iWeaponType == WPN_TYPE_MG )
+ {
+ // they are deployed, cannot drop
+ Hints()->HintMessage( "#game_cannot_drop_while" );
+ }
+ else
+ {
+ // must be a weapon type that cannot be dropped
+ Hints()->HintMessage( "#game_cannot_drop" );
+ }
+
+ return false;
+ }
+ }
+
+ return false;
+}
+
+CWeaponDODBase* CDODPlayer::GetActiveDODWeapon() const
+{
+ return dynamic_cast< CWeaponDODBase* >( GetActiveWeapon() );
+}
+
+void CDODPlayer::PreThink()
+{
+ BaseClass::PreThink();
+
+ if ( m_afButtonLast != m_nButtons )
+ m_flLastMovement = gpGlobals->curtime;
+
+ if ( g_fGameOver )
+ return;
+
+ State_PreThink();
+
+ //Reset bullet force accumulator, only lasts one frame, for ragdoll forces from multiple shots
+ m_vecTotalBulletForce = vec3_origin;
+
+ if ( mp_autokick.GetInt() && !IsBot() && !IsHLTV() )
+ {
+ if ( m_flLastMovement + mp_autokick.GetInt()*60 < gpGlobals->curtime )
+ {
+ UTIL_ClientPrintAll( HUD_PRINTCONSOLE, "#Game_idle_kick", GetPlayerName() );
+ engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", GetUserID() ) );
+ m_flLastMovement = gpGlobals->curtime;
+ }
+ }
+}
+
+
+bool CDODPlayer::DropPrimaryWeapon( void )
+{
+ CWeaponDODBase *pWeapon = (CWeaponDODBase *)Weapon_GetSlot( WPN_SLOT_PRIMARY );
+
+ if( pWeapon )
+ {
+ DODWeaponDrop( pWeapon, false );
+ return true;
+ }
+
+ return false;
+}
+
+bool CDODPlayer::DODWeaponDrop( CBaseCombatWeapon *pWeapon, bool bThrowForward )
+{
+ bool bSuccess = false;
+
+ if ( pWeapon )
+ {
+ Vector vForward;
+
+ AngleVectors( EyeAngles(), &vForward, NULL, NULL );
+ Vector vTossPos = WorldSpaceCenter();
+
+ if( bThrowForward )
+ vTossPos = vTossPos + vForward * 64;
+
+ Weapon_Drop( pWeapon, &vTossPos, NULL );
+
+ pWeapon->SetSolidFlags( FSOLID_NOT_STANDABLE | FSOLID_TRIGGER | FSOLID_USE_TRIGGER_BOUNDS );
+ pWeapon->SetMoveCollide( MOVECOLLIDE_FLY_BOUNCE );
+
+ CWeaponDODBase *pDODWeapon = dynamic_cast< CWeaponDODBase* >( pWeapon );
+
+ if( pDODWeapon )
+ {
+ pDODWeapon->SetDieThink(true);
+
+ pDODWeapon->SetWeaponModelIndex( pDODWeapon->GetDODWpnData().szWorldModel );
+
+ //Find out the index of the ammo type
+ int iAmmoIndex = pDODWeapon->GetPrimaryAmmoType();
+
+ //If it has an ammo type, find out how much the player has
+ if( iAmmoIndex != -1 )
+ {
+ int iAmmoToDrop = GetAmmoCount( iAmmoIndex );
+
+ //Add this much to the dropped weapon
+ pDODWeapon->SetExtraAmmoCount( iAmmoToDrop );
+
+ //Remove all ammo of this type from the player
+ SetAmmoCount( 0, iAmmoIndex );
+ }
+ }
+
+ //=========================================
+ // Teleport the weapon to the player's hand
+ //=========================================
+ int iBIndex = -1;
+ int iWeaponBoneIndex = -1;
+
+ CStudioHdr *hdr = pWeapon->GetModelPtr();
+ // If I have a hand, set the weapon position to my hand bone position.
+ if ( hdr && hdr->numbones() > 0 )
+ {
+ // Assume bone zero is the root
+ for ( iWeaponBoneIndex = 0; iWeaponBoneIndex < hdr->numbones(); ++iWeaponBoneIndex )
+ {
+ iBIndex = LookupBone( hdr->pBone( iWeaponBoneIndex )->pszName() );
+ // Found one!
+ if ( iBIndex != -1 )
+ {
+ break;
+ }
+ }
+
+ if ( iWeaponBoneIndex == hdr->numbones() )
+ return true;
+
+ if ( iBIndex == -1 )
+ {
+ iBIndex = LookupBone( "ValveBiped.Bip01_R_Hand" );
+ }
+ }
+ else
+ {
+ iBIndex = LookupBone( "ValveBiped.Bip01_R_Hand" );
+ }
+
+ if ( iBIndex != -1)
+ {
+ Vector origin;
+ QAngle angles;
+ matrix3x4_t transform;
+
+ // Get the transform for the weapon bonetoworldspace in the NPC
+ GetBoneTransform( iBIndex, transform );
+
+ // find offset of root bone from origin in local space
+ // Make sure we're detached from hierarchy before doing this!!!
+ pWeapon->StopFollowingEntity();
+ pWeapon->SetAbsOrigin( Vector( 0, 0, 0 ) );
+ pWeapon->SetAbsAngles( QAngle( 0, 0, 0 ) );
+ pWeapon->InvalidateBoneCache();
+ matrix3x4_t rootLocal;
+ pWeapon->GetBoneTransform( iWeaponBoneIndex, rootLocal );
+
+ // invert it
+ matrix3x4_t rootInvLocal;
+ MatrixInvert( rootLocal, rootInvLocal );
+
+ matrix3x4_t weaponMatrix;
+ ConcatTransforms( transform, rootInvLocal, weaponMatrix );
+ MatrixAngles( weaponMatrix, angles, origin );
+
+ // trace the bounding box of the weapon in the desired position
+ trace_t tr;
+ Vector mins, maxs;
+
+ // not exactly correct bounds, we haven't rotated them to match the attachment
+ pWeapon->CollisionProp()->WorldSpaceSurroundingBounds( &mins, &maxs );
+
+ UTIL_TraceHull( WorldSpaceCenter(), origin, mins, maxs, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.fraction < 1.0 )
+ origin = WorldSpaceCenter();
+
+ pWeapon->Teleport( &origin, &angles, NULL );
+
+ //Have to teleport the physics object as well
+
+ IPhysicsObject *pWeaponPhys = pWeapon->VPhysicsGetObject();
+
+ if( pWeaponPhys )
+ {
+ Vector vPos;
+ QAngle vAngles;
+ pWeaponPhys->GetPosition( &vPos, &vAngles );
+ pWeaponPhys->SetPosition( vPos, angles, true );
+
+ AngularImpulse angImp(0,0,0);
+ Vector vecAdd = GetAbsVelocity();
+ pWeaponPhys->AddVelocity( &vecAdd, &angImp );
+ }
+
+ m_hLastDroppedWeapon = pWeapon;
+ }
+
+ if ( !GetActiveWeapon() )
+ {
+ // we haven't auto-switched to anything usable
+ // switch to the first weapon we find, even if its empty
+
+ CBaseCombatWeapon *pCheck;
+
+ for ( int i = 0 ; i < WeaponCount(); ++i )
+ {
+ pCheck = GetWeapon( i );
+ if ( !pCheck || !pCheck->CanDeploy() )
+ {
+ continue;
+ }
+
+ Weapon_Switch( pCheck );
+ break;
+ }
+ }
+
+ bSuccess = true;
+ }
+
+ return bSuccess;
+}
+
+extern int g_iHelmetModels[NUM_HELMETS];
+
+void CDODPlayer::PopHelmet( Vector vecDir, Vector vecForceOrigin )
+{
+ int iPlayerClass = m_Shared.PlayerClass();
+
+ CDODTeam *pTeam = GetGlobalDODTeam( GetTeamNumber() );
+ const CDODPlayerClassInfo &pClassInfo = pTeam->GetPlayerClassInfo( iPlayerClass );
+
+ // See if they already lost their helmet
+ if( GetBodygroup( BODYGROUP_HELMET ) == pClassInfo.m_iHairGroup )
+ return;
+
+ // Nope.. take it off
+ SetBodygroup( BODYGROUP_HELMET, pClassInfo.m_iHairGroup );
+
+ // Add the velocity of the player
+ vecDir += GetAbsVelocity();
+
+ //CDisablePredictionFiltering disabler;
+
+ EntityMessageBegin( this, true );
+ WRITE_BYTE( DOD_PLAYER_POP_HELMET );
+ WRITE_VEC3COORD( vecDir );
+ WRITE_VEC3COORD( vecForceOrigin );
+ WRITE_SHORT( g_iHelmetModels[pClassInfo.m_iDropHelmet] );
+ MessageEnd();
+}
+
+bool CDODPlayer::BumpWeapon( CBaseCombatWeapon *pBaseWeapon )
+{
+ CWeaponDODBase *pWeapon = dynamic_cast< CWeaponDODBase* >( pBaseWeapon );
+ if ( !pWeapon )
+ {
+ Assert( false );
+ return false;
+ }
+
+ CBaseCombatCharacter *pOwner = pWeapon->GetOwner();
+
+ // Can I have this weapon type?
+ if ( pOwner || !Weapon_CanUse( pWeapon ) || !g_pGameRules->CanHavePlayerItem( this, pWeapon ) )
+ {
+ extern int gEvilImpulse101;
+ if ( gEvilImpulse101 )
+ {
+ UTIL_Remove( pWeapon );
+ }
+ return false;
+ }
+
+ // Don't let the player fetch weapons through walls
+ if( !pWeapon->FVisible( this ) && !(GetFlags() & FL_NOTARGET) )
+ {
+ return false;
+ }
+
+ /*
+ CBaseCombatWeapon *pOwnedWeapon = Weapon_OwnsThisType( pWeapon->GetClassname() );
+
+ if( pOwnedWeapon != NULL && ( pWeapon->GetWpnData().iFlags & ITEM_FLAG_EXHAUSTIBLE ) )
+ {
+ // its an item that we can hold several of, and use up
+
+ // Just give one ammo
+ if( GiveAmmo( 1, pOwnedWeapon->GetPrimaryAmmoType(), true ) > 0 )
+ return true;
+ else
+ return false;
+ }
+ */
+
+ // ----------------------------------------
+ // If I already have it just take the ammo
+ // ----------------------------------------
+ if (Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType()))
+ {
+ if( Weapon_EquipAmmoOnly( pWeapon ) )
+ {
+ // Only remove me if I have no ammo left
+ if ( pWeapon->HasPrimaryAmmo() )
+ return false;
+
+ UTIL_Remove( pWeapon );
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ pWeapon->CheckRespawn();
+
+ pWeapon->AddSolidFlags( FSOLID_NOT_SOLID );
+ pWeapon->AddEffects( EF_NODRAW );
+
+ Weapon_Equip( pWeapon );
+
+ int iExtraAmmo = pWeapon->GetExtraAmmoCount();
+
+ if( iExtraAmmo )
+ {
+ //Find out the index of the ammo
+ int iAmmoIndex = pWeapon->GetPrimaryAmmoType();
+
+ if( iAmmoIndex != -1 )
+ {
+ //Remove the extra ammo from the weapon
+ pWeapon->SetExtraAmmoCount(0);
+
+ //Give it to the player
+ SetAmmoCount( iExtraAmmo, iAmmoIndex );
+ }
+ }
+
+ return true;
+}
+
+
+bool CDODPlayer::HandleCommand_JoinClass( int iClass )
+{
+ Assert( GetTeamNumber() != TEAM_SPECTATOR );
+ Assert( GetTeamNumber() != TEAM_UNASSIGNED );
+
+ if( GetTeamNumber() == TEAM_SPECTATOR )
+ return false;
+
+ if( iClass == PLAYERCLASS_UNDEFINED )
+ return false; //they typed in something weird
+
+ int iOldPlayerClass = m_Shared.DesiredPlayerClass();
+
+ // See if we're joining the class we already are
+ if( iClass == iOldPlayerClass )
+ return true;
+
+ if( !DODGameRules()->IsPlayerClassOnTeam( iClass, GetTeamNumber() ) )
+ return false;
+
+ const char *classname = DODGameRules()->GetPlayerClassName( iClass, GetTeamNumber() );
+
+ if( DODGameRules()->CanPlayerJoinClass( this, iClass ) )
+ {
+ m_Shared.SetDesiredPlayerClass( iClass ); //real class value is set when the player spawns
+
+ if( State_Get() == STATE_PICKINGCLASS )
+ State_Transition( STATE_OBSERVER_MODE );
+
+ if( iClass == PLAYERCLASS_RANDOM )
+ {
+ if( IsAlive() )
+ {
+ ClientPrint(this, HUD_PRINTTALK, "#game_respawn_asrandom" );
+ }
+ else
+ {
+ ClientPrint(this, HUD_PRINTTALK, "#game_spawn_asrandom" );
+ }
+ }
+ else
+ {
+ if( IsAlive() )
+ {
+ ClientPrint(this, HUD_PRINTTALK, "#game_respawn_as", classname );
+ }
+ else
+ {
+ ClientPrint(this, HUD_PRINTTALK, "#game_spawn_as", classname );
+ }
+ }
+
+ IGameEvent * event = gameeventmanager->CreateEvent( "player_changeclass" );
+ if ( event )
+ {
+ event->SetInt( "userid", GetUserID() );
+ event->SetInt( "class", iClass );
+
+ gameeventmanager->FireEvent( event );
+ }
+
+ TallyLatestTimePlayedPerClass( GetTeamNumber(), iOldPlayerClass );
+
+ // if we're near a respawn point and can see it, just spawn us right away
+ if ( ShouldInstantRespawn() )
+ {
+ DODRespawn();
+ m_fNextSuicideTime = gpGlobals->curtime + 1.0;
+
+ // if we dropped our primary weapon, and noone else picked it up, destroy it
+ CBaseCombatWeapon *pWeapon = dynamic_cast<CBaseCombatWeapon *>( m_hLastDroppedWeapon.Get() );
+ if ( pWeapon )
+ {
+ if ( !pWeapon->GetOwner() )
+ {
+ UTIL_Remove( m_hLastDroppedWeapon.Get() );
+ }
+ }
+
+ // if we dropped our primary weapon, and noone else picked it up, destroy it
+ CAmmoBox *pAmmo = dynamic_cast<CAmmoBox *>( m_hLastDroppedAmmoBox.Get() );
+ if ( pAmmo )
+ {
+ UTIL_Remove( pAmmo );
+ }
+ }
+ }
+ else
+ {
+ ClientPrint(this, HUD_PRINTTALK, "#game_class_limit", classname );
+ ShowClassSelectMenu();
+ }
+
+ // if they missed the last wave while choosing class
+ // then restart it now
+ DODGameRules()->CreateOrJoinRespawnWave( this );
+
+ return true;
+}
+
+void CDODPlayer::ShowClassSelectMenu()
+{
+ if ( GetTeamNumber() == TEAM_ALLIES )
+ {
+ ShowViewPortPanel( PANEL_CLASS_ALLIES );
+ }
+ else if ( GetTeamNumber() == TEAM_AXIS )
+ {
+ ShowViewPortPanel( PANEL_CLASS_AXIS );
+ }
+}
+
+void CDODPlayer::CheckRotateIntroCam( void )
+{
+ // Update whatever intro camera it's at.
+ if( m_pIntroCamera && (gpGlobals->curtime >= m_fIntroCamTime) )
+ {
+ MoveToNextIntroCamera();
+ }
+}
+
+int CDODPlayer::GetHealthAsString( char *pDest, int iDestSize )
+{
+ Q_snprintf( pDest, iDestSize, "%d", GetHealth() );
+ return Q_strlen(pDest);
+}
+
+int CDODPlayer::GetLastPlayerIDAsString( char *pDest, int iDestSize )
+{
+ Q_snprintf( pDest, iDestSize, "last player id'd" );
+ return Q_strlen(pDest);
+}
+
+int CDODPlayer::GetClosestPlayerHealthAsString( char *pDest, int iDestSize )
+{
+ Q_snprintf( pDest, iDestSize, "some guy's health" );
+ return Q_strlen(pDest);
+}
+
+int CDODPlayer::GetPlayerClassAsString( char *pDest, int iDestSize )
+{
+ int team = GetTeamNumber();
+
+ if( team == TEAM_ALLIES || team == TEAM_AXIS )
+ {
+ const char *pszClassName = DODGameRules()->GetPlayerClassName( m_Shared.PlayerClass(), GetTeamNumber() );
+
+ Q_snprintf( pDest, iDestSize, "%s", pszClassName );
+ return Q_strlen(pDest);
+ }
+ else
+ {
+ pDest[0] = '\0';
+ return 0;
+ }
+}
+
+int CDODPlayer::GetNearestLocationAsString( char *pDest, int iDestSize )
+{
+ float flMinDist = FLT_MAX;
+ float flDist;
+
+ const char *pLocationName = "";
+
+ CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "dod_control_point" );
+
+ while( pEnt )
+ {
+ Vector vecDelta = GetAbsOrigin() - pEnt->GetAbsOrigin();
+ flDist = vecDelta.Length();
+
+ if( flDist < flMinDist )
+ {
+ CControlPoint *pPoint = static_cast< CControlPoint* >( pEnt );
+ pLocationName = pPoint->GetName();
+ flMinDist = flDist;
+ }
+
+ pEnt = gEntList.FindEntityByClassname( pEnt, "dod_control_point" );
+ }
+
+ pEnt = gEntList.FindEntityByClassname( NULL, "dod_location" );
+
+ while( pEnt )
+ {
+ Vector vecDelta = GetAbsOrigin() - pEnt->GetAbsOrigin();
+ flDist = vecDelta.Length();
+
+ if( flDist < flMinDist )
+ {
+ CDODLocation *pPoint = static_cast< CDODLocation* >( pEnt );
+ pLocationName = pPoint->GetName();
+ flMinDist = flDist;
+ }
+
+ pEnt = gEntList.FindEntityByClassname( pEnt, "dod_location" );
+ }
+
+ Q_snprintf( pDest, iDestSize, "%s", pLocationName );
+ return Q_strlen(pDest);
+}
+
+int CDODPlayer::GetTimeleftAsString( char *pDest, int iDestSize )
+{
+ if( !DODGameRules()->IsGameUnderTimeLimit() )
+ {
+ Q_snprintf( pDest, iDestSize, "-:--" );
+ return 4;
+ }
+ else
+ {
+
+ int iTimeLeft = DODGameRules()->GetTimeLeft();
+
+ if ( iTimeLeft < 0 )
+ {
+ Q_snprintf( pDest, iDestSize, "0:00" );
+ return 4;
+ }
+
+ int iMinutes = iTimeLeft / 60;
+ int iSeconds = iTimeLeft % 60;
+
+ Q_snprintf( pDest, iDestSize, "%d:%.02d", iMinutes, iSeconds );
+ return Q_strlen(pDest);
+ }
+}
+
+// Copy the result into pDest
+// return value is number of chars copied
+int CDODPlayer::GetStringForEscapeSequence( char c, char *pDest, int iDestSize )
+{
+ int iCharsCopied = 0;
+
+ switch( c )
+ {
+ case 't':
+ iCharsCopied = GetTimeleftAsString( pDest, iDestSize );
+ break;
+ case 'h':
+ iCharsCopied = GetHealthAsString( pDest, iDestSize );
+ break;
+ case 'l':
+ iCharsCopied = GetNearestLocationAsString( pDest, iDestSize );
+ break;
+ case 'c':
+ iCharsCopied = GetPlayerClassAsString( pDest, iDestSize );
+ break;
+ /*case 'i':
+ iCharsCopied = GetLastPlayerIDAsString( pDest, iDestSize );
+ break;
+ case 'r':
+ iCharsCopied = GetClosestPlayerHealthAsString( pDest, iDestSize );
+ break;
+ */
+ default:
+ iCharsCopied = 0;
+ break;
+ }
+
+ return iCharsCopied;
+}
+
+void CDODPlayer::CheckChatText( char *p, int bufsize )
+{
+ //Look for escape sequences and replace
+
+ char *buf = new char[bufsize];
+ int pos = 0;
+
+ // Parse say text for escape sequences
+ for ( char *pSrc = p; pSrc != NULL && *pSrc != 0 && pos < bufsize-1; pSrc++ )
+ {
+ if ( *pSrc == '%' )
+ {
+ char szSubst[32];
+
+ int iResult = GetStringForEscapeSequence( *(pSrc+1), szSubst, sizeof(szSubst) );
+
+ if( iResult )
+ {
+ buf[pos] = '\0';
+ Q_strncat( buf, szSubst, bufsize, COPY_ALL_CHARACTERS );
+
+ pos = MIN( pos + Q_strlen(szSubst), bufsize-1 );
+
+ pSrc++;
+ }
+ else
+ {
+ //just copy in the '%'
+ buf[pos] = *pSrc;
+ pos++;
+ }
+ }
+ else
+ {
+ // copy each char across
+ buf[pos] = *pSrc;
+ pos++;
+ }
+ }
+
+ buf[pos] = '\0';
+
+ // copy buf back into p
+ Q_strncpy( p, buf, bufsize );
+
+ delete[] buf;
+
+ const char *pReadyCheck = p;
+
+ DODGameRules()->CheckChatForReadySignal( this, pReadyCheck );
+}
+
+bool CDODPlayer::ClientCommand( const CCommand &args )
+{
+ const char *pcmd = args[0];
+ if ( FStrEq( pcmd, "jointeam" ) )
+ {
+ if ( args.ArgC() < 2 )
+ {
+ Warning( "Player sent bad jointeam syntax\n" );
+ }
+
+ int iTeam = atoi( args[1] );
+ HandleCommand_JoinTeam( iTeam );
+ return true;
+ }
+ else if( !Q_strncmp( pcmd, "cls_", 4 ) )
+ {
+ CDODTeam *pTeam = GetGlobalDODTeam( GetTeamNumber() );
+
+ Assert( pTeam );
+
+ int iClassIndex = PLAYERCLASS_UNDEFINED;
+
+ if( pTeam->IsClassOnTeam( pcmd, iClassIndex ) )
+ {
+ HandleCommand_JoinClass( iClassIndex );
+ }
+ else
+ {
+ DevMsg( "player tried to join a class that isn't on this team ( %s )\n", pcmd );
+ ShowClassSelectMenu();
+ }
+ return true;
+ }
+ else if ( FStrEq( pcmd, "spectate" ) )
+ {
+ // instantly join spectators
+ HandleCommand_JoinTeam( TEAM_SPECTATOR );
+ return true;
+ }
+ else if ( FStrEq( pcmd, "joingame" ) )
+ {
+ // player just closed MOTD dialog
+ if ( m_iPlayerState == STATE_WELCOME )
+ {
+ State_Transition( STATE_PICKINGTEAM );
+ }
+
+ return true;
+ }
+ else if ( FStrEq( pcmd, "joinclass" ) )
+ {
+ if ( args.ArgC() < 2 )
+ {
+ Warning( "Player sent bad joinclass syntax\n" );
+ }
+
+ int iClass = atoi( args[1] );
+ HandleCommand_JoinClass( iClass );
+ return true;
+ }
+ else if ( FStrEq( pcmd, "dropammo" ) )
+ {
+ DropGenericAmmo();
+ return true;
+ }
+ else if ( FStrEq( pcmd, "drop" ) )
+ {
+ DropActiveWeapon();
+ return true;
+ }
+ else if( !Q_strncmp( pcmd, "voice_", 6 ) )
+ {
+ HandleCommand_Voice( pcmd );
+ return true;
+ }
+ else if( !Q_strncmp( pcmd, "signal_", 7 ) )
+ {
+ HandleCommand_HandSignal( pcmd );
+ return true;
+ }
+ else if ( FStrEq( pcmd, "extendfreeze" ) )
+ {
+ m_flDeathTime += 2.0f;
+ return true;
+ }
+
+#if defined ( DEBUG )
+ else if ( FStrEq( pcmd, "debug" ) )
+ {
+ DODGameRules()->WriteStatsFile( "1.xml" );
+ return true;
+ }
+ else if ( FStrEq( pcmd, "test_stun" ) )
+ {
+ Vector vecForward;
+ AngleVectors( EyeAngles(), &vecForward );
+
+ trace_t tr;
+ UTIL_TraceLine( EyePosition(), EyePosition() + vecForward * 1000, MASK_SOLID, NULL, &tr );
+
+ float flStunAmount = 100;
+ float flStunRadius = 100;
+
+ if ( args.ArgC() > 1 )
+ {
+ flStunAmount = atof( args[1] );
+ }
+
+ if ( args.ArgC() > 2 )
+ {
+ flStunRadius = atof( args[2] );
+ }
+
+ if ( tr.fraction < 1.0 )
+ {
+ CTakeDamageInfo info( this, this, vec3_origin, tr.endpos, flStunAmount, DMG_STUN );
+ DODGameRules()->RadiusStun( info, tr.endpos, flStunRadius );
+ }
+
+ return true;
+ }
+ else if ( FStrEq( pcmd, "switch_prim" ) )
+ {
+ CWeaponDODBase *pWpn = (CWeaponDODBase *)Weapon_GetSlot( WPN_SLOT_PRIMARY );
+
+ Weapon_Switch( pWpn );
+ return true;
+ }
+ else if ( FStrEq( pcmd, "switch_sec" ) )
+ {
+ CWeaponDODBase *pWpn = (CWeaponDODBase *)Weapon_GetSlot( WPN_SLOT_SECONDARY );
+
+ Weapon_Switch( pWpn );
+ return true;
+ }
+ else if ( FStrEq( pcmd, "switch_melee" ) )
+ {
+ CWeaponDODBase *pWpn = (CWeaponDODBase *)Weapon_GetSlot( WPN_SLOT_MELEE );
+
+ Weapon_Switch( pWpn );
+ return true;
+ }
+ else if ( FStrEq( pcmd, "switch_gren" ) )
+ {
+ CWeaponDODBase *pWpn = (CWeaponDODBase *)Weapon_GetSlot( WPN_SLOT_GRENADES );
+
+ Weapon_Switch( pWpn );
+ return true;
+ }
+ else if ( FStrEq( pcmd, "switch_tnt" ) )
+ {
+ CWeaponDODBase *pWpn = (CWeaponDODBase *)Weapon_GetSlot( WPN_SLOT_BOMB );
+
+ Weapon_Switch( pWpn );
+ return true;
+ }
+ else if ( FStrEq( pcmd, "nextwpn" ) )
+ {
+ CBaseCombatWeapon *pWpn = GetActiveWeapon();
+
+ SwitchToNextBestWeapon( pWpn );
+ return true;
+ }
+ else if ( FStrEq( pcmd, "smoke" ) )
+ {
+ CWeaponDODBase *pWpn = (CWeaponDODBase *)Weapon_GetSlot( 3 );
+
+ Weapon_Switch( pWpn );
+ return true;
+ }
+ else if ( FStrEq( pcmd, "resetents" ) )
+ {
+ DODGameRules()->CleanUpMap();
+ return true;
+ }
+ else if( FStrEq( pcmd, "pop" ) )
+ {
+ Vector vecDir(0,0,200);
+
+ if ( args.ArgC() > 1 )
+ {
+ vecDir.z = atof( args[1] );
+ }
+
+ PopHelmet( vecDir, vec3_origin );
+ return true;
+ }
+ else if ( FStrEq( pcmd, "bandage" ) )
+ {
+ if ( sv_cheats->GetBool() )
+ {
+ Bandage();
+ }
+ return true;
+ }
+ else if ( FStrEq( pcmd, "printstats" ) )
+ {
+ PrintLifetimeStats();
+ return true;
+ }
+
+#endif //_DEBUG
+
+ return BaseClass::ClientCommand( args );
+}
+
+// returns true if the selection has been handled and the player's menu
+// can be closed...false if the menu should be displayed again
+bool CDODPlayer::HandleCommand_JoinTeam( int team )
+{
+ CDODGameRules *mp = DODGameRules();
+ int iOldTeam = GetTeamNumber();
+ int iOldPlayerClass = m_Shared.DesiredPlayerClass();
+
+ if ( !GetGlobalTeam( team ) )
+ {
+ Warning( "HandleCommand_JoinTeam( %d ) - invalid team index.\n", team );
+ return false;
+ }
+
+ // If we already died and changed teams once, deny
+ if( m_bTeamChanged && team != TEAM_SPECTATOR && iOldTeam != TEAM_SPECTATOR )
+ {
+ ClientPrint( this, HUD_PRINTCENTER, "game_switch_teams_once" );
+ return true;
+ }
+
+ if ( team == TEAM_UNASSIGNED )
+ {
+ // Attempt to auto-select a team, may set team to T, CT or SPEC
+ team = mp->SelectDefaultTeam();
+
+ if ( team == TEAM_UNASSIGNED )
+ {
+ // still team unassigned, try to kick a bot if possible
+
+ ClientPrint( this, HUD_PRINTTALK, "#All_Teams_Full" );
+
+ team = TEAM_SPECTATOR;
+ }
+ }
+
+ if ( team == iOldTeam )
+ return true; // we wouldn't change the team
+
+ if ( mp->TeamFull( team ) )
+ {
+ if ( team == TEAM_ALLIES )
+ {
+ ClientPrint( this, HUD_PRINTTALK, "#Allies_Full" );
+ }
+ else if ( team == TEAM_AXIS )
+ {
+ ClientPrint( this, HUD_PRINTTALK, "#Axis_Full" );
+ }
+
+ ShowViewPortPanel( PANEL_TEAM );
+ return false;
+ }
+
+ if ( team == TEAM_SPECTATOR )
+ {
+ // Prevent this if the cvar is set
+ if ( !mp_allowspectators.GetInt() && !IsHLTV() )
+ {
+ ClientPrint( this, HUD_PRINTTALK, "#Cannot_Be_Spectator" );
+ ShowViewPortPanel( PANEL_TEAM );
+ return false;
+ }
+
+ ChangeTeam( TEAM_SPECTATOR );
+
+ return true;
+ }
+
+ // If the code gets this far, the team is not TEAM_UNASSIGNED
+
+ // Player is switching to a new team (It is possible to switch to the
+ // same team just to choose a new appearance)
+
+ if (mp->TeamStacked( team, GetTeamNumber() ))//players are allowed to change to their own team so they can just change their model
+ {
+ // The specified team is full
+ ClientPrint(
+ this,
+ HUD_PRINTCENTER,
+ ( team == TEAM_ALLIES ) ? "#Allies_full" : "#Axis_full" );
+
+ ShowViewPortPanel( PANEL_TEAM );
+ return false;
+ }
+
+ // Show the appropriate Choose Appearance menu
+ // This must come before ClientKill() for CheckWinConditions() to function properly
+
+ // Switch their actual team...
+ ChangeTeam( team );
+
+ DODGameRules()->CreateOrJoinRespawnWave( this );
+
+ // Force them to choose a new class
+ m_Shared.SetDesiredPlayerClass( PLAYERCLASS_UNDEFINED );
+ m_Shared.SetPlayerClass( PLAYERCLASS_UNDEFINED );
+
+ TallyLatestTimePlayedPerClass( iOldTeam, iOldPlayerClass );
+
+ return true;
+}
+
+void CDODPlayer::State_Transition( DODPlayerState newState )
+{
+ State_Leave();
+ State_Enter( newState );
+}
+
+
+void CDODPlayer::State_Enter( DODPlayerState newState )
+{
+ m_iPlayerState = newState;
+ m_pCurStateInfo = State_LookupInfo( newState );
+
+ if ( dod_playerstatetransitions.GetInt() == -1 || dod_playerstatetransitions.GetInt() == entindex() )
+ {
+ if ( m_pCurStateInfo )
+ Msg( "ShowStateTransitions: entering '%s'\n", m_pCurStateInfo->m_pStateName );
+ else
+ Msg( "ShowStateTransitions: entering #%d\n", newState );
+ }
+
+ // Initialize the new state.
+ if ( m_pCurStateInfo && m_pCurStateInfo->pfnEnterState )
+ (this->*m_pCurStateInfo->pfnEnterState)();
+}
+
+void CDODPlayer::State_Leave()
+{
+ if ( m_pCurStateInfo && m_pCurStateInfo->pfnLeaveState )
+ {
+ (this->*m_pCurStateInfo->pfnLeaveState)();
+ }
+}
+
+
+void CDODPlayer::State_PreThink()
+{
+ if ( m_pCurStateInfo && m_pCurStateInfo->pfnPreThink )
+ {
+ (this->*m_pCurStateInfo->pfnPreThink)();
+ }
+}
+
+
+CDODPlayerStateInfo* CDODPlayer::State_LookupInfo( DODPlayerState state )
+{
+ // This table MUST match the
+ static CDODPlayerStateInfo playerStateInfos[] =
+ {
+ { STATE_ACTIVE, "STATE_ACTIVE", &CDODPlayer::State_Enter_ACTIVE, NULL, &CDODPlayer::State_PreThink_ACTIVE },
+ { STATE_WELCOME, "STATE_WELCOME", &CDODPlayer::State_Enter_WELCOME, NULL, &CDODPlayer::State_PreThink_WELCOME },
+ { STATE_PICKINGTEAM, "STATE_PICKINGTEAM", &CDODPlayer::State_Enter_PICKINGTEAM, NULL, &CDODPlayer::State_PreThink_PICKING },
+ { STATE_PICKINGCLASS, "STATE_PICKINGCLASS", &CDODPlayer::State_Enter_PICKINGCLASS, NULL, &CDODPlayer::State_PreThink_PICKING },
+ { STATE_DEATH_ANIM, "STATE_DEATH_ANIM", &CDODPlayer::State_Enter_DEATH_ANIM, NULL, &CDODPlayer::State_PreThink_DEATH_ANIM },
+ { STATE_OBSERVER_MODE, "STATE_OBSERVER_MODE", &CDODPlayer::State_Enter_OBSERVER_MODE, NULL, &CDODPlayer::State_PreThink_OBSERVER_MODE }
+ };
+
+ for ( int i=0; i < ARRAYSIZE( playerStateInfos ); i++ )
+ {
+ if ( playerStateInfos[i].m_iPlayerState == state )
+ return &playerStateInfos[i];
+ }
+
+ return NULL;
+}
+
+void CDODPlayer::PhysObjectSleep()
+{
+ IPhysicsObject *pObj = VPhysicsGetObject();
+ if ( pObj )
+ pObj->Sleep();
+}
+
+void CDODPlayer::PhysObjectWake()
+{
+ IPhysicsObject *pObj = VPhysicsGetObject();
+ if ( pObj )
+ pObj->Wake();
+}
+
+void CDODPlayer::State_Enter_WELCOME()
+{
+ SetMoveType( MOVETYPE_NONE );
+ AddSolidFlags( FSOLID_NOT_SOLID );
+
+ PhysObjectSleep();
+
+ // Show info panel
+ if ( IsBot() )
+ {
+ // If they want to auto join a team for debugging, pretend they clicked the button.
+ CCommand args;
+ args.Tokenize( "joingame" );
+ ClientCommand( args );
+ }
+ else
+ {
+ const ConVar *hostname = cvar->FindVar( "hostname" );
+ const char *title = (hostname) ? hostname->GetString() : "MESSAGE OF THE DAY";
+
+ KeyValues *data = new KeyValues("data");
+ data->SetString( "title", title ); // info panel title
+ data->SetString( "type", "1" ); // show userdata from stringtable entry
+ data->SetString( "msg", "motd" ); // use this stringtable entry
+ data->SetInt( "cmd", TEXTWINDOW_CMD_CHANGETEAM ); // exec this command if panel closed
+ data->SetBool( "unload", sv_motd_unload_on_dismissal.GetBool() );
+
+ ShowViewPortPanel( PANEL_INFO, true, data );
+
+ data->deleteThis();
+ }
+}
+
+
+void CDODPlayer::State_PreThink_WELCOME()
+{
+ // Verify some state.
+ Assert( GetMoveType() == MOVETYPE_NONE );
+ Assert( IsSolidFlagSet( FSOLID_NOT_SOLID ) );
+ Assert( GetAbsVelocity().Length() == 0 );
+
+ CheckRotateIntroCam();
+}
+
+
+void CDODPlayer::State_Enter_PICKINGTEAM()
+{
+ ShowViewPortPanel( PANEL_TEAM );
+}
+
+void CDODPlayer::State_PreThink_PICKING()
+{
+}
+
+void CDODPlayer::State_Enter_DEATH_ANIM()
+{
+ 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();
+ }
+
+ // Used for a timer.
+ m_flDeathTime = gpGlobals->curtime;
+
+ StartObserverMode( OBS_MODE_DEATHCAM ); // go to observer mode
+
+ RemoveEffects( EF_NODRAW ); // still draw player body
+
+ DODGameRules()->CreateOrJoinRespawnWave( this );
+
+ m_bAbortFreezeCam = false;
+
+ m_bPlayedFreezeCamSound = false;
+}
+
+#define DOD_DEATH_ANIMATION_TIME 1.6f
+#define DOD_FREEZECAM_LENGTH 3.4f
+
+void CDODPlayer::State_PreThink_DEATH_ANIM()
+{
+ // If the anim is done playing, go to the next state (waiting for a keypress to
+ // either respawn the guy or put him into observer mode).
+ if ( GetFlags() & FL_ONGROUND )
+ {
+ float flForward = GetAbsVelocity().Length() - 20;
+ if (flForward <= 0)
+ {
+ SetAbsVelocity( vec3_origin );
+ }
+ else
+ {
+ Vector vAbsVel = GetAbsVelocity();
+ VectorNormalize( vAbsVel );
+ vAbsVel *= flForward;
+ SetAbsVelocity( vAbsVel );
+ }
+ }
+
+ if ( dod_freezecam.GetBool() )
+ {
+ // important! freeze end time must match DEATH_CAM_TIME
+ // or else players will start missing respawn waves.
+
+ float flFreezeEnd = (m_flDeathTime + DOD_DEATH_ANIMATION_TIME + DOD_FREEZECAM_LENGTH );
+
+ if ( !m_bPlayedFreezeCamSound && GetObserverTarget() && GetObserverTarget() != this )
+ {
+ // Start the sound so that it ends at the freezecam lock on time
+ float flFreezeSoundLength = 0.3;
+ float flFreezeSoundTime = m_flDeathTime + DOD_DEATH_ANIMATION_TIME - flFreezeSoundLength + 0.4f /* travel time */;
+ if ( gpGlobals->curtime >= flFreezeSoundTime )
+ {
+ CSingleUserRecipientFilter filter( this );
+ EmitSound_t params;
+ params.m_flSoundTime = 0;
+ params.m_pSoundName = "Player.FreezeCam";
+ EmitSound( filter, entindex(), params );
+
+ m_bPlayedFreezeCamSound = true;
+ }
+ }
+
+ if ( gpGlobals->curtime >= (m_flDeathTime + DOD_DEATH_ANIMATION_TIME ) ) // allow 2 seconds death animation / death cam
+ {
+ if ( GetObserverTarget() && GetObserverTarget() != this )
+ {
+ if ( !m_bAbortFreezeCam && gpGlobals->curtime < flFreezeEnd )
+ {
+ if ( GetObserverMode() != OBS_MODE_FREEZECAM )
+ {
+ StartObserverMode( OBS_MODE_FREEZECAM );
+ PhysObjectSleep();
+ }
+ return;
+ }
+ }
+
+ if ( GetObserverMode() == OBS_MODE_FREEZECAM )
+ {
+ // If we're in freezecam, and we want out, abort.
+ if ( m_bAbortFreezeCam )
+ {
+ m_lifeState = LIFE_DEAD;
+
+ StopAnimation();
+
+ IncrementInterpolationFrame();
+
+ if ( GetMoveType() != MOVETYPE_NONE && (GetFlags() & FL_ONGROUND) )
+ SetMoveType( MOVETYPE_NONE );
+
+ State_Transition( STATE_OBSERVER_MODE );
+ }
+ }
+
+ // Don't allow anyone to respawn until freeze time is over, even if they're not
+ // in freezecam. This prevents players skipping freezecam to spawn faster.
+
+ if ( gpGlobals->curtime >= flFreezeEnd )
+ {
+ m_lifeState = LIFE_DEAD;
+
+ StopAnimation();
+
+ IncrementInterpolationFrame();
+
+ if ( GetMoveType() != MOVETYPE_NONE && (GetFlags() & FL_ONGROUND) )
+ SetMoveType( MOVETYPE_NONE );
+
+ State_Transition( STATE_OBSERVER_MODE );
+ }
+ }
+ }
+ else
+ {
+ if ( gpGlobals->curtime >= (m_flDeathTime + DEATH_CAM_TIME ) ) // allow x seconds death animation / death cam
+ {
+ m_lifeState = LIFE_DEAD;
+
+ StopAnimation();
+
+ IncrementInterpolationFrame();
+
+ if ( GetMoveType() != MOVETYPE_NONE && (GetFlags() & FL_ONGROUND) )
+ SetMoveType( MOVETYPE_NONE );
+
+ // Disabled death cam!
+ //StartReplayMode( 8, 8, entindex() );
+
+ State_Transition( STATE_OBSERVER_MODE );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CDODPlayer::AttemptToExitFreezeCam( void )
+{
+ float flFreezeTravelTime = (m_flDeathTime + DOD_DEATH_ANIMATION_TIME ) + 0.4 /*spec_freeze_traveltime.GetFloat()*/ + 0.5;
+ if ( gpGlobals->curtime < flFreezeTravelTime )
+ return;
+
+ m_bAbortFreezeCam = true;
+}
+
+void CDODPlayer::State_Enter_OBSERVER_MODE()
+{
+ // Always start a spectator session in chase mode
+ m_iObserverLastMode = OBS_MODE_CHASE;
+
+ if( m_hObserverTarget == NULL )
+ {
+ // find a new observer target
+ CheckObserverSettings();
+ }
+
+ // Change our observer target to the player nearest us
+ CTeam *pTeam = GetGlobalTeam( TEAM_ALLIES );
+
+ CBasePlayer *pPlayer;
+ Vector localOrigin = GetAbsOrigin();
+ Vector targetOrigin;
+ float flMinDist = FLT_MAX;
+ float flDist;
+
+ for ( int i=0;i<pTeam->GetNumPlayers();i++ )
+ {
+ pPlayer = pTeam->GetPlayer(i);
+
+ if ( !pPlayer )
+ continue;
+
+ if ( !IsValidObserverTarget(pPlayer) )
+ continue;
+
+ targetOrigin = pPlayer->GetAbsOrigin();
+
+ flDist = ( targetOrigin - localOrigin ).Length();
+
+ if ( flDist < flMinDist )
+ {
+ m_hObserverTarget.Set( pPlayer );
+ flMinDist = flDist;
+ }
+ }
+
+ if ( GetTeamNumber() == TEAM_ALLIES || GetTeamNumber() == TEAM_AXIS )
+ {
+ // we are entering spec while playing ( not on TEAM_SPECTATOR )
+ Hints()->HintMessage( HINT_CLASSMENU, true );
+ }
+
+ StartObserverMode( m_iObserverLastMode );
+
+ PhysObjectSleep();
+}
+
+void CDODPlayer::State_PreThink_OBSERVER_MODE()
+{
+ // Make sure nobody has changed any of our state.
+ Assert( m_takedamage == DAMAGE_NO );
+ Assert( IsSolidFlagSet( FSOLID_NOT_SOLID ) );
+
+ // Must be dead.
+ Assert( m_lifeState == LIFE_DEAD );
+ Assert( pl.deadflag );
+}
+
+void CDODPlayer::State_Enter_PICKINGCLASS()
+{
+ // go to spec mode, if dying keep deathcam
+ if ( GetObserverMode() == OBS_MODE_DEATHCAM )
+ {
+ StartObserverMode( OBS_MODE_DEATHCAM );
+ }
+ else
+ {
+ StartObserverMode( OBS_MODE_ROAMING );
+ }
+
+ PhysObjectSleep();
+
+ ShowClassSelectMenu();
+}
+
+void CDODPlayer::State_Enter_ACTIVE()
+{
+ SetMoveType( MOVETYPE_WALK );
+ RemoveSolidFlags( FSOLID_NOT_SOLID );
+ m_Local.m_iHideHUD = 0;
+ PhysObjectWake();
+}
+
+void CDODPlayer::State_PreThink_ACTIVE()
+{
+ return;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Initialize prone at spawn.
+//-----------------------------------------------------------------------------
+void CDODPlayer::InitProne( void )
+{
+ m_Shared.SetProne( false, true );
+}
+
+void CDODPlayer::InitSprinting( void )
+{
+ m_Shared.SetSprinting( false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns whether or not we are allowed to sprint now.
+//-----------------------------------------------------------------------------
+bool CDODPlayer::CanSprint()
+{
+ return (
+ //!IsWalking() && // Not if we're walking
+ !( m_Local.m_bDucked && !m_Local.m_bDucking ) && // Nor if we're ducking
+ (GetWaterLevel() != 3) ); // Certainly not underwater
+}
+
+void CDODPlayer::MoveToNextIntroCamera()
+{
+ m_pIntroCamera = gEntList.FindEntityByClassname( m_pIntroCamera, "point_viewcontrol" );
+
+ // if m_pIntroCamera is NULL we just were at end of list, start searching from start again
+ if(!m_pIntroCamera)
+ m_pIntroCamera = gEntList.FindEntityByClassname(m_pIntroCamera, "point_viewcontrol");
+
+ if( !m_pIntroCamera ) //if there are no cameras find a spawn point and black out the screen
+ {
+ DODGameRules()->GetPlayerSpawnSpot( this );
+ SetAbsAngles( QAngle( 0, 0, 0 ) );
+ m_pIntroCamera = NULL; // never update again
+ }
+ else
+ {
+ Vector vIntroCamera = m_pIntroCamera->GetAbsOrigin();
+
+ QAngle CamAngles = m_pIntroCamera->GetAbsAngles();
+
+ UTIL_SetSize( this, vec3_origin, vec3_origin );
+
+ SetAbsOrigin( vIntroCamera );
+ SetAbsAngles( CamAngles );
+ SnapEyeAngles( CamAngles );
+ SetViewOffset( vec3_origin );
+ m_fIntroCamTime = gpGlobals->curtime + 6;
+ }
+}
+
+CBaseEntity *CDODPlayer::SelectSpawnSpot( CUtlVector<EHANDLE> *pSpawnPoints, int &iLastSpawnIndex )
+{
+ Assert( pSpawnPoints );
+
+ int iNumSpawns = pSpawnPoints->Count();
+
+ CBaseEntity *pSpot = NULL;
+
+ for ( int i=0;i<iNumSpawns;i++ )
+ {
+ int testIndex = ( iLastSpawnIndex + i ) % iNumSpawns;
+
+ EHANDLE handle = pSpawnPoints->Element(testIndex);
+
+ if ( !handle )
+ continue;
+
+ pSpot = handle.Get();
+
+ if ( !pSpot )
+ continue;
+
+ if( g_pGameRules->IsSpawnPointValid( pSpot, this ) )
+ {
+ if ( pSpot->GetAbsOrigin() == Vector( 0, 0, 0 ) )
+ {
+ continue;
+ }
+
+ // if so, go to pSpot
+ iLastSpawnIndex = testIndex;
+ return pSpot;
+ }
+ }
+
+ DevMsg("CDODPlayer::SelectSpawnSpot: couldn't find valid spawn point.\n");
+
+ // If we get here, all spawn points are blocked.
+ // Try the 4 locations around each spawn point
+
+ Assert( pSpot );
+
+ Vector vecForward, vecRight, vecUp;
+
+ Vector mins = VEC_HULL_MIN;
+ Vector maxs = VEC_HULL_MAX;
+ float flHalfWidth = maxs.x;
+ float flTestDist = 3 * flHalfWidth;
+
+ for ( int i=0;i<iNumSpawns;i++ )
+ {
+ int testIndex = ( iLastSpawnIndex + i ) % iNumSpawns;
+
+ EHANDLE handle = pSpawnPoints->Element(testIndex);
+
+ if ( !handle )
+ continue;
+
+ pSpot = handle.Get();
+
+ if ( !pSpot )
+ continue;
+
+ // we know this spot is blocked, but check to the N, E, S, W of it.
+
+ // if we find a clear spot, create a new spawn point there, copied from pSpot
+
+ AngleVectors( pSpot->GetAbsAngles(), &vecForward, &vecRight, &vecUp );
+
+ for( int i=0;i<4;i++ )
+ {
+ Vector origin = pSpot->GetAbsOrigin();
+
+ switch( i )
+ {
+ case 0: origin += vecForward * flTestDist; break;
+ case 1: origin += vecRight * flTestDist; break;
+ case 2: origin -= vecForward * flTestDist; break;
+ case 3: origin -= vecRight * flTestDist; break;
+ }
+
+ Vector vTestMins = origin + mins;
+ Vector vTestMaxs = origin + maxs;
+
+ if ( UTIL_IsSpaceEmpty( this, vTestMins, vTestMaxs ) )
+ {
+ QAngle spotAngles = pSpot->GetAbsAngles();
+
+ // make a new spawnpoint so we don't have to do this a bunch of times
+ pSpot = CreateEntityByName( pSpot->GetClassname() );
+ pSpot->SetAbsOrigin( origin );
+ pSpot->SetAbsAngles( spotAngles );
+
+ // delete it in a while so we don't accumulate entities
+ pSpot->SetThink( &CBaseEntity::SUB_Remove );
+ pSpot->SetNextThink( gpGlobals->curtime + 0.5 );
+
+ Assert( pSpot );
+
+ iLastSpawnIndex = 0;
+
+ return pSpot;
+ }
+ }
+ }
+
+ // if we get here, we're really screwed. Spawning the person is going to stick them
+ // into someone or something, but we really tried hard, I swear.
+ Assert( !"We should never be here" );
+
+ return NULL;
+}
+
+
+CBaseEntity* CDODPlayer::EntSelectSpawnPoint()
+{
+ CBaseEntity *pSpot = NULL;
+
+ switch( GetTeamNumber() )
+ {
+ case TEAM_ALLIES:
+ {
+ CUtlVector<EHANDLE> *pSpawnList = DODGameRules()->GetSpawnPointListForTeam( TEAM_ALLIES );
+ pSpot = SelectSpawnSpot( pSpawnList, g_iLastAlliesSpawnIndex );
+ }
+ break;
+ case TEAM_AXIS:
+ {
+ CUtlVector<EHANDLE> *pSpawnList = DODGameRules()->GetSpawnPointListForTeam( TEAM_AXIS );
+ pSpot = SelectSpawnSpot( pSpawnList, g_iLastAxisSpawnIndex );
+ }
+ break;
+ case TEAM_SPECTATOR:
+ case TEAM_UNASSIGNED:
+ default:
+ {
+ pSpot = CBaseEntity::Instance( INDEXENT(0) );
+ }
+ break;
+ }
+
+ if ( !pSpot )
+ {
+ Warning( "PutClientInServer: no valid spawns on level\n" );
+ return CBaseEntity::Instance( INDEXENT(0) );
+ }
+
+ return pSpot;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Put the player in the specified team
+//-----------------------------------------------------------------------------
+void CDODPlayer::ChangeTeam( int iTeamNum )
+{
+ if ( !GetGlobalTeam( iTeamNum ) )
+ {
+ Warning( "CDODPlayer::ChangeTeam( %d ) - invalid team index.\n", iTeamNum );
+ return;
+ }
+
+ int iOldTeam = GetTeamNumber();
+
+ // if this is our current team, just abort
+ if ( iTeamNum == iOldTeam )
+ return;
+
+ m_bTeamChanged = true;
+
+ // do the team change:
+ BaseClass::ChangeTeam( iTeamNum );
+
+ // update client state
+ if ( iTeamNum == TEAM_UNASSIGNED )
+ {
+ State_Transition( STATE_OBSERVER_MODE );
+ }
+ else if ( iTeamNum == TEAM_SPECTATOR )
+ {
+ RemoveAllItems( true );
+
+ State_Transition( STATE_OBSERVER_MODE );
+
+ StatEvent_UploadStats();
+ m_StatProperty.SetClassAndTeamForThisLife( -1, TEAM_SPECTATOR );
+ }
+ else // active player
+ {
+ if ( !IsDead() )
+ {
+ // Kill player if switching teams while alive
+ CommitSuicide();
+
+ // add 1 to frags to balance out the 1 subtracted for killing yourself
+ // IncrementFragCount( 1 );
+ }
+
+ if( iOldTeam == TEAM_SPECTATOR )
+ SetMoveType( MOVETYPE_NONE );
+
+ // Put up the class selection menu.
+ State_Transition( STATE_PICKINGCLASS );
+ }
+
+ RemoveNemesisRelationships();
+}
+
+void CDODPlayer::DODRespawn( void )
+{
+ // if it's a forced respawn, accumulate the stats now
+ if ( IsAlive() )
+ {
+ StatEvent_UploadStats();
+ }
+
+ RemoveAllItems(true);
+
+ StopObserverMode();
+
+ State_Transition( STATE_ACTIVE );
+ Spawn();
+ m_nButtons = 0;
+ SetNextThink( TICK_NEVER_THINK );
+
+ OutputDamageGiven();
+ OutputDamageTaken();
+ ResetDamageCounters();
+}
+
+bool CDODPlayer::IsReadyToPlay( void )
+{
+ bool bResult = ( ( GetTeamNumber() == TEAM_ALLIES || GetTeamNumber() == TEAM_AXIS ) &&
+ m_Shared.DesiredPlayerClass() != PLAYERCLASS_UNDEFINED );
+
+ return bResult;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns whether or not we can switch to the given weapon.
+// Input : pWeapon -
+//-----------------------------------------------------------------------------
+bool CDODPlayer::Weapon_CanSwitchTo( CBaseCombatWeapon *pWeapon )
+{
+#if !defined( CLIENT_DLL )
+ IServerVehicle *pVehicle = GetVehicle();
+#else
+ IClientVehicle *pVehicle = GetVehicle();
+#endif
+
+ if (pVehicle && !UsingStandardWeaponsInVehicle())
+ return false;
+
+ if ( !pWeapon->CanDeploy() )
+ return false;
+
+ if ( GetActiveWeapon() )
+ {
+ if ( !GetActiveWeapon()->CanHolster() )
+ return false;
+ }
+
+ return true;
+}
+
+void CDODPlayer::SetScore( int score )
+{
+ m_iScore = score;
+}
+
+void CDODPlayer::AddScore( int num )
+{
+ int n = MAX( 0, num );
+ m_iScore += n;
+}
+
+void CDODPlayer::HandleSignals( void )
+{
+ int changed = m_signals.Update();
+ int state = m_signals.GetState();
+
+ if ( changed & SIGNAL_CAPTUREAREA )
+ {
+ if ( state & SIGNAL_CAPTUREAREA )
+ {
+ //do nothing
+ }
+ else
+ {
+ SetCapAreaIndex(-1);
+ SetCPIndex(-1);
+ m_iCapAreaIconIndex = -1;
+ }
+ }
+}
+
+void CDODPlayer::SetCapAreaIndex( int index )
+{
+ m_iCapAreaIndex = index;
+}
+
+int CDODPlayer::GetCapAreaIndex( void )
+{
+ return m_iCapAreaIndex;
+}
+
+void CDODPlayer::SetCPIndex( int index )
+{
+ m_Shared.SetCPIndex( index );
+}
+
+int CDODPlayer::OnTakeDamage_Alive( const CTakeDamageInfo &info )
+{
+ // set damage type sustained
+ m_bitsDamageType |= info.GetDamageType();
+
+ int iInitialHealth = m_iHealth;
+
+ if ( !CBaseCombatCharacter::OnTakeDamage_Alive( info ) )
+ return 0;
+
+ IGameEvent * event = gameeventmanager->CreateEvent( "player_hurt" );
+
+ if ( event )
+ {
+ event->SetInt("userid", GetUserID() );
+ event->SetInt("health", MAX(0, m_iHealth) );
+ event->SetInt("damage", info.GetDamage() );
+ event->SetInt("hitgroup", m_LastHitGroup );
+
+ CBaseEntity *attacker = info.GetAttacker();
+ const char *weaponName = "";
+ DODWeaponID weaponID = WEAPON_NONE;
+
+ if ( attacker->IsPlayer() )
+ {
+ CBasePlayer *player = ToBasePlayer( attacker );
+ event->SetInt("attacker", player->GetUserID() ); // hurt by other player
+
+ // ff damage
+ // no hint for bomb explosions, it was their own fault!
+ CDODPlayer *pDODPlayer = ToDODPlayer( player );
+ if( pDODPlayer->GetTeamNumber() == GetTeamNumber() && pDODPlayer != this && !FBitSet( info.GetDamageType(), DMG_BOMB ) )
+ {
+ pDODPlayer->Hints()->HintMessage( HINT_FRIEND_INJURED );
+ }
+
+ CBaseEntity *pInflictor = info.GetInflictor();
+ if ( pInflictor )
+ {
+ if ( pInflictor == pDODPlayer )
+ {
+ // If the inflictor is the killer, then it must be their current weapon doing the damage
+ if ( pDODPlayer->GetActiveWeapon() )
+ {
+ CWeaponDODBase *pWeapon = pDODPlayer->GetActiveDODWeapon();
+ weaponName = pWeapon->GetClassname();
+
+ if ( info.GetDamageCustom() & MELEE_DMG_SECONDARYATTACK )
+ weaponID = pWeapon->GetAltWeaponID();
+ else
+ weaponID = pWeapon->GetStatsWeaponID();
+ }
+ }
+ else
+ {
+ weaponName = STRING( pInflictor->m_iClassname ); // it's just that easy
+ }
+ }
+ }
+ else
+ {
+ event->SetInt("attacker", 0 ); // hurt by "world"
+ }
+
+ if ( strncmp( weaponName, "weapon_", 7 ) == 0 )
+ {
+ weaponName += 7;
+ }
+ else if ( strncmp( weaponName, "rocket_", 7 ) == 0 )
+ {
+ weaponName += 7;
+
+ CDODBaseRocket *pRocket = dynamic_cast< CDODBaseRocket *>( info.GetInflictor() );
+ if ( pRocket )
+ {
+ weaponID = pRocket->GetEmitterWeaponID();
+ }
+ }
+ else if ( strncmp( weaponName, "grenade_", 8 ) == 0 )
+ {
+ weaponName += 8;
+
+ CDODBaseGrenade *pGren = dynamic_cast< CDODBaseGrenade *>( info.GetInflictor() );
+ if ( pGren )
+ {
+ weaponID = pGren->GetEmitterWeaponID();
+ }
+ }
+ else if ( Q_stricmp( weaponName, "dod_bomb_target" ) == 0 )
+ {
+ weaponID = WEAPON_NONE;
+ }
+
+ event->SetString( "weapon", weaponName );
+ event->SetInt( "priority", 5 );
+
+ gameeventmanager->FireEvent( event );
+
+#ifndef CLIENT_DLL
+ IGameEvent * stats_event = gameeventmanager->CreateEvent( "dod_stats_player_damage" );
+ if ( stats_event && attacker->IsPlayer() && weaponID != WEAPON_NONE )
+ {
+ CBasePlayer *pAttacker = ToBasePlayer( attacker );
+
+ int iDamage = ( info.GetDamage() + 0.5f ); // round to nearest integer
+
+ stats_event->SetInt( "attacker", pAttacker->GetUserID() );
+ stats_event->SetInt( "victim", GetUserID() );
+ stats_event->SetInt( "weapon", weaponID );
+ stats_event->SetInt( "damage", iDamage );
+
+ // damage_given is the amount of damage applied, not to exceed how much life we have
+ stats_event->SetInt( "damage_given", MIN( iDamage, iInitialHealth ) );
+
+ CBaseEntity *pInflictor = info.GetInflictor();
+ float flDist = ( pInflictor->GetAbsOrigin() - GetAbsOrigin() ).Length();
+ stats_event->SetFloat( "distance", flDist );
+
+ stats_event->SetInt("hitgroup", m_LastHitGroup );
+
+ gameeventmanager->FireEvent( stats_event );
+ }
+#endif //CLIENT_DLL
+ }
+
+ return 1;
+}
+
+ConVar dod_explosionforcescale( "dod_explosionforcescale", "1.0", FCVAR_CHEAT );
+ConVar dod_bulletforcescale( "dod_bulletforcescale", "1.0", FCVAR_CHEAT );
+
+int CDODPlayer::OnTakeDamage( const CTakeDamageInfo &inputInfo )
+{
+ CTakeDamageInfo info = inputInfo;
+
+ CBaseEntity *pInflictor = info.GetInflictor();
+
+ if ( !pInflictor )
+ return 0;
+
+ if ( GetMoveType() == MOVETYPE_NOCLIP )
+ return 0;
+
+ // if the player's team does not match the inflictor's team
+ // the player has changed teams between when they started the attack
+ if( pInflictor->GetTeamNumber() != TEAM_UNASSIGNED &&
+ info.GetAttacker() != NULL &&
+ pInflictor->GetTeamNumber() != info.GetAttacker()->GetTeamNumber() )
+ {
+ info.SetDamage( 0 );
+ info.SetDamageType( 0 );
+ }
+
+ bool bFriendlyFire = DODGameRules()->IsFriendlyFireOn();
+
+ if ( bFriendlyFire ||
+ info.GetAttacker()->GetTeamNumber() != GetTeamNumber() ||
+ pInflictor == this ||
+ info.GetAttacker() == this ||
+ info.GetDamageType() & DMG_BOMB )
+ {
+ // Do special stun damage effect
+ if ( info.GetDamageType() & DMG_STUN )
+ {
+ OnDamageByStun( info );
+ return 0;
+ }
+
+ CDODPlayer *pPlayer = ToDODPlayer( info.GetAttacker() );
+
+ // keep track of amount of damage last sustained
+ m_lastDamageAmount = info.GetDamage();
+ m_LastDamageType = info.GetDamageType();
+
+ if( !FBitSet( info.GetDamageType(), DMG_FALL ) )
+ Pain();
+
+ float flForceScale = 1.0;
+
+ // Do special explosion damage effect
+ if ( info.GetDamageType() & DMG_BLAST )
+ {
+ OnDamagedByExplosion( info );
+ flForceScale = dod_explosionforcescale.GetFloat();
+ }
+
+ if( info.GetDamageType() & DMG_BULLET )
+ {
+ flForceScale = dod_bulletforcescale.GetFloat();
+ }
+
+ // round up!
+ int iDamage = (int)( info.GetDamage() + 0.5 );
+
+ if ( pPlayer )
+ {
+ // Record for the shooter
+ pPlayer->RecordDamageGiven( this, iDamage );
+
+ // And for the victim
+ RecordDamageTaken( pPlayer, iDamage );
+ }
+ else
+ {
+ RecordWorldDamageTaken( iDamage );
+ }
+
+ m_vecTotalBulletForce += info.GetDamageForce() * flForceScale;
+
+ CSingleUserRecipientFilter user( this );
+ user.MakeReliable();
+ UserMessageBegin( user, "Damage" );
+ WRITE_BYTE( (int)info.GetDamage() );
+ WRITE_LONG( info.GetDamageType() );
+ WRITE_VEC3COORD( info.GetInflictor()->WorldSpaceCenter() );
+ MessageEnd();
+
+ gamestats->Event_PlayerDamage( this, info );
+
+ return CBaseCombatCharacter::OnTakeDamage( info );
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+void CDODPlayer::Pain( void )
+{
+ if ( m_LastDamageType & DMG_CLUB)
+ {
+ EmitSound( "Player.MajorPain" );
+ }
+ else if ( m_LastDamageType & DMG_BLAST )
+ {
+ EmitSound( "Player.MajorPain" );
+ }
+ else
+ {
+ EmitSound( "Player.MinorPain" );
+ }
+}
+
+void CDODPlayer::DeathSound( const CTakeDamageInfo &info )
+{
+ if ( m_LastDamageType & DMG_CLUB )
+ {
+ EmitSound( "Player.MegaPain" );
+ }
+ else if ( m_LastDamageType & DMG_BLAST )
+ {
+ EmitSound( "Player.MegaPain" );
+ }
+ else if ( m_LastHitGroup == HITGROUP_HEAD )
+ {
+ EmitSound( "Player.DeathHeadShot" );
+ }
+ else
+ {
+ EmitSound( "Player.MinorPain" );
+ }
+}
+
+void CDODPlayer::OnDamagedByExplosion( const CTakeDamageInfo &info )
+{
+ if ( info.GetDamage() >= 30.0f )
+ {
+ SetContextThink( &CDODPlayer::DeafenThink, gpGlobals->curtime + 0.3, DOD_DEAFEN_CONTEXT );
+
+ // The blast will naturally blow the temp ent helmet away
+ PopHelmet( info.GetDamagePosition(), info.GetDamageForce() );
+ }
+}
+
+ConVar dod_stun_min_pitch( "dod_stun_min_pitch", "30", FCVAR_CHEAT );
+ConVar dod_stun_max_pitch( "dod_stun_max_pitch", "50", FCVAR_CHEAT );
+ConVar dod_stun_min_yaw( "dod_stun_min_yaw", "120", FCVAR_CHEAT );
+ConVar dod_stun_max_yaw( "dod_stun_max_yaw", "150", FCVAR_CHEAT );
+ConVar dod_stun_min_roll( "dod_stun_min_roll", "15", FCVAR_CHEAT );
+ConVar dod_stun_max_roll( "dod_stun_max_roll", "30", FCVAR_CHEAT );
+
+void CDODPlayer::OnDamageByStun( const CTakeDamageInfo &info )
+{
+ DevMsg( 2, "took %.1f stun damage\n", info.GetDamage() );
+
+ float flPercent = ( info.GetDamage() / 100.0f );
+
+ m_flStunDuration = 0.0; // mark it as dirty so it transmits, incase we get the same value twice
+ m_flStunDuration = 4.0f * flPercent;
+ m_flStunMaxAlpha = 255;
+
+ float flPitch = flPercent * RandomFloat( dod_stun_min_pitch.GetFloat(), dod_stun_max_pitch.GetFloat() ) *
+ ( (RandomInt( 0, 1 )) ? 1 : -1 );
+
+ float flYaw = flPercent * RandomFloat( dod_stun_min_yaw.GetFloat(), dod_stun_max_yaw.GetFloat() )
+ * ( (RandomInt( 0, 1 )) ? 1 : -1 );
+
+ float flRoll = flPercent * RandomFloat( dod_stun_min_roll.GetFloat(), dod_stun_max_roll.GetFloat() )
+ * ( (RandomInt( 0, 1 )) ? 1 : -1 );
+
+ DevMsg( 2, "punch: pitch %.1f yaw %.1f roll %.1f\n", flPitch, flYaw, flRoll );
+
+ SetPunchAngle( QAngle( flPitch, flYaw, flRoll ) );
+}
+
+void CDODPlayer::DeafenThink( void )
+{
+ int effect = random->RandomInt( 32, 34 );
+
+ CSingleUserRecipientFilter user( this );
+ enginesound->SetPlayerDSP( user, effect, false );
+}
+
+//=======================================================
+// Remember this amount of damage that we dealt for stats
+//=======================================================
+void CDODPlayer::RecordDamageGiven( CDODPlayer *pVictim, int iDamageGiven )
+{
+ if ( iDamageGiven <= 0 )
+ return;
+
+ FOR_EACH_LL( m_DamageGivenList, i )
+ {
+ if ( pVictim->GetLifeID() == m_DamageGivenList[i]->GetLifeID() )
+ {
+ m_DamageGivenList[i]->AddDamage( iDamageGiven );
+ return;
+ }
+ }
+
+ CDamageRecord *record = new CDamageRecord( pVictim->GetPlayerName(), pVictim->GetLifeID(), iDamageGiven );
+ int tail = m_DamageGivenList.AddToTail();
+ m_DamageGivenList[tail] = record;
+}
+
+//=======================================================
+// Remember this amount of damage that we took for stats
+//=======================================================
+void CDODPlayer::RecordDamageTaken( CDODPlayer *pAttacker, int iDamageTaken )
+{
+ if ( iDamageTaken <= 0 )
+ return;
+
+ FOR_EACH_LL( m_DamageTakenList, i )
+ {
+ if ( pAttacker->GetLifeID() == m_DamageTakenList[i]->GetLifeID() )
+ {
+ m_DamageTakenList[i]->AddDamage( iDamageTaken );
+ return;
+ }
+ }
+
+ CDamageRecord *record = new CDamageRecord( pAttacker->GetPlayerName(), pAttacker->GetLifeID(), iDamageTaken );
+ int tail = m_DamageTakenList.AddToTail();
+ m_DamageTakenList[tail] = record;
+}
+
+void CDODPlayer::RecordWorldDamageTaken( int iDamageTaken )
+{
+ if ( iDamageTaken <= 0 )
+ return;
+
+ FOR_EACH_LL( m_DamageTakenList, i )
+ {
+ if ( m_DamageTakenList[i]->GetLifeID() == 0 )
+ {
+ m_DamageTakenList[i]->AddDamage( iDamageTaken );
+ return;
+ }
+ }
+
+ CDamageRecord *record = new CDamageRecord( "world", 0, iDamageTaken );
+ int tail = m_DamageTakenList.AddToTail();
+ m_DamageTakenList[tail] = record;
+}
+
+//=======================================================
+// Reset our damage given and taken counters
+//=======================================================
+void CDODPlayer::ResetDamageCounters()
+{
+ m_DamageGivenList.PurgeAndDeleteElements();
+ m_DamageTakenList.PurgeAndDeleteElements();
+}
+
+//=======================================================
+// Output the damage that we dealt to other players
+//=======================================================
+void CDODPlayer::OutputDamageTaken( void )
+{
+ bool bPrintHeader = true;
+ CDamageRecord *pRecord;
+ char buf[64];
+ int msg_dest = HUD_PRINTCONSOLE;
+
+ FOR_EACH_LL( m_DamageTakenList, i )
+ {
+ if( bPrintHeader )
+ {
+ ClientPrint( this, msg_dest, "Player: %s1 - Damage Taken\n", GetPlayerName() );
+ ClientPrint( this, msg_dest, "-------------------------\n" );
+ bPrintHeader = false;
+ }
+ pRecord = m_DamageTakenList[i];
+
+ if( pRecord )
+ {
+ Q_snprintf( buf, sizeof(buf), "( life ID %i ) - %d in %d %s",
+ pRecord->GetLifeID(),
+ pRecord->GetDamage(),
+ pRecord->GetNumHits(),
+ ( pRecord->GetNumHits() == 1 ) ? "hit" : "hits" );
+
+ ClientPrint( this, msg_dest, "Damage Taken from \"%s1\" - %s2\n", pRecord->GetPlayerName(), buf );
+ }
+ }
+}
+
+//=======================================================
+// Output the damage that we took from other players
+//=======================================================
+void CDODPlayer::OutputDamageGiven( void )
+{
+ bool bPrintHeader = true;
+ CDamageRecord *pRecord;
+ char buf[64];
+ int msg_dest = HUD_PRINTCONSOLE;
+
+ FOR_EACH_LL( m_DamageGivenList, i )
+ {
+ if( bPrintHeader )
+ {
+ ClientPrint( this, msg_dest, "Player: %s1 - Damage Given\n", GetPlayerName() );
+ ClientPrint( this, msg_dest, "-------------------------\n" );
+ bPrintHeader = false;
+ }
+
+ pRecord = m_DamageGivenList[i];
+
+ if( pRecord )
+ {
+ Q_snprintf( buf, sizeof(buf), "( life ID %i ) - %d in %d %s",
+ pRecord->GetLifeID(),
+ pRecord->GetDamage(),
+ pRecord->GetNumHits(),
+ ( pRecord->GetNumHits() == 1 ) ? "hit" : "hits" );
+
+ ClientPrint( this, msg_dest, "Damage Given to \"%s1\" - %s2\n", pRecord->GetPlayerName(), buf );
+ }
+ }
+}
+
+// Sets the player's speed - returns true if successful
+bool CDODPlayer::SetSpeed( int speed )
+{
+ DevMsg( 2, "Changing max speed to %d\n", speed );
+
+ SetMaxSpeed( (float)speed );
+
+ return true;
+}
+
+void CDODPlayer::CreateViewModel( int index /*=0*/ )
+{
+ Assert( index >= 0 && index < MAX_VIEWMODELS );
+
+ if ( GetViewModel( index ) )
+ return;
+
+ CDODViewModel *vm = ( CDODViewModel * )CreateEntityByName( "dod_viewmodel" );
+ if ( vm )
+ {
+ vm->SetAbsOrigin( GetAbsOrigin() );
+ vm->SetOwner( this );
+ vm->SetIndex( index );
+ DispatchSpawn( vm );
+ vm->FollowEntity( this, false );
+ m_hViewModel.Set( index, vm );
+ }
+}
+
+void CDODPlayer::ResetScores( void )
+{
+ ResetFragCount();
+ ResetDeathCount();
+ SetScore(0);
+
+ Q_memset( iNumKilledByUnanswered, 0, sizeof( iNumKilledByUnanswered ) );
+}
+
+bool CDODPlayer::SetObserverMode(int mode)
+{
+ // DoD forces mp_forcecamera to be team only
+ mp_forcecamera.SetValue( OBS_ALLOW_TEAM );
+
+ if ( mode < OBS_MODE_NONE || mode > OBS_MODE_ROAMING )
+ return false;
+
+ // Skip over OBS_MODE_ROAMING for dead players
+ if( GetTeamNumber() > TEAM_SPECTATOR )
+ {
+ if( mode == OBS_MODE_ROAMING )
+ mode = OBS_MODE_IN_EYE;
+ }
+
+ 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;
+
+ case OBS_MODE_ROAMING :
+ SetFOV( this, 0 ); // Reset FOV
+ SetObserverTarget( m_hObserverTarget );
+ SetViewOffset( vec3_origin );
+ SetMoveType( MOVETYPE_OBSERVER );
+ break;
+
+ }
+
+ CheckObserverSettings();
+
+ return true;
+}
+
+extern ConVar sv_alltalk;
+bool CDODPlayer::CanHearChatFrom( CBasePlayer *pPlayer )
+{
+ // can always hear the console
+ if ( !pPlayer )
+ return true;
+
+ // teammates can always hear each other if alltalk is on
+ if ( sv_alltalk.GetBool() == true )
+ return true;
+
+ // can hear dead people in bonus round and at round end
+ if ( DODGameRules()->IsInBonusRound() || DODGameRules()->State_Get() == STATE_GAME_OVER )
+ return true;
+
+ // alive players cannot hear dead players
+ if ( IsAlive() && !pPlayer->IsAlive() && !( pPlayer->GetTeamNumber() == TEAM_SPECTATOR ) )
+ {
+ return false;
+ }
+
+ return true;
+}
+
+void CDODPlayer::ResetBleeding( void )
+{
+ SetBandager( NULL );
+}
+
+void CDODPlayer::Bandage( void )
+{
+ ResetBleeding();
+
+ TakeHealth( mp_bandage_heal_amount.GetFloat(), 0 );
+
+ // change helmet to bandages
+}
+
+void CDODPlayer::SetBandager( CDODPlayer *pPlayer )
+{
+ m_hBandager = pPlayer;
+}
+
+bool CDODPlayer::IsBeingBandaged( void )
+{
+ return ( m_hBandager.Get() != NULL );
+}
+
+void CDODPlayer::CommitSuicide( bool bExplode /*= false*/, bool bForce /*= false*/ )
+{
+ CommitSuicide( vec3_origin, false, bForce );
+}
+
+void CDODPlayer::CommitSuicide( const Vector &vecForce, bool bExplode /*= false*/, bool bForce /*= false*/ )
+{
+ // If they're suiciding in the spawn, its most likely they're changing class
+ // ( or possible suiciding to avoid being killed in the endround )
+ // On linux the suicide option sometimes arrives before the joinclass so just
+ // reject it here
+ if ( ShouldInstantRespawn() )
+ return;
+
+ if( !IsAlive() )
+ return;
+
+ // prevent suiciding too often
+ if ( m_fNextSuicideTime > gpGlobals->curtime )
+ return;
+
+ // don't let them suicide for 5 seconds after suiciding
+ m_fNextSuicideTime = gpGlobals->curtime + 5;
+
+ // have the player kill themselves
+ CTakeDamageInfo info( this, this, 0, DMG_NEVERGIB );
+ Event_Killed( info );
+ Event_Dying( info );
+}
+
+bool CDODPlayer::StartReplayMode( float fDelay, float fDuration, int iEntity )
+{
+ if ( !BaseClass::StartReplayMode( fDelay, fDuration, iEntity ) )
+ return false;
+
+ CSingleUserRecipientFilter filter( this );
+ filter.MakeReliable();
+
+ UserMessageBegin( filter, "KillCam" );
+ WRITE_BYTE( OBS_MODE_IN_EYE );
+
+ if ( m_hObserverTarget.Get() )
+ {
+ WRITE_BYTE( m_hObserverTarget.Get()->entindex() ); // first target
+ WRITE_BYTE( entindex() ); //second target
+ }
+ else
+ {
+ WRITE_BYTE( entindex() ); // first target
+ WRITE_BYTE( 0 ); //second target
+ }
+ MessageEnd();
+
+ ClientPrint( this, HUD_PRINTCENTER, "Kill Cam Replay" );
+
+ return true;
+}
+
+void CDODPlayer::StopReplayMode()
+{
+ BaseClass::StopReplayMode();
+
+ CSingleUserRecipientFilter filter( this );
+ filter.MakeReliable();
+
+ UserMessageBegin( filter, "KillCam" );
+ WRITE_BYTE( OBS_MODE_NONE );
+ WRITE_BYTE( 0 );
+ WRITE_BYTE( 0 );
+ MessageEnd();
+}
+
+void CDODPlayer::PickUpWeapon( CWeaponDODBase *pWeapon )
+{
+ // if we have a primary weapon and we are allowed to drop it, drop it and
+ // pick up the one we +used
+
+ CWeaponDODBase *pCurrentPrimaryWpn = (CWeaponDODBase *)Weapon_GetSlot( WPN_SLOT_PRIMARY );
+
+ // drop primary if we can
+ if( pCurrentPrimaryWpn )
+ {
+ if ( pCurrentPrimaryWpn->CanDrop() == false )
+ {
+ return;
+ }
+
+ DODWeaponDrop( pCurrentPrimaryWpn, true );
+ }
+
+ // pick up the new one
+ if ( BumpWeapon( pWeapon ) )
+ {
+ pWeapon->OnPickedUp( this );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Override setup bones so that is uses the render angles from
+// the DOD animation state to setup the hitboxes.
+//-----------------------------------------------------------------------------
+void CDODPlayer::SetupBones( matrix3x4_t *pBoneToWorld, int boneMask )
+{
+ VPROF_BUDGET( "CBaseAnimating::SetupBones", VPROF_BUDGETGROUP_SERVER_ANIM );
+
+ // Set the mdl cache semaphore.
+ MDLCACHE_CRITICAL_SECTION();
+
+ // Get the studio header.
+ Assert( GetModelPtr() );
+ CStudioHdr *pStudioHdr = GetModelPtr( );
+
+ Vector pos[MAXSTUDIOBONES];
+ Quaternion q[MAXSTUDIOBONES];
+
+ // Adjust hit boxes based on IK driven offset.
+ Vector adjOrigin = GetAbsOrigin() + Vector( 0, 0, m_flEstIkOffset );
+
+ // FIXME: pass this into Studio_BuildMatrices to skip transforms
+ CBoneBitList boneComputed;
+ if ( m_pIk )
+ {
+ m_iIKCounter++;
+ m_pIk->Init( pStudioHdr, GetAbsAngles(), adjOrigin, gpGlobals->curtime, m_iIKCounter, boneMask );
+ GetSkeleton( pStudioHdr, pos, q, boneMask );
+
+ m_pIk->UpdateTargets( pos, q, pBoneToWorld, boneComputed );
+ CalculateIKLocks( gpGlobals->curtime );
+ m_pIk->SolveDependencies( pos, q, pBoneToWorld, boneComputed );
+ }
+ else
+ {
+ GetSkeleton( pStudioHdr, pos, q, boneMask );
+ }
+
+ CBaseAnimating *pParent = dynamic_cast< CBaseAnimating* >( GetMoveParent() );
+ if ( pParent )
+ {
+ // We're doing bone merging, so do special stuff here.
+ CBoneCache *pParentCache = pParent->GetBoneCache();
+ if ( pParentCache )
+ {
+ BuildMatricesWithBoneMerge(
+ pStudioHdr,
+ m_PlayerAnimState->GetRenderAngles(),
+ adjOrigin,
+ pos,
+ q,
+ pBoneToWorld,
+ pParent,
+ pParentCache );
+
+ return;
+ }
+ }
+
+ Studio_BuildMatrices(
+ pStudioHdr,
+ m_PlayerAnimState->GetRenderAngles(),
+ adjOrigin,
+ pos,
+ q,
+ -1,
+ GetModelScale(), // Scaling
+ pBoneToWorld,
+ boneMask );
+}
+
+
+extern ConVar sv_debug_player_use;
+extern float IntervalDistance( float x, float x0, float x1 );
+
+//-----------------------------------------------------------------------------
+// Purpose: Find which ents are higher priority for receiving our +use
+//-----------------------------------------------------------------------------
+int CDODPlayer::GetPriorityForPickUpEnt( CBaseEntity *pEnt )
+{
+ CDODBombTarget *pBombTarget = dynamic_cast< CDODBombTarget *>( pEnt );
+
+ if ( pBombTarget )
+ {
+ // its a bomb target for planting or defusing, most important
+ return 20;
+ }
+
+ CDODBaseGrenade *pGren = dynamic_cast< CDODBaseGrenade *>( pEnt );
+
+ if ( pGren )
+ {
+ // its a grenade, high priority
+ return 10;
+ }
+
+ CWeaponDODBase *pWeapon = dynamic_cast< CWeaponDODBase *>( pEnt );
+
+ if ( pWeapon )
+ {
+ // weapons are medium priority
+ return 5;
+ }
+
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: like regular FindUseEntity, but picks up grenades before other things
+//-----------------------------------------------------------------------------
+CBaseEntity *CDODPlayer::FindUseEntity()
+{
+ Vector forward, up;
+ EyeVectors( &forward, NULL, &up );
+
+ trace_t tr;
+ // Search for objects in a sphere (tests for entities that are not solid, yet still useable)
+ Vector searchCenter = EyePosition();
+
+ // NOTE: Some debris objects are useable too, so hit those as well
+ // A button, etc. can be made out of clip brushes, make sure it's +useable via a traceline, too.
+ int useableContents = MASK_SOLID | CONTENTS_DEBRIS | CONTENTS_PLAYERCLIP;
+
+ UTIL_TraceLine( searchCenter, searchCenter + forward * 1024, useableContents, this, COLLISION_GROUP_NONE, &tr );
+
+ // try the hit entity if there is one, or the ground entity if there isn't.
+ CBaseEntity *pNearest = NULL;
+ CBaseEntity *pObject = tr.m_pEnt;
+
+ // UNDONE: Might be faster to just fold this range into the sphere query
+ int count = 0;
+ const int NUM_TANGENTS = 7;
+ while ( !IsUseableEntity(pObject, 0) && count < NUM_TANGENTS)
+ {
+ // trace a box at successive angles down
+ // 45 deg, 30 deg, 20 deg, 15 deg, 10 deg, -10, -15
+ const float tangents[NUM_TANGENTS] = { 1, 0.57735026919f, 0.3639702342f, 0.267949192431f, 0.1763269807f, -0.1763269807f, -0.267949192431f };
+ Vector down = forward - tangents[count]*up;
+ VectorNormalize(down);
+ UTIL_TraceHull( searchCenter, searchCenter + down * 72, -Vector(16,16,16), Vector(16,16,16), useableContents, this, COLLISION_GROUP_NONE, &tr );
+ pObject = tr.m_pEnt;
+ count++;
+ }
+ float nearestDot = CONE_90_DEGREES;
+ if ( IsUseableEntity(pObject, 0) )
+ {
+ Vector delta = tr.endpos - tr.startpos;
+ float centerZ = CollisionProp()->WorldSpaceCenter().z;
+ delta.z = IntervalDistance( tr.endpos.z, centerZ + CollisionProp()->OBBMins().z, centerZ + CollisionProp()->OBBMaxs().z );
+ float dist = delta.Length();
+ if ( dist < PLAYER_USE_RADIUS )
+ {
+ if ( sv_debug_player_use.GetBool() )
+ {
+ NDebugOverlay::Line( searchCenter, tr.endpos, 0, 255, 0, true, 30 );
+ NDebugOverlay::Cross3D( tr.endpos, 16, 0, 255, 0, true, 30 );
+ }
+
+ pNearest = pObject;
+ nearestDot = 0;
+ }
+ }
+
+ // check ground entity first
+ // if you've got a useable ground entity, then shrink the cone of this search to 45 degrees
+ // otherwise, search out in a 90 degree cone (hemisphere)
+ if ( GetGroundEntity() && IsUseableEntity(GetGroundEntity(), FCAP_USE_ONGROUND) )
+ {
+ pNearest = GetGroundEntity();
+ nearestDot = CONE_45_DEGREES;
+ }
+
+ int iHighestPriority = GetPriorityForPickUpEnt( pObject );
+
+ for ( CEntitySphereQuery sphere( searchCenter, PLAYER_USE_RADIUS ); ( pObject = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() )
+ {
+ if ( !pObject )
+ continue;
+
+ if ( !IsUseableEntity( pObject, FCAP_USE_IN_RADIUS ) )
+ continue;
+
+ // see if it's more roughly in front of the player than previous guess
+ Vector point;
+ pObject->CollisionProp()->CalcNearestPoint( searchCenter, &point );
+
+ Vector dir = point - searchCenter;
+ VectorNormalize(dir);
+ float dot = DotProduct( dir, forward );
+
+ // Need to be looking at the object more or less
+ if ( dot < 0.8 )
+ continue;
+
+ //NEW FOR DOD
+ // if this entity is higher priority than previous ent, use this one
+
+ int iPriority = GetPriorityForPickUpEnt( pObject );
+
+ // if higher priority, always use
+ // within the same priority, use the closer one
+ if ( ( iPriority > iHighestPriority ) || ( iPriority == iHighestPriority && dot > nearestDot ) )
+ {
+ // Since this has purely been a radius search to this point, we now
+ // make sure the object isn't behind glass or a grate.
+ trace_t trCheckOccluded;
+ UTIL_TraceLine( searchCenter, point, useableContents, this, COLLISION_GROUP_NONE, &trCheckOccluded );
+
+ if ( trCheckOccluded.fraction == 1.0 || trCheckOccluded.m_pEnt == pObject )
+ {
+ pNearest = pObject;
+ nearestDot = dot;
+ iHighestPriority = iPriority;
+ }
+ }
+ }
+
+ if ( sv_debug_player_use.GetBool() )
+ {
+ if ( !pNearest )
+ {
+ NDebugOverlay::Line( searchCenter, tr.endpos, 255, 0, 0, true, 30 );
+ NDebugOverlay::Cross3D( tr.endpos, 16, 255, 0, 0, true, 30 );
+ }
+ else
+ {
+ NDebugOverlay::Box( pNearest->WorldSpaceCenter(), Vector(-8, -8, -8), Vector(8, 8, 8), 0, 255, 0, true, 30 );
+ }
+ }
+
+ // Special check for bomb targets, whose radius for +use is larger than other entities
+ if ( pNearest == NULL )
+ {
+ CBaseEntity *pEnt = NULL;
+ float flBestDist = FLT_MAX;
+
+ // If we didn't find anything, check to see if the bomb target is close enough to use.
+ // This is done separately since there might be something blocking our LOS to it
+ // but we might want to use it anyway if it's close enough.
+
+ while( ( pEnt = gEntList.FindEntityByClassname( pEnt, "dod_bomb_target" ) ) != NULL )
+ {
+ CDODBombTarget *pTarget = static_cast<CDODBombTarget *>( pEnt );
+
+ if ( !pTarget->CanPlantHere( this ) )
+ continue;
+
+ Vector pos = WorldSpaceCenter();
+
+ float flDist = ( pos - pTarget->GetAbsOrigin() ).Length();
+
+ Vector toBomb = pTarget->GetAbsOrigin() - EyePosition();
+ toBomb.NormalizeInPlace();
+
+ if ( DotProduct( forward, toBomb ) < 0.8 )
+ {
+ continue;
+ }
+
+ // if we are looking directly at a bomb target and it is within our radius, that automatically wins
+ if ( flDist < flBestDist && flDist < DOD_BOMB_PLANT_RADIUS )
+ {
+ flBestDist = flDist;
+ pNearest = pTarget;
+ }
+ }
+ }
+
+ return pNearest;
+}
+
+void CDODPlayer::PrintLifetimeStats( void )
+{
+ unsigned int i;
+
+ Msg( "\nWeapon Stats\n================\n" );
+
+ Msg( " (shots) (hits) (damage) (avg.dist) (kills) (hits taken) (dmg taken) (times killed) (accuracy)\n" );
+ // ( "* thompson 4 3 110 89.8 1 1 0 0 75.0
+ //Msg( "*%9s %2i %2i %3i %5.1f %2i %2i %3i %2i %3.1f\n",
+
+ for ( i=0;i<WEAPON_MAX;i++ )
+ {
+ if ( m_WeaponStats[i].m_iNumShotsTaken > 0 )
+ {
+ Msg( "* %10s (%2i) %6i %6i %8i %9.1f %7i %9i %9i %9i %9.1f\n",
+ WeaponIDToAlias( i ), i,
+ m_WeaponStats[i].m_iNumShotsTaken,
+ m_WeaponStats[i].m_iNumShotsHit,
+ m_WeaponStats[i].m_iTotalDamageGiven,
+ m_WeaponStats[i].m_flAverageHitDistance,
+ m_WeaponStats[i].m_iNumKills,
+ m_WeaponStats[i].m_iNumHitsTaken,
+ m_WeaponStats[i].m_iTotalDamageTaken,
+ m_WeaponStats[i].m_iTimesKilled,
+ 100.0 * ( (float)m_WeaponStats[i].m_iNumShotsHit / (float)m_WeaponStats[i].m_iNumShotsTaken ) );
+ }
+ }
+
+ Msg( "\nPlayer Stats\n================\n" );
+
+ for( i=0;i<m_KilledPlayers.Count();i++ )
+ {
+ CBasePlayer *pPlayer = UTIL_PlayerByUserId( m_KilledPlayers[i].m_iUserID );
+
+ const char *pName = pPlayer ? pPlayer->GetPlayerName() : m_KilledByPlayers[i].m_szPlayerName;
+
+ Msg( "* Killed Player '%s' %i times ( %i damage )\n",
+ pName,
+ m_KilledPlayers[i].m_iKills,
+ m_KilledPlayers[i].m_iTotalDamage );
+ }
+
+ Msg( "\n" );
+
+ for( i=0;i<m_KilledByPlayers.Count();i++ )
+ {
+ CBasePlayer *pPlayer = UTIL_PlayerByUserId( m_KilledByPlayers[i].m_iUserID );
+
+ Assert( pPlayer && "If this fails, the player has disconnected. Fix this!" );
+
+ const char *pName = pPlayer ? pPlayer->GetPlayerName() : m_KilledByPlayers[i].m_szPlayerName;
+
+ Msg( "* Player '%s' killed you %i times ( %i damage )\n",
+ pName,
+ m_KilledByPlayers[i].m_iKills,
+ m_KilledByPlayers[i].m_iTotalDamage );
+ }
+
+ Msg( "\n" );
+
+ Msg( "* Areas Captured: %i\n", m_iNumAreaCaptures );
+ Msg( "* Areas Defended: %i\n", m_iNumAreaDefenses );
+ Msg( "* Num Bonus Round Kills: %i\n", m_iNumBonusRoundKills );
+
+ Msg( "\n" );
+
+ // time spent as each class
+ Msg( "Time spent as each class:\n" );
+
+ // Make sure to tally the latest time
+ int playerclass = m_Shared.DesiredPlayerClass();
+
+ //evil, re-map -2 to 6 so it goes on the end of the array
+ if ( playerclass == PLAYERCLASS_RANDOM )
+ playerclass = 6;
+
+ /*
+ m_flTimePlayedPerClass[playerclass] += ( gpGlobals->curtime - m_flLastClassChangeTime );
+ m_flLastClassChangeTime = gpGlobals->curtime;
+
+ Msg( "* Rifleman: %.0f\n", m_flTimePlayedPerClass[0] );
+ Msg( "* Assault: %.0f\n", m_flTimePlayedPerClass[1] );
+ Msg( "* Support: %.0f\n", m_flTimePlayedPerClass[2] );
+ Msg( "* Sniper: %.0f\n", m_flTimePlayedPerClass[3] );
+ Msg( "* MG: %.0f\n", m_flTimePlayedPerClass[4] );
+ Msg( "* Rocket: %.0f\n", m_flTimePlayedPerClass[5] );
+ Msg( "* Random: %.0f\n", m_flTimePlayedPerClass[6] );
+
+ Msg( "\n" );
+ Msg( "Total time connected %.0f\n", ( gpGlobals->curtime - m_flConnectTime ) );
+ */
+}
+
+void CDODPlayer::Stats_WeaponFired( int weaponID )
+{
+ // increment shots taken for this weapon
+ m_WeaponStats[weaponID].m_iNumShotsTaken++;
+
+ DODGameRules()->Stats_WeaponFired( weaponID );
+
+ StatEvent_WeaponFired( (DODWeaponID)weaponID );
+}
+
+void CDODPlayer::Stats_WeaponHit( CDODPlayer *pVictim, int weaponID, int iDamage, int iDamageGiven, int hitgroup, float flHitDistance )
+{
+ // distance
+ float flTotalHitDistance = m_WeaponStats[weaponID].m_flAverageHitDistance * m_WeaponStats[weaponID].m_iNumShotsHit;
+ flTotalHitDistance += flHitDistance;
+ m_WeaponStats[weaponID].m_flAverageHitDistance = flTotalHitDistance / ( m_WeaponStats[weaponID].m_iNumShotsHit + 1 );
+
+ // damage
+ m_WeaponStats[weaponID].m_iTotalDamageGiven += iDamageGiven;
+
+ // hitgroup
+ m_WeaponStats[weaponID].m_iBodygroupsHit[hitgroup]++;
+
+ // shots hit
+ m_WeaponStats[weaponID].m_iNumShotsHit++;
+
+ int userID = pVictim->GetUserID();
+
+ // add total damage to the player record
+ int lookup = m_KilledPlayers.Find( userID );
+ if ( lookup == m_KilledPlayers.InvalidIndex() )
+ {
+ // make a new one
+ playerstat_t p;
+ Q_strncpy( p.m_szPlayerName, pVictim->GetPlayerName(), MAX_PLAYER_NAME_LENGTH );
+ p.m_iUserID = userID;
+ p.m_iKills = 0;
+ p.m_iTotalDamage = iDamageGiven;
+
+ m_KilledPlayers.Insert( userID, p );
+ }
+ else
+ {
+ m_KilledPlayers[lookup].m_iTotalDamage += iDamageGiven;
+ }
+
+ DODGameRules()->Stats_WeaponHit( weaponID, flHitDistance );
+
+ StatEvent_WeaponHit( (DODWeaponID)weaponID, ( hitgroup == HITGROUP_HEAD ) );
+}
+
+void CDODPlayer::Stats_HitByWeapon( CDODPlayer *pAttacker, int weaponID, int iDamage, int iDamageGiven, int hitgroup )
+{
+ // damage
+ m_WeaponStats[weaponID].m_iTotalDamageTaken += iDamageGiven;
+
+ // hitgroup
+ m_WeaponStats[weaponID].m_iHitInBodygroups[hitgroup]++;
+
+ // shots hit
+ m_WeaponStats[weaponID].m_iNumHitsTaken++;
+
+ int userID = pAttacker->GetUserID();
+
+ // add total damage to the player record
+ int lookup = m_KilledByPlayers.Find( userID );
+ if ( lookup == m_KilledByPlayers.InvalidIndex() )
+ {
+ // make a new one
+ playerstat_t p;
+ Q_strncpy( p.m_szPlayerName, pAttacker->GetPlayerName(), MAX_PLAYER_NAME_LENGTH );
+ p.m_iUserID = userID;
+ p.m_iKills = 0;
+ p.m_iTotalDamage = iDamageGiven;
+
+ m_KilledByPlayers.Insert( userID, p );
+ }
+ else
+ {
+ m_KilledByPlayers[lookup].m_iTotalDamage += iDamageGiven;
+ }
+}
+
+void CDODPlayer::Stats_KilledPlayer( CDODPlayer *pVictim, int weaponID )
+{
+ m_WeaponStats[weaponID].m_iNumKills++;
+
+ int userID = pVictim->GetUserID();
+
+ // add a kill to the player record
+ int lookup = m_KilledPlayers.Find( userID );
+ if ( lookup == m_KilledPlayers.InvalidIndex() )
+ {
+ // make a new one
+ playerstat_t p;
+ Q_strncpy( p.m_szPlayerName, pVictim->GetPlayerName(), MAX_PLAYER_NAME_LENGTH );
+ p.m_iUserID = userID;
+ p.m_iKills = 1;
+ p.m_iTotalDamage = 0;
+
+ m_KilledPlayers.Insert( userID, p );
+ }
+ else
+ {
+ m_KilledPlayers[lookup].m_iKills++;
+ }
+
+ // Gamerules records kills per team
+ DODGameRules()->Stats_PlayerKill( GetTeamNumber(), m_Shared.PlayerClass() );
+
+ m_iPerRoundKills++;
+
+ StatEvent_KilledPlayer( (DODWeaponID)weaponID );
+}
+
+void CDODPlayer::Stats_KilledByPlayer( CDODPlayer *pAttacker, int weaponID )
+{
+ m_WeaponStats[weaponID].m_iTimesKilled++;
+
+ int userID = pAttacker->GetUserID();
+
+ // add a kill to the player record
+ int lookup = m_KilledByPlayers.Find( userID );
+ if ( lookup == m_KilledByPlayers.InvalidIndex() )
+ {
+ // make a new one
+ playerstat_t p;
+ Q_strncpy( p.m_szPlayerName, pAttacker->GetPlayerName(), MAX_PLAYER_NAME_LENGTH );
+ p.m_iUserID = userID;
+ p.m_iKills = 1;
+ p.m_iTotalDamage = 0;
+
+ m_KilledByPlayers.Insert( userID, p );
+ }
+ else
+ {
+ m_KilledByPlayers[lookup].m_iKills++;
+ }
+}
+
+void CDODPlayer::Stats_AreaDefended()
+{
+ // map count
+ m_iNumAreaDefenses++;
+
+ // round count
+ m_iPerRoundDefenses++;
+}
+
+void CDODPlayer::Stats_AreaCaptured()
+{
+ // map count
+ m_iNumAreaCaptures++;
+
+ // round count
+ m_iPerRoundCaptures++;
+}
+
+void CDODPlayer::Stats_BonusRoundKill()
+{
+ m_iNumBonusRoundKills++;
+}
+
+void CDODPlayer::Stats_BombDetonated()
+{
+ m_iPerRoundBombsDetonated++;
+}
+
+void CDODPlayer::NoteWeaponFired()
+{
+ Assert( m_pCurrentCommand );
+ if( m_pCurrentCommand )
+ {
+ m_iLastWeaponFireUsercmd = m_pCurrentCommand->command_number;
+ }
+}
+
+bool CDODPlayer::WantsLagCompensationOnEntity( const CBasePlayer *pPlayer, const CUserCmd *pCmd, const CBitVec<MAX_EDICTS> *pEntityTransmitBits ) const
+{
+ // No need to lag compensate at all if we're not attacking in this command and
+ // we haven't attacked recently.
+ if ( !( pCmd->buttons & IN_ATTACK ) && (pCmd->command_number - m_iLastWeaponFireUsercmd > 5) )
+ return false;
+
+ return BaseClass::WantsLagCompensationOnEntity( pPlayer, pCmd, pEntityTransmitBits );
+}
+
+void CDODPlayer::TallyLatestTimePlayedPerClass( int iOldTeam, int iOldPlayerClass )
+{
+ // Tally the time we spent in the last class, for stats purposes
+ if ( iOldPlayerClass != PLAYERCLASS_UNDEFINED && ( iOldTeam == TEAM_ALLIES || iOldTeam == TEAM_AXIS ) )
+ {
+ // store random in the last slot
+ if ( iOldPlayerClass == PLAYERCLASS_RANDOM )
+ iOldPlayerClass = 6;
+
+ Assert( iOldPlayerClass >= 0 && iOldPlayerClass <= 6 );
+ Assert( iOldTeam == TEAM_ALLIES || iOldTeam == TEAM_AXIS );
+
+ if ( iOldTeam == TEAM_ALLIES )
+ {
+ m_flTimePlayedPerClass_Allies[iOldPlayerClass] += ( gpGlobals->curtime - m_flLastClassChangeTime );
+ }
+ else if ( iOldTeam == TEAM_AXIS )
+ {
+ m_flTimePlayedPerClass_Axis[iOldPlayerClass] += ( gpGlobals->curtime - m_flLastClassChangeTime );
+ }
+ }
+
+ m_flLastClassChangeTime = gpGlobals->curtime;
+}
+
+void CDODPlayer::ResetProgressBar( void )
+{
+ SetProgressBarTime( 0 );
+}
+
+void CDODPlayer::SetProgressBarTime( int barTime )
+{
+ m_iProgressBarDuration = barTime;
+ m_flProgressBarStartTime = this->m_flSimulationTime;
+}
+
+void CDODPlayer::SetDefusing( CDODBombTarget *pTarget )
+{
+ bool bIsDefusing = ( pTarget != NULL );
+
+ if ( bIsDefusing && !m_bIsDefusing )
+ {
+ // start defuse sound
+ EmitSound( "Weapon_C4.Disarm" );
+ m_Shared.SetDefusing( true );
+ }
+ else if ( !bIsDefusing && m_bIsDefusing )
+ {
+ // stop defuse sound
+ StopSound( "Weapon_C4.Disarm" );
+ m_Shared.SetDefusing( false );
+ }
+
+ m_bIsDefusing = bIsDefusing;
+
+ m_pDefuseTarget = pTarget;
+}
+
+void CDODPlayer::SetPlanting( CDODBombTarget *pTarget )
+{
+ bool bIsPlanting = ( pTarget != NULL );
+
+ if ( bIsPlanting && !m_bIsPlanting )
+ {
+ // start defuse sound
+ EmitSound( "Weapon_C4.Plant" );
+ m_Shared.SetPlanting( true );
+ }
+ else if ( !bIsPlanting && m_bIsPlanting )
+ {
+ // stop defuse sound
+ StopSound( "Weapon_C4.Plant" );
+ m_Shared.SetPlanting( false );
+ }
+
+ m_bIsPlanting = bIsPlanting;
+ m_pPlantTarget = pTarget;
+}
+
+void CDODPlayer::StoreCaptureBlock( int iAreaIndex, int iCapAttempt )
+{
+ m_iLastBlockAreaIndex = iAreaIndex;
+ m_iLastBlockCapAttempt = iCapAttempt;
+}
+
+// The capture attempt number that was taking place the last time we blocked a capture
+int CDODPlayer::GetLastBlockCapAttempt( void )
+{
+ return m_iLastBlockCapAttempt;
+}
+
+// The index of the area where we last blocked a capture
+int CDODPlayer::GetLastBlockAreaIndex( void )
+{
+ return m_iLastBlockCapAttempt;
+}
+
+// NOTE! This is unused, unless we reenable our collision bounds to use USE_GAME_CODE
+// - This extends our trigger bounds out a long ways and breaks many things, so only
+// do this if you know what you're doing.
+void CDODPlayer::ComputeWorldSpaceSurroundingBox( Vector *pVecWorldMins, Vector *pVecWorldMaxs )
+{
+ m_Shared.ComputeWorldSpaceSurroundingBox( pVecWorldMins, pVecWorldMaxs );
+}
+
+// Called when we fire a bullet, with the number of headshots we hit
+void CDODPlayer::HandleHeadshotAchievement( int iNumHeadshots )
+{
+ if ( iNumHeadshots <= 0 )
+ {
+ SetPerLifeCounterKV( "headshots", 0 );
+ }
+ else
+ {
+ if ( DODGameRules()->State_Get() != STATE_RND_RUNNING )
+ return;
+
+ int iHeadshots = GetPerLifeCounterKV( "headshots" ) + iNumHeadshots;
+
+ if ( iHeadshots >= ACHIEVEMENT_NUM_CONSECUTIVE_HEADSHOTS )
+ {
+ AwardAchievement( ACHIEVEMENT_DOD_CONSECUTIVE_HEADSHOTS );
+ }
+
+ SetPerLifeCounterKV( "headshots", iHeadshots );
+ }
+}
+
+void CDODPlayer::HandleDeployedMGKillCount( int iNumDeployedKills )
+{
+ if ( iNumDeployedKills <= 0 )
+ {
+ SetPerLifeCounterKV( "deployed_mg_kills", 0 );
+ }
+ else
+ {
+ if ( DODGameRules()->State_Get() != STATE_RND_RUNNING )
+ return;
+
+ int iKillsFromPos = GetPerLifeCounterKV( "deployed_mg_kills" ) + iNumDeployedKills;
+
+ if ( iKillsFromPos >= ACHIEVEMENT_MG_STREAK_IS_DOMINATING )
+ {
+ AwardAchievement( ACHIEVEMENT_DOD_MG_POSITION_STREAK );
+ }
+
+ SetPerLifeCounterKV( "deployed_mg_kills", iKillsFromPos );
+ }
+}
+
+int CDODPlayer::GetDeployedKillStreak( void )
+{
+ return GetPerLifeCounterKV( "deployed_mg_kills" );
+}
+
+void CDODPlayer::HandleEnemyWeaponsAchievement( int iNumEnemyWpnKills )
+{
+ if ( iNumEnemyWpnKills <= 0 )
+ {
+ SetPerLifeCounterKV( "enemy_wpn_kills", 0 );
+ }
+ else
+ {
+ if ( DODGameRules()->State_Get() != STATE_RND_RUNNING )
+ return;
+
+ int iKills = GetPerLifeCounterKV( "enemy_wpn_kills" ) + iNumEnemyWpnKills;
+
+ if ( iKills >= ACHIEVEMENT_NUM_ENEMY_WPN_KILLS )
+ {
+ AwardAchievement( ACHIEVEMENT_DOD_USE_ENEMY_WEAPONS );
+ }
+
+ SetPerLifeCounterKV( "enemy_wpn_kills", iKills );
+ }
+}
+
+void CDODPlayer::ResetComboWeaponKill( void )
+{
+ SetPerLifeCounterKV( "combo_wpn_mask", 0 );
+}
+
+void CDODPlayer::HandleComboWeaponKill( int iWeaponType )
+{
+ if ( DODGameRules()->State_Get() != STATE_RND_RUNNING )
+ return;
+
+ int iMask = GetPerLifeCounterKV( "combo_wpn_mask" );
+
+ iMask |= iWeaponType;
+
+ SetPerLifeCounterKV( "combo_wpn_mask", iMask );
+
+ const int iRequiredMask = ( WPN_TYPE_MG | WPN_TYPE_SNIPER | WPN_TYPE_RIFLE | WPN_TYPE_SUBMG );
+ const int iExplosiveMask = ( WPN_TYPE_GRENADE | WPN_TYPE_RIFLEGRENADE );
+
+ if ( ( iMask & iRequiredMask ) == iRequiredMask ) // must have all these bits set
+ {
+ if ( ( iMask & iExplosiveMask ) > 0 ) // has one of these bits set
+ {
+ AwardAchievement( ACHIEVEMENT_DOD_WEAPON_MASTERY );
+ }
+ }
+}
+
+void CDODPlayer::PlayUseDenySound()
+{
+ EmitSound( "Player.UseDeny" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Removes all nemesis relationships between this player and others
+//-----------------------------------------------------------------------------
+void CDODPlayer::RemoveNemesisRelationships()
+{
+ for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ )
+ {
+ CDODPlayer *pPlayer = ToDODPlayer( UTIL_PlayerByIndex( i ) );
+ if ( pPlayer && pPlayer != this )
+ {
+ // we are no longer dominating anyone
+ m_Shared.SetPlayerDominated( pPlayer, false );
+ Q_memset( iNumKilledByUnanswered, 0, sizeof( iNumKilledByUnanswered ) );
+
+ // noone is dominating us anymore
+ pPlayer->m_Shared.SetPlayerDominated( this, false );
+ pPlayer->iNumKilledByUnanswered[entindex()] = 0;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Called when we get a new achievement, recalc our awards
+//-----------------------------------------------------------------------------
+void CDODPlayer::OnAchievementEarned( int iAchievement )
+{
+ BaseClass::OnAchievementEarned( iAchievement );
+
+ RecalculateAchievementAwardsMask();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Determine what achievement awards this player has
+//-----------------------------------------------------------------------------
+void CDODPlayer::RecalculateAchievementAwardsMask( void )
+{
+#if !defined(NO_STEAM)
+
+ if ( steamgameserverapicontext->SteamGameServerStats() )
+ {
+ CSteamID steamIDForPlayer;
+ if ( GetSteamID( &steamIDForPlayer ) && steamIDForPlayer.BIndividualAccount() )
+ {
+ steamgameserverapicontext->SteamGameServerStats()->RequestUserStats( steamIDForPlayer );
+ }
+ }
+#endif
+}
+
+#if !defined(NO_STEAM)
+//-----------------------------------------------------------------------------
+// Purpose: Called when Steam returns a user's stats
+//-----------------------------------------------------------------------------
+void CDODPlayer::OnGSStatsReceived( GSStatsReceived_t *pResponse )
+{
+ if ( pResponse->m_eResult != k_EResultOK )
+ return;
+
+ if ( pResponse->m_steamIDUser == GetSteamIDAsUInt64() )
+ {
+ for ( int i = 1; i < NUM_ACHIEVEMENT_AWARDS; i++ )
+ {
+ bool bUnlocked;
+ if ( steamgameserverapicontext->SteamGameServerStats()->GetUserAchievement( pResponse->m_steamIDUser, g_pszAchievementAwards[i], &bUnlocked ) )
+ {
+ if ( bUnlocked )
+ {
+ m_iAchievementAwardsMask |= (1<<i);
+ break;
+ }
+ }
+ }
+ }
+}
+#endif
+
+void CDODPlayer::StatEvent_UploadStats( void )
+{
+ int iSecondsAlive = (int)( gpGlobals->curtime - m_flTimeAsClassAccumulator );
+
+ m_StatProperty.IncrementPlayerClassStat( DODSTAT_PLAYTIME, iSecondsAlive );
+
+ m_StatProperty.SendStatsToPlayer( this );
+
+ m_flTimeAsClassAccumulator = gpGlobals->curtime;
+}
+
+void CDODPlayer::StatEvent_KilledPlayer( DODWeaponID iKillingWeapon )
+{
+ m_StatProperty.IncrementPlayerClassStat( DODSTAT_KILLS );
+
+ m_StatProperty.IncrementWeaponStat( iKillingWeapon, DODSTAT_KILLS );
+}
+
+void CDODPlayer::StatEvent_WasKilled( void )
+{
+ m_StatProperty.IncrementPlayerClassStat( DODSTAT_DEATHS );
+}
+
+void CDODPlayer::StatEvent_RoundWin( void )
+{
+ m_StatProperty.IncrementPlayerClassStat( DODSTAT_ROUNDSWON );
+}
+
+void CDODPlayer::StatEvent_RoundLoss( void )
+{
+ m_StatProperty.IncrementPlayerClassStat( DODSTAT_ROUNDSLOST );
+}
+
+void CDODPlayer::StatEvent_PointCaptured( void )
+{
+ m_StatProperty.IncrementPlayerClassStat( DODSTAT_CAPTURES );
+}
+
+void CDODPlayer::StatEvent_CaptureBlocked( void )
+{
+ m_StatProperty.IncrementPlayerClassStat( DODSTAT_BLOCKS );
+}
+
+void CDODPlayer::StatEvent_BombPlanted( void )
+{
+ m_StatProperty.IncrementPlayerClassStat( DODSTAT_BOMBSPLANTED );
+}
+
+void CDODPlayer::StatEvent_BombDefused( void )
+{
+ m_StatProperty.IncrementPlayerClassStat( DODSTAT_BOMBSDEFUSED );
+}
+
+void CDODPlayer::StatEvent_ScoredDomination( void )
+{
+ m_StatProperty.IncrementPlayerClassStat( DODSTAT_DOMINATIONS );
+}
+
+void CDODPlayer::StatEvent_ScoredRevenge( void )
+{
+ m_StatProperty.IncrementPlayerClassStat( DODSTAT_REVENGES );
+}
+
+void CDODPlayer::StatEvent_WeaponFired( DODWeaponID iWeaponID )
+{
+ m_StatProperty.IncrementWeaponStat( iWeaponID, DODSTAT_SHOTS_FIRED );
+}
+
+void CDODPlayer::StatEvent_WeaponHit( DODWeaponID iWeaponID, bool bWasHeadshot )
+{
+ m_StatProperty.IncrementWeaponStat( iWeaponID, DODSTAT_SHOTS_HIT );
+
+ if ( bWasHeadshot )
+ {
+ m_StatProperty.IncrementWeaponStat( iWeaponID, DODSTAT_HEADSHOTS );
+ }
+}
+
+
+// CDODPlayerStatProperty
+
+void CDODPlayerStatProperty::SetClassAndTeamForThisLife( int iPlayerClass, int iTeam )
+{
+ m_bRecordingStats = ( ( iTeam == TEAM_ALLIES || iTeam == TEAM_AXIS ) && ( iPlayerClass >= 0 && iPlayerClass <= NUM_DOD_PLAYERCLASSES ) );
+
+ m_iCurrentLifePlayerClass = iPlayerClass;
+ m_iCurrentLifePlayerTeam = iTeam;
+}
+
+void CDODPlayerStatProperty::IncrementPlayerClassStat( DODStatType_t statType, int iValue /* = 1 */ )
+{
+ if ( !m_bRecordingStats )
+ return;
+
+ Assert( m_iCurrentLifePlayerClass >= 0 && m_iCurrentLifePlayerClass <= 5 );
+
+ m_PlayerStatsPerLife.m_iStat[statType] += iValue;
+}
+
+void CDODPlayerStatProperty::IncrementWeaponStat( DODWeaponID iWeaponID, DODStatType_t statType, int iValue /* = 1 */ )
+{
+ if ( !m_bRecordingStats )
+ return;
+
+ Assert( iWeaponID >= 0 && iWeaponID <= WEAPON_MAX );
+ m_WeaponStatsPerLife[iWeaponID].m_iStat[statType] += iValue;
+ m_bWeaponStatsDirty[iWeaponID] = true;
+}
+
+void CDODPlayerStatProperty::ResetPerLifeStats( void )
+{
+ Q_memset( &m_PlayerStatsPerLife, 0, sizeof(m_PlayerStatsPerLife) );
+ Q_memset( &m_WeaponStatsPerLife, 0, sizeof(m_WeaponStatsPerLife) );
+ Q_memset( &m_bWeaponStatsDirty, 0, sizeof(m_bWeaponStatsDirty) );
+}
+
+void CDODPlayerStatProperty::SendStatsToPlayer( CDODPlayer *pPlayer )
+{
+ if ( !m_bRecordingStats )
+ return;
+
+ // make a bit field of all the stats we want to send (all with non-zero values)
+ int iStat;
+ int iSendPlayerBits = 0;
+ for ( iStat = DODSTAT_FIRST; iStat < DODSTAT_MAX; iStat++ )
+ {
+ if ( m_PlayerStatsPerLife.m_iStat[iStat] > 0 )
+ {
+ iSendPlayerBits |= ( 1 << ( iStat - DODSTAT_FIRST ) );
+ }
+ }
+
+ CUtlVector<int> vecWeaponsToSend;
+
+ // for every weapon that we have stats for, set a bit in the weapon mask
+ for ( int iWeapon = WEAPON_NONE; iWeapon < WEAPON_MAX; iWeapon++ )
+ {
+ if ( m_bWeaponStatsDirty[iWeapon] )
+ {
+ vecWeaponsToSend.AddToTail( iWeapon );
+ }
+ }
+
+ if ( iSendPlayerBits == 0 && vecWeaponsToSend.Count() == 0 )
+ {
+ ResetPerLifeStats();
+ return;
+ }
+
+ iStat = DODSTAT_FIRST;
+
+ CSingleUserRecipientFilter filter( (CBasePlayer *)pPlayer );
+ filter.MakeReliable();
+
+ UserMessageBegin( filter, "DODPlayerStatsUpdate" );
+
+ WRITE_BYTE( m_iCurrentLifePlayerClass ); // write the class
+ WRITE_BYTE( m_iCurrentLifePlayerTeam );
+
+ WRITE_LONG( iSendPlayerBits ); // write the bit mask of which stats follow in the message
+
+ // write all the non-zero stats according to the bit mask
+ while ( iSendPlayerBits > 0 )
+ {
+ if ( iSendPlayerBits & 1 )
+ {
+ WRITE_LONG( m_PlayerStatsPerLife.m_iStat[iStat] );
+ }
+ iSendPlayerBits >>= 1;
+ iStat++;
+ }
+
+ // now the weapon bits
+ // how many weapons
+ WRITE_BYTE( vecWeaponsToSend.Count() );
+
+ int i;
+
+ // send the weapons
+ for ( i=0;i<vecWeaponsToSend.Count(); i++ )
+ {
+ WRITE_BYTE( vecWeaponsToSend.Element(i) );
+ }
+
+ // for each weapon that we're sending stats for
+ for ( i=0;i<vecWeaponsToSend.Count(); i++ )
+ {
+ int iWeapon = vecWeaponsToSend.Element(i);
+
+ // what stats does iWeapon want to send?
+ int iWeaponStatBits = 0;
+
+ for ( iStat = DODSTAT_FIRST; iStat < DODSTAT_MAX; iStat++ )
+ {
+ if ( m_WeaponStatsPerLife[iWeapon].m_iStat[iStat] > 0 )
+ {
+ iWeaponStatBits |= ( 1 << ( iStat - DODSTAT_FIRST ) );
+ }
+ }
+
+ // send the mask of stats that we're sending for this weapon
+ WRITE_LONG( iWeaponStatBits );
+
+ // send the stats
+ iStat = DODSTAT_FIRST;
+
+ // send that mask
+ while ( iWeaponStatBits > 0 )
+ {
+ if ( iWeaponStatBits & 1 )
+ {
+ WRITE_LONG( m_WeaponStatsPerLife[iWeapon].m_iStat[iStat] );
+ }
+ iWeaponStatBits >>= 1;
+ iStat++;
+ }
+ }
+
+ MessageEnd();
+
+ ResetPerLifeStats();
+}