//========= 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();
}