//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//===========================================================================//

#include "cbase.h"
#include "c_dod_player.h"
#include "c_user_message_register.h"
#include "view.h"
#include "iclientvehicle.h"
#include "ivieweffects.h"
#include "input.h"
#include "IEffects.h"
#include "fx.h"
#include "c_basetempentity.h"
#include "hud_macros.h"
#include "engine/ivdebugoverlay.h"
#include "smoke_fog_overlay.h"
#include "bone_setup.h"
#include "in_buttons.h"
#include "r_efx.h"
#include "dlight.h"
#include "shake.h"
#include "cl_animevent.h"
#include "fx_dod_blood.h"
#include "effect_dispatch_data.h"	//for water ripple / splash effect
#include "c_te_effect_dispatch.h"	//ditto
#include "dod_gamerules.h"
#include <igameevents.h>
#include "physpropclientside.h"
#include "obstacle_pushaway.h"
#include "prediction.h"
#include "viewangleanim.h"
#include "soundenvelope.h"
#include "weapon_dodbipodgun.h"
#include "c_dod_basegrenade.h"
#include "dod_weapon_parse.h"
#include "view_scene.h"
#include "dod_headiconmanager.h"
#include "c_world.h"
#include "c_dod_bombtarget.h"
#include "toolframework/itoolframework.h"
#include "toolframework_client.h"
#include "c_team.h"
#include "collisionutils.h"
#include "weapon_dodsniper.h"
// NVNT - haptics system for spectating and grenades
#include "haptics/haptic_utils.h"
// NVNT - for grenade effects
#include "weapon_dodbasegrenade.h"
// NVNT - for planting bomb effect
#include "weapon_dodbasebomb.h"

#if defined( CDODPlayer )
	#undef CDODPlayer
#endif

#include "iviewrender_beams.h"			// flashlight beam

#include "materialsystem/imesh.h"		//for materials->FindMaterial
#include "iviewrender.h"				//for view->
ConVar cl_ragdoll_physics_enable( "cl_ragdoll_physics_enable", "1", 0, "Enable/disable ragdoll physics." );

ConVar cl_autoreload( "cl_autoreload", "1", FCVAR_USERINFO | FCVAR_ARCHIVE, "Set to 1 to auto reload your weapon when it is empty" );
ConVar cl_autorezoom( "cl_autorezoom", "1", FCVAR_USERINFO | FCVAR_ARCHIVE, "When set to 1, sniper rifles and bazooka weapons will automatically raise after each shot" );

#include "tier0/memdbgon.h"

//======================================================
//
// Cold Breath Emitter - for DOD players.
//
class ColdBreathEmitter : public CSimpleEmitter
{
public:
	
	ColdBreathEmitter( const char *pDebugName ) : CSimpleEmitter( pDebugName ) {}

	static ColdBreathEmitter *Create( const char *pDebugName )
	{
		return new ColdBreathEmitter( pDebugName );
	}

	void UpdateVelocity( SimpleParticle *pParticle, float timeDelta )
	{
		// Float up when lifetime is half gone.
		pParticle->m_vecVelocity[2] -= ( 8.0f * timeDelta );


		// FIXME: optimize this....
		pParticle->m_vecVelocity *= ExponentialDecay( 0.9, 0.03, timeDelta );
	}

	virtual	float UpdateRoll( SimpleParticle *pParticle, float timeDelta )
	{
		pParticle->m_flRoll += pParticle->m_flRollDelta * timeDelta;
		
		pParticle->m_flRollDelta += pParticle->m_flRollDelta * ( timeDelta * -2.0f );

		//Cap the minimum roll
		if ( fabs( pParticle->m_flRollDelta ) < 0.5f )
		{
			pParticle->m_flRollDelta = ( pParticle->m_flRollDelta > 0.0f ) ? 0.5f : -0.5f;
		}

		return pParticle->m_flRoll;
	}

private:

	ColdBreathEmitter( const ColdBreathEmitter & );
};


void RecvProxy_StunTime( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
	C_DODPlayer *pPlayerData = (C_DODPlayer *) pStruct;

	if( pPlayerData != C_BasePlayer::GetLocalPlayer() )
		return;

	if ( (pPlayerData->m_flStunDuration != pData->m_Value.m_Float) && pData->m_Value.m_Float > 0 )
	{
		pPlayerData->m_flStunAlpha = 1;
	}

	pPlayerData->m_flStunDuration = pData->m_Value.m_Float;
	pPlayerData->m_flStunEffectTime = gpGlobals->curtime + pPlayerData->m_flStunDuration;
}

// -------------------------------------------------------------------------------- //
// Player animation event. Sent to the client when a player fires, jumps, reloads, etc..
// -------------------------------------------------------------------------------- //

class C_TEPlayerAnimEvent : public C_BaseTempEntity
{
public:
	DECLARE_CLASS( C_TEPlayerAnimEvent, C_BaseTempEntity );
	DECLARE_CLIENTCLASS();

	virtual void PostDataUpdate( DataUpdateType_t updateType )
	{
		// Create the effect.
		C_DODPlayer *pPlayer = dynamic_cast< C_DODPlayer* >( m_hPlayer.Get() );
		if ( pPlayer && !pPlayer->IsDormant() )
		{
			pPlayer->DoAnimationEvent( (PlayerAnimEvent_t)m_iEvent.Get(), m_nData );
		}	
	}

public:
	CNetworkHandle( CBasePlayer, m_hPlayer );
	CNetworkVar( int, m_iEvent );
	CNetworkVar( int, m_nData );
};

IMPLEMENT_CLIENTCLASS_EVENT( C_TEPlayerAnimEvent, DT_TEPlayerAnimEvent, CTEPlayerAnimEvent );


BEGIN_RECV_TABLE_NOBASE( C_TEPlayerAnimEvent, DT_TEPlayerAnimEvent )
	RecvPropEHandle( RECVINFO( m_hPlayer ) ),
	RecvPropInt( RECVINFO( m_iEvent ) ),
	RecvPropInt( RECVINFO( m_nData ) )
END_RECV_TABLE()

void RecvProxy_CapAreaIndex( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
	CDODPlayerShared *pShared = ( CDODPlayerShared *)pStruct;

	int iCapAreaIndex = pData->m_Value.m_Int;

	if ( iCapAreaIndex != pShared->GetCPIndex() )
	{
		pShared->SetCPIndex( iCapAreaIndex );
	}
}

// ------------------------------------------------------------------------------------------ //
// Data tables.
// ------------------------------------------------------------------------------------------ //

// CDODPlayerShared Data Tables
//=============================

// specific to the local player ( ideally should not be in CDODPlayerShared! )
BEGIN_RECV_TABLE_NOBASE( CDODPlayerShared, DT_DODSharedLocalPlayerExclusive )
	RecvPropInt( RECVINFO( m_iPlayerClass ) ),
	RecvPropInt( RECVINFO( m_iDesiredPlayerClass ) ),
	RecvPropFloat( RECVINFO( m_flDeployedYawLimitLeft ) ),
	RecvPropFloat( RECVINFO( m_flDeployedYawLimitRight ) ),
	RecvPropInt( RECVINFO( m_iCPIndex ), 0, RecvProxy_CapAreaIndex ),
	RecvPropArray3( RECVINFO_ARRAY( m_bPlayerDominated ), RecvPropBool( RECVINFO( m_bPlayerDominated[0] ) ) ),
	RecvPropArray3( RECVINFO_ARRAY( m_bPlayerDominatingMe ), RecvPropBool( RECVINFO( m_bPlayerDominatingMe[0] ) ) ),
END_RECV_TABLE()

// main table
BEGIN_RECV_TABLE_NOBASE( CDODPlayerShared, DT_DODPlayerShared )
	RecvPropFloat( RECVINFO( m_flStamina ) ),
	RecvPropTime( RECVINFO( m_flSlowedUntilTime ) ),
	RecvPropBool( RECVINFO( m_bProne ) ),
	RecvPropBool( RECVINFO( m_bIsSprinting ) ),
	RecvPropTime( RECVINFO( m_flGoProneTime ) ),
	RecvPropTime( RECVINFO( m_flUnProneTime ) ),
	RecvPropTime( RECVINFO( m_flDeployChangeTime ) ),
	RecvPropFloat( RECVINFO( m_flDeployedHeight ) ),
	RecvPropBool( RECVINFO( m_bPlanting ) ),
	RecvPropBool( RECVINFO( m_bDefusing ) ),
	RecvPropDataTable( "dodsharedlocaldata", 0, 0, &REFERENCE_RECV_TABLE(DT_DODSharedLocalPlayerExclusive) ),
END_RECV_TABLE()


// C_DODPlayer Data Tables
//=========================

// specific to the local player
BEGIN_RECV_TABLE_NOBASE( C_DODPlayer, DT_DODLocalPlayerExclusive )
	RecvPropVector( RECVINFO_NAME( m_vecNetworkOrigin, m_vecOrigin ) ),
	RecvPropFloat( RECVINFO( m_flStunDuration ), 0, RecvProxy_StunTime ),
	RecvPropFloat( RECVINFO( m_flStunMaxAlpha)),
	RecvPropInt( RECVINFO( m_iProgressBarDuration ) ),
	RecvPropFloat( RECVINFO( m_flProgressBarStartTime ) ),
END_RECV_TABLE()

// all players except the local player
BEGIN_RECV_TABLE_NOBASE( C_DODPlayer, DT_DODNonLocalPlayerExclusive )
	RecvPropVector( RECVINFO_NAME( m_vecNetworkOrigin, m_vecOrigin ) ),
END_RECV_TABLE()

// main table
IMPLEMENT_CLIENTCLASS_DT( C_DODPlayer, DT_DODPlayer, CDODPlayer )
	RecvPropDataTable( RECVINFO_DT( m_Shared ), 0, &REFERENCE_RECV_TABLE( DT_DODPlayerShared ) ),

	RecvPropDataTable( "dodlocaldata", 0, 0, &REFERENCE_RECV_TABLE(DT_DODLocalPlayerExclusive) ),
	RecvPropDataTable( "dodnonlocaldata", 0, 0, &REFERENCE_RECV_TABLE(DT_DODNonLocalPlayerExclusive) ),

	RecvPropFloat( RECVINFO( m_angEyeAngles[0] ) ),
	RecvPropFloat( RECVINFO( m_angEyeAngles[1] ) ),
	RecvPropEHandle( RECVINFO( m_hRagdoll ) ),
	RecvPropBool( RECVINFO( m_bSpawnInterpCounter ) ),
	RecvPropInt( RECVINFO( m_iAchievementAwardsMask ) ),

END_RECV_TABLE()


// ------------------------------------------------------------------------------------------ //
// Prediction tables.
// ------------------------------------------------------------------------------------------ //
BEGIN_PREDICTION_DATA_NO_BASE( CDODPlayerShared )
	DEFINE_PRED_FIELD( m_bProne, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
	DEFINE_PRED_FIELD( m_flStamina, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
	DEFINE_PRED_FIELD( m_bIsSprinting, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
	DEFINE_PRED_FIELD( m_flGoProneTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
	DEFINE_PRED_FIELD( m_flUnProneTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
	DEFINE_PRED_FIELD( m_flDeployChangeTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
	DEFINE_PRED_FIELD( m_flDeployedHeight, FIELD_FLOAT, FTYPEDESC_INSENDTABLE )
END_PREDICTION_DATA()

BEGIN_PREDICTION_DATA( C_DODPlayer )
	DEFINE_PRED_TYPEDESCRIPTION( m_Shared, CDODPlayerShared ),
	DEFINE_PRED_FIELD( m_flCycle, FIELD_FLOAT, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE | FTYPEDESC_NOERRORCHECK ),
	DEFINE_PRED_FIELD( m_nSequence, FIELD_INTEGER, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE | FTYPEDESC_NOERRORCHECK ),
END_PREDICTION_DATA()


// ----------------------------------------------------------------------------- //
// Client ragdoll entity.
// ----------------------------------------------------------------------------- //

ConVar cl_low_violence( "cl_low_violence", "0" );
ConVar cl_ragdoll_fade_time( "cl_ragdoll_fade_time", "15", FCVAR_CLIENTDLL );
ConVar cl_ragdoll_pronecheck_distance( "cl_ragdoll_pronecheck_distance", "64", FCVAR_GAMEDLL );

class C_DODRagdoll : public C_BaseAnimatingOverlay
{
public:
	DECLARE_CLASS( C_DODRagdoll, C_BaseAnimatingOverlay );
	DECLARE_CLIENTCLASS();
	
	C_DODRagdoll();
	~C_DODRagdoll();

	virtual void OnDataChanged( DataUpdateType_t type );

	int GetPlayerEntIndex() const;
	IRagdoll* GetIRagdoll() const;

	void ImpactTrace( trace_t *pTrace, int iDamageType, const char *pCustomImpactName );

	void ClientThink( void );
	void StartFadeOut( float fDelay );
	
	bool IsRagdollVisible();
private:
	
	C_DODRagdoll( const C_DODRagdoll & ) {}

	void Interp_Copy( C_BaseAnimatingOverlay *pSourceEntity );

	void CreateLowViolenceRagdoll();
	void CreateDODRagdoll();

private:

	EHANDLE	m_hPlayer;
	CNetworkVector( m_vecRagdollVelocity );
	CNetworkVector( m_vecRagdollOrigin );
	float m_fDeathTime;
	bool  m_bFadingOut;
};


IMPLEMENT_CLIENTCLASS_DT_NOBASE( C_DODRagdoll, DT_DODRagdoll, CDODRagdoll )
	RecvPropVector( RECVINFO(m_vecRagdollOrigin) ),
	RecvPropEHandle( RECVINFO( m_hPlayer ) ),
	RecvPropInt( RECVINFO( m_nModelIndex ) ),
	RecvPropInt( RECVINFO(m_nForceBone) ),
	RecvPropVector( RECVINFO(m_vecForce) ),
	RecvPropVector( RECVINFO( m_vecRagdollVelocity ) )
END_RECV_TABLE()


C_DODRagdoll::C_DODRagdoll()
{
	m_fDeathTime = -1;
	m_bFadingOut = false;
}

C_DODRagdoll::~C_DODRagdoll()
{
	PhysCleanupFrictionSounds( this );
}

void C_DODRagdoll::Interp_Copy( C_BaseAnimatingOverlay *pSourceEntity )
{
	if ( !pSourceEntity )
		return;
	
	VarMapping_t *pSrc = pSourceEntity->GetVarMapping();
	VarMapping_t *pDest = GetVarMapping();
    	
	// Find all the VarMapEntry_t's that represent the same variable.
	for ( int i = 0; i < pDest->m_Entries.Count(); i++ )
	{
		VarMapEntry_t *pDestEntry = &pDest->m_Entries[i];
		for ( int j=0; j < pSrc->m_Entries.Count(); j++ )
		{
			VarMapEntry_t *pSrcEntry = &pSrc->m_Entries[j];
			if ( !Q_strcmp( pSrcEntry->watcher->GetDebugName(),
				pDestEntry->watcher->GetDebugName() ) )
			{
				pDestEntry->watcher->Copy( pSrcEntry->watcher );
				break;
			}
		}
	}
}

void C_DODRagdoll::ImpactTrace( trace_t *pTrace, int iDamageType, const char *pCustomImpactName )
{
	IPhysicsObject *pPhysicsObject = VPhysicsGetObject();

	if( !pPhysicsObject )
		return;

	Vector dir = pTrace->endpos - pTrace->startpos;

	if ( iDamageType == DMG_BLAST )
	{
		dir *= 4000;  // adjust impact strength
				
		// apply force at object mass center
		pPhysicsObject->ApplyForceCenter( dir );
	}
	else
	{
		Vector hitpos;  
	
		VectorMA( pTrace->startpos, pTrace->fraction, dir, hitpos );
		VectorNormalize( dir );

		dir *= 4000;  // adjust impact strength

		// apply force where we hit it
		pPhysicsObject->ApplyForceOffset( dir, hitpos );	

		// Blood spray!
		FX_DOD_BloodSpray( hitpos, dir, 10 );
	}

	m_pRagdoll->ResetRagdollSleepAfterTime();
}


void C_DODRagdoll::CreateLowViolenceRagdoll()
{
	// Just play a death animation.
	// Find a death anim to play.
	int iMinDeathAnim = 9999, iMaxDeathAnim = -9999;
	for ( int iAnim=1; iAnim < 100; iAnim++ )
	{
		char str[512];
		Q_snprintf( str, sizeof( str ), "death%d", iAnim );
		if ( LookupSequence( str ) == -1 )
			break;
		
		iMinDeathAnim = MIN( iMinDeathAnim, iAnim );
		iMaxDeathAnim = MAX( iMaxDeathAnim, iAnim );
	}

	if ( iMinDeathAnim == 9999 )
	{
		CreateDODRagdoll();
	}
	else
	{
		int iDeathAnim = RandomInt( iMinDeathAnim, iMaxDeathAnim );
		char str[512];
		Q_snprintf( str, sizeof( str ), "death%d", iDeathAnim );

		SetSequence( LookupSequence( str ) );
		ForceClientSideAnimationOn();

		SetNetworkOrigin( m_vecRagdollOrigin );
		SetAbsOrigin( m_vecRagdollOrigin );
		SetAbsVelocity( m_vecRagdollVelocity );

		C_DODPlayer *pPlayer = dynamic_cast< C_DODPlayer* >( m_hPlayer.Get() );
		if ( pPlayer && !pPlayer->IsDormant() )
		{
			// move my current model instance to the ragdoll's so decals are preserved.
			pPlayer->SnatchModelInstance( this );

			SetAbsAngles( pPlayer->GetRenderAngles() );
			SetNetworkAngles( pPlayer->GetRenderAngles() );
		}
	
		Interp_Reset( GetVarMapping() );
	}
}

void C_DODRagdoll::CreateDODRagdoll()
{
	// First, initialize all our data. If we have the player's entity on our client,
	// then we can make ourselves start out exactly where the player is.
	C_DODPlayer *pPlayer = dynamic_cast< C_DODPlayer* >( m_hPlayer.Get() );

#ifdef _DEBUG
	DevMsg( 2, "CreateDODRagdoll %d %d\n", gpGlobals->framecount, pPlayer ? pPlayer->entindex() : 0 );
#endif
	
	if ( pPlayer && !pPlayer->IsDormant() )
	{
		// move my current model instance to the ragdoll's so decals are preserved.
		pPlayer->SnatchModelInstance( this );

		VarMapping_t *varMap = GetVarMapping();

		// Copy all the interpolated vars from the player entity.
		// The entity uses the interpolated history to get bone velocity.		
		if ( !pPlayer->IsLocalPlayer() && pPlayer->dod_IsInterpolationEnabled() )
		{
			Interp_Copy( pPlayer );

			SetAbsAngles( pPlayer->GetRenderAngles() );
			GetRotationInterpolator().Reset();

			m_flAnimTime = pPlayer->m_flAnimTime;
			SetSequence( pPlayer->GetSequence() );
			m_flPlaybackRate = pPlayer->GetPlaybackRate();
		}
		else
		{
			// This is the local player, so set them in a default
			// pose and slam their velocity, angles and origin
			SetAbsOrigin( m_vecRagdollOrigin );
			
			SetAbsAngles( pPlayer->GetRenderAngles() );

			SetAbsVelocity( m_vecRagdollVelocity );

			int iSeq = LookupSequence( "RagdollSpawn" );	// hax, find a neutral standing pose
			if ( iSeq == -1 )
			{
				Assert( false );	// missing look_idle?
				iSeq = 0;
			}
			
			SetSequence( iSeq );	// look_idle, basic pose
			SetCycle( 0.0 );

			Interp_Reset( varMap );
		}		

		m_nBody = pPlayer->GetBody();
	}
	else
	{
		// overwrite network origin so later interpolation will
		// use this position
		SetNetworkOrigin( m_vecRagdollOrigin );

		SetAbsOrigin( m_vecRagdollOrigin );
		SetAbsVelocity( m_vecRagdollVelocity );

		Interp_Reset( GetVarMapping() );
		
	}

	SetModelIndex( m_nModelIndex );
	
	// Turn it into a ragdoll.
	if ( cl_ragdoll_physics_enable.GetInt() )
	{
		// Make us a ragdoll..
		m_nRenderFX = kRenderFxRagdoll;

		matrix3x4_t boneDelta0[MAXSTUDIOBONES];
		matrix3x4_t boneDelta1[MAXSTUDIOBONES];
		matrix3x4_t currentBones[MAXSTUDIOBONES];
		const float boneDt = 0.05f;

		if ( pPlayer && pPlayer == C_BasePlayer::GetLocalPlayer() )
		{
			pPlayer->GetRagdollInitBoneArrays( boneDelta0, boneDelta1, currentBones, boneDt );
		}
		else
		{
			GetRagdollInitBoneArrays( boneDelta0, boneDelta1, currentBones, boneDt );
		}

		InitAsClientRagdoll( boneDelta0, boneDelta1, currentBones, boneDt );
	}
	else
	{
		ClientLeafSystem()->SetRenderGroup( GetRenderHandle(), RENDER_GROUP_TRANSLUCENT_ENTITY );
	}		

	// Fade out the ragdoll in a while
	StartFadeOut( cl_ragdoll_fade_time.GetFloat() );
	SetNextClientThink( gpGlobals->curtime + 5.0f );
}

void C_DODRagdoll::OnDataChanged( DataUpdateType_t type )
{
	BaseClass::OnDataChanged( type );

	if ( type == DATA_UPDATE_CREATED )
	{
		if ( cl_low_violence.GetInt() )
		{
			CreateLowViolenceRagdoll();
		}
		else
		{
			CreateDODRagdoll();
		}
	}
	else 
	{
		if ( !cl_ragdoll_physics_enable.GetInt() )
		{
			// Don't let it set us back to a ragdoll with data from the server.
			m_nRenderFX = kRenderFxNone;
		}
	}
}

IRagdoll* C_DODRagdoll::GetIRagdoll() const
{
	return m_pRagdoll;
}

bool C_DODRagdoll::IsRagdollVisible()
{
	Vector vMins = Vector(-1,-1,-1);	//WorldAlignMins();
	Vector vMaxs = Vector(1,1,1);	//WorldAlignMaxs();
		
	Vector origin = GetAbsOrigin();
	
	if( !engine->IsBoxInViewCluster( vMins + origin, vMaxs + origin) )
	{
		return false;
	}
	else if( engine->CullBox( vMins + origin, vMaxs + origin ) )
	{
		return false;
	}

	return true;
}

void C_DODRagdoll::ClientThink( void )
{
	SetNextClientThink( CLIENT_THINK_ALWAYS );

	if ( m_bFadingOut == true )
	{
		int iAlpha = GetRenderColor().a;
		int iFadeSpeed = 600.0f;

		iAlpha = MAX( iAlpha - ( iFadeSpeed * gpGlobals->frametime ), 0 );

		SetRenderMode( kRenderTransAlpha );
		SetRenderColorA( iAlpha );

		if ( iAlpha == 0 )
		{
			//Release();
			AddEffects( EF_NODRAW );
		}

		return;
	}

	for( int iClient = 1; iClient <= gpGlobals->maxClients; ++iClient )
	{
		C_DODPlayer *pEnt = static_cast< C_DODPlayer *> ( UTIL_PlayerByIndex( iClient ) );

		if(!pEnt || !pEnt->IsPlayer())
			continue;

		if ( m_hPlayer == NULL )
			continue;

		if ( pEnt->entindex() == m_hPlayer->entindex() )
			continue;
		
		if ( pEnt->GetHealth() <= 0 )
			continue;

		if ( pEnt->m_Shared.IsProne() == false )
			continue;

		Vector vTargetOrigin = pEnt->GetAbsOrigin();
		Vector vMyOrigin =  GetAbsOrigin();

		Vector vDir = vTargetOrigin - vMyOrigin;

		if ( vDir.Length() > cl_ragdoll_pronecheck_distance.GetInt() ) 
			continue;

		SetNextClientThink( CLIENT_THINK_ALWAYS );
		m_bFadingOut = true;
		return;
	}

	// if the player is looking at us, delay the fade
	if ( IsRagdollVisible() )
	{
		StartFadeOut( 5.0 );
		return;
	}

	if ( m_fDeathTime > gpGlobals->curtime )
		return;

	//Release(); // Die
	AddEffects( EF_NODRAW );
}

void C_DODRagdoll::StartFadeOut( float fDelay )
{
	m_fDeathTime = gpGlobals->curtime + fDelay;
	SetNextClientThink( CLIENT_THINK_ALWAYS );
}

// ------------------------------------------------------------------------------------------ //
// C_DODPlayer implementation.
// ------------------------------------------------------------------------------------------ //
C_DODPlayer::C_DODPlayer() : 
	m_iv_angEyeAngles( "C_DODPlayer::m_iv_angEyeAngles" )
{
	m_PlayerAnimState = CreatePlayerAnimState( this );
	
	m_Shared.Init( this );

	m_flPitchRecoilAccumulator = 0.0;
	m_flYawRecoilAccumulator = 0.0;
	m_flRecoilTimeRemaining = 0.0;

	m_iProgressBarDuration = 0;
	m_flProgressBarStartTime = 0.0f;

	AddVar( &m_angEyeAngles, &m_iv_angEyeAngles, LATCH_SIMULATION_VAR );

	m_flProneViewOffset = 0.0;
	m_bProneSwayingRight = true;
	m_iIDEntIndex = 0;

	m_Hints.Init( this, NUM_HINTS, g_pszHintMessages );

	m_pFlashlightBeam = NULL;

	m_fNextThinkPushAway = 0.0f;

	// Cold breath.
	m_bColdBreathOn = false;
	m_flColdBreathTimeStart = 0.0f;
	m_flColdBreathTimeEnd = 0.0f;
	m_hColdBreathEmitter = NULL;
	m_hColdBreathMaterial = INVALID_MATERIAL_HANDLE;

	m_flHideHeadIconUntilTime = 0.0f;

	m_iAchievementAwardsMask = 0;
	m_pHeadIconMaterial = NULL;
}


C_DODPlayer::~C_DODPlayer()
{
	m_PlayerAnimState->Release();

	ReleaseFlashlight();

	// Kill the stamina sound!
	if ( m_pStaminaSound )
	{
		CSoundEnvelopeController::GetController().SoundDestroy( m_pStaminaSound );
		m_pStaminaSound = NULL;
	}

	// Cold breath.
	DestroyColdBreathEmitter();
}


C_DODPlayer* C_DODPlayer::GetLocalDODPlayer()
{
	return ToDODPlayer( C_BasePlayer::GetLocalPlayer() );
}

IRagdoll* C_DODPlayer::GetRepresentativeRagdoll() const
{
	if ( m_hRagdoll.Get() )
	{
		C_DODRagdoll *pRagdoll = (C_DODRagdoll*)m_hRagdoll.Get();

		return pRagdoll->GetIRagdoll();
	}
	else
	{
		return NULL;
	}
}

const QAngle& C_DODPlayer::GetRenderAngles()
{
	if ( IsRagdoll() )
	{
		return vec3_angle;
	}
	else
	{
		return m_PlayerAnimState->GetRenderAngles();
	}
}


void C_DODPlayer::UpdateClientSideAnimation()
{
	// Update the animation data. It does the local check here so this works when using
	// a third-person camera (and we don't have valid player angles).
	if ( this == C_DODPlayer::GetLocalDODPlayer() )
		m_PlayerAnimState->Update( EyeAngles()[YAW], m_angEyeAngles[PITCH] );
	else
		m_PlayerAnimState->Update( m_angEyeAngles[YAW], m_angEyeAngles[PITCH] );

	BaseClass::UpdateClientSideAnimation();
}

ConVar dod_playachievementsound( "dod_playachievementsound", "1", FCVAR_ARCHIVE );

void C_DODPlayer::OnAchievementAchieved( int iAchievement )
{
	// don't draw the head icon for a length of time after showing the particle effect
	m_flHideHeadIconUntilTime = gpGlobals->curtime + 2.5;

	if ( dod_playachievementsound.GetBool() )
	{
		EmitSound( "Achievement.Earned" );
	}

	BaseClass::OnAchievementAchieved( iAchievement );
}

int C_DODPlayer::DrawModel( int flags )
{
	int nRetval = BaseClass::DrawModel( flags );
	if ( nRetval != 0 )
	{
		// register to draw the head icon, unless we're hiding it due to the "achieved" particle effect
		if ( gpGlobals->curtime > m_flHideHeadIconUntilTime )
		{
			HeadIconManager()->PlayerDrawn( this );
		}
	}
	return nRetval;
}

void C_DODPlayer::DoAnimationEvent( PlayerAnimEvent_t event, int nData )
{
	m_PlayerAnimState->DoAnimationEvent( event, nData );
}

DODPlayerState C_DODPlayer::State_Get() const
{
	return m_iPlayerState;
}

bool C_DODPlayer::CanShowClassMenu( void )
{
	return ( GetTeamNumber() == TEAM_ALLIES || GetTeamNumber() == TEAM_AXIS );
}

void C_DODPlayer::DoRecoil( int iWpnID, float flWpnRecoil )
{
	float flPitchRecoil = flWpnRecoil;
	float flYawRecoil = flPitchRecoil / 4;

	if( iWpnID == WEAPON_BAR )
		flYawRecoil = MIN( flYawRecoil, 1.3 );

	if ( m_Shared.IsInMGDeploy() )
	{
		flPitchRecoil = 0.0;
		flYawRecoil = 0.0;
	}
	else if ( m_Shared.IsProne() && 
		iWpnID != WEAPON_30CAL && 
		iWpnID != WEAPON_MG42 ) //minor hackage
	{
		flPitchRecoil = flPitchRecoil / 4;
		flYawRecoil = flYawRecoil / 4;
	}
	else if ( m_Shared.IsDucking() )
	{
		flPitchRecoil = flPitchRecoil / 2;
		flYawRecoil = flYawRecoil / 2;
	}

	SetRecoilAmount( flPitchRecoil, flYawRecoil );
}

//Set the amount of pitch and yaw recoil we want to do over the next RECOIL_DURATION seconds
void C_DODPlayer::SetRecoilAmount( float flPitchRecoil, float flYawRecoil )
{
	//Slam the values, abandon previous recoils
	m_flPitchRecoilAccumulator = flPitchRecoil;

	flYawRecoil = flYawRecoil * random->RandomFloat( 0.8, 1.1 );

	if( random->RandomInt( 0,1 ) <= 0 )
		m_flYawRecoilAccumulator = flYawRecoil;
	else
		m_flYawRecoilAccumulator = -flYawRecoil;

	m_flRecoilTimeRemaining = RECOIL_DURATION;
}

//Get the amount of recoil we should do this frame
void C_DODPlayer::GetRecoilToAddThisFrame( float &flPitchRecoil, float &flYawRecoil )
{
	if( m_flRecoilTimeRemaining <= 0 )
	{
		flPitchRecoil = 0.0;
		flYawRecoil = 0.0;
		return;
	}

	float flRemaining = MIN( m_flRecoilTimeRemaining, gpGlobals->frametime );

	float flRecoilProportion = ( flRemaining / RECOIL_DURATION );

	flPitchRecoil = m_flPitchRecoilAccumulator * flRecoilProportion;
	flYawRecoil = m_flYawRecoilAccumulator * flRecoilProportion;

	m_flRecoilTimeRemaining -= gpGlobals->frametime;
}

//-----------------------------------------------------------------------------
// Purpose: Input handling
//-----------------------------------------------------------------------------
bool C_DODPlayer::CreateMove( float flInputSampleTime, CUserCmd *pCmd )
{
	// Lock view if deployed
	if( m_Shared.IsInMGDeploy() )
	{
		m_Shared.ClampDeployedAngles( &pCmd->viewangles );
	}

	//if we're prone and moving, do some sway
	if( m_Shared.IsProne() && IsAlive() )
	{
		float flSpeed = GetAbsVelocity().Length();

		float flSwayAmount = PRONE_SWAY_AMOUNT * gpGlobals->frametime;

		if( flSpeed > 10 )
		{
			if (m_flProneViewOffset >= PRONE_MAX_SWAY)
			{
				m_bProneSwayingRight = false;
			}
			else if (m_flProneViewOffset <= -PRONE_MAX_SWAY)
			{
				m_bProneSwayingRight = true;
			}

			if (m_bProneSwayingRight)
			{
				pCmd->viewangles[YAW]	+= flSwayAmount;
				m_flProneViewOffset		+= flSwayAmount;
			}
			else
			{
				pCmd->viewangles[YAW]	-= flSwayAmount;
				m_flProneViewOffset		-= flSwayAmount;
			}
		}
		else
		{
			// Return to 0 prone sway offset gradually

			//Quick Checks to make sure it isn't bigger or smaller than our sway amount
			if ( (m_flProneViewOffset < 0.0 && m_flProneViewOffset > -flSwayAmount) ||
				 (m_flProneViewOffset > 0.0 && m_flProneViewOffset < flSwayAmount) )
			{
				m_flProneViewOffset = 0.0;
			}

			if (m_flProneViewOffset > 0.0)
			{
				pCmd->viewangles[YAW]	-= flSwayAmount;
				m_flProneViewOffset		-= flSwayAmount;
			}
			else if (m_flProneViewOffset < 0.0)
			{
				pCmd->viewangles[YAW]	+= flSwayAmount;
				m_flProneViewOffset		+= flSwayAmount;
			}
		}
	}

	bool bResult = BaseClass::CreateMove( flInputSampleTime, pCmd );

	AvoidPlayers( pCmd );

	return bResult;
}

// How fast to avoid collisions with center of other object, in units per second
#define AVOID_SPEED 1000.0f
ConVar cl_avoidspeed( "cl_avoidspeed", "1000.0f", FCVAR_CLIENTDLL );
extern ConVar cl_forwardspeed;
extern ConVar cl_backspeed;
extern ConVar cl_sidespeed;

bool C_DODPlayer::ShouldDraw( void )
{
	if( IsDormant() )
		return false;

	// If we're dead, our ragdoll will be drawn for us instead.
	if ( !IsAlive() )
		return false;

	if( GetTeamNumber() == TEAM_SPECTATOR )
		return false;

	if( IsLocalPlayer() )
	{
		if ( IsRagdoll() )
			return true;
	}

	return BaseClass::ShouldDraw();
}

//-----------------------------------------------------------------------------
// Deal with visibility
//-----------------------------------------------------------------------------
void C_DODPlayer::GetToolRecordingState( KeyValues *msg )
{
#ifndef _XBOX
	BaseClass::GetToolRecordingState( msg );
	BaseEntityRecordingState_t *pBaseEntityState = (BaseEntityRecordingState_t*)msg->GetPtr( "baseentity" );
	if ( IsLocalPlayer() )
	{
		pBaseEntityState->m_bVisible = !IsDormant() && IsAlive() && ( GetTeamNumber() != TEAM_SPECTATOR ) &&
			( GetRenderMode() != kRenderNone ) && (GetObserverMode() != OBS_MODE_DEATHCAM) && !IsEffectActive(EF_NODRAW);
	}
#endif
}


CWeaponDODBase* C_DODPlayer::GetActiveDODWeapon() const
{
	C_BaseCombatWeapon *pWpn = GetActiveWeapon();

	if ( !pWpn )
		return NULL;

	return dynamic_cast< CWeaponDODBase* >( pWpn );
}

C_BaseAnimating * C_DODPlayer::BecomeRagdollOnClient()
{
	return NULL;
}

void C_DODPlayer::FireEvent( const Vector& origin, const QAngle& angles, int event, const char *options )
{
	if( event == 7002 )
	{
		if( this == C_BasePlayer::GetLocalPlayer() )
			return;

		CWeaponDODBase *pWeapon = GetActiveDODWeapon();

		if ( !pWeapon )
			return;

		int iAttachment = 2;
		Vector vecOrigin;
		QAngle angAngles;

		if( pWeapon->GetAttachment( iAttachment, vecOrigin, angAngles ) )
		{
			int shellType = atoi(options);

			CEffectData data;
			data.m_nHitBox = shellType;
			data.m_vOrigin = vecOrigin;
			data.m_vAngles = angAngles;
			DispatchEffect( "DOD_EjectBrass", data );
		}
	}
	else
		BaseClass::FireEvent( origin, angles, event, options );

	/*
	// MATTTODO: water footstep effects
	if( event == 7001 )
	{
		bool bInWater = ( enginetrace->GetPointContents(origin) & CONTENTS_WATER );

		if( bInWater )
		{
			//run splash
			CEffectData data;

			//trace up from foot position to the water surface
			trace_t tr;
			Vector vecTrace(0,0,1024);
			UTIL_TraceLine( origin, origin + vecTrace, MASK_WATER, NULL, COLLISION_GROUP_NONE, &tr );
			if ( tr.fractionleftsolid )
			{
				data.m_vOrigin = origin + (vecTrace * tr.fractionleftsolid);
			}
			else
			{
				data.m_vOrigin = origin;
			}
			
			data.m_vNormal = Vector( 0,0,1 );
			data.m_flScale = random->RandomFloat( 4.0f, 5.0f );
			DispatchEffect( "watersplash", data );
		}		
	}
	else if( event == 7002 )
	{
		bool bInWater = ( enginetrace->GetPointContents(origin) & CONTENTS_WATER );
		
		if( bInWater )
		{
			//walk ripple
			CEffectData data;

			//trace up from foot position to the water surface
			trace_t tr;
			Vector vecTrace(0,0,1024);
			UTIL_TraceLine( origin, origin + vecTrace, MASK_WATER, NULL, COLLISION_GROUP_NONE, &tr );
			if ( tr.fractionleftsolid )
			{
				data.m_vOrigin = origin + (vecTrace * tr.fractionleftsolid);
			}
			else
			{
				data.m_vOrigin = origin;
			}
	
			data.m_vNormal = Vector( 0,0,1 );
			data.m_flScale = random->RandomFloat( 4.0f, 7.0f );
			DispatchEffect( "waterripple", data );
		}
	}
	*/
}
// NVNT gate for spectating.
static bool inSpectating_Haptics = false;
// NVNT check grenade things ( -- this is here to avoid modificaions to the grenade class -- )
static bool s_holdingGrenade = false;
static bool s_grenadePinPulled = false;
static bool s_grenadeArmed = false;
static bool s_bombPlanting = false;
void C_DODPlayer::ClientThink()
{
	BaseClass::ClientThink();

	if ( gpGlobals->curtime >= m_fNextThinkPushAway )
	{
		PerformObstaclePushaway( this );
		m_fNextThinkPushAway =  gpGlobals->curtime + PUSHAWAY_THINK_INTERVAL;
	}

	if ( IsLocalPlayer() )
	{	
		UpdateIDTarget();

		StaminaSoundThink();

		// Recoil
		QAngle viewangles;
		engine->GetViewAngles( viewangles );

		float flYawRecoil;
		float flPitchRecoil;
		GetRecoilToAddThisFrame( flPitchRecoil, flYawRecoil );

		// Recoil
		if( flPitchRecoil > 0 )
		{
			//add the recoil pitch
			viewangles[PITCH] -= flPitchRecoil;
			viewangles[YAW] += flYawRecoil;
		}

		// Sniper sway
		if( m_Shared.IsSniperZoomed() && GetFOV() <= 20 )
		{
			//multiply by frametime to balance for framerate changes
			float x = gpGlobals->frametime * cos( gpGlobals->curtime );
			float y = gpGlobals->frametime * 2 * cos( 2 * gpGlobals->curtime );

			float scale;

			if( m_Shared.IsDucking() )				//duck
				scale = ZOOM_SWAY_DUCKING;
			else if( m_Shared.IsProne() )
				scale = ZOOM_SWAY_PRONE;
			else									//standing
				scale = ZOOM_SWAY_STANDING; 

			if( GetAbsVelocity().Length() > 10 )
				scale += ZOOM_SWAY_MOVING_PENALTY;

			viewangles[PITCH] += y * scale;
			viewangles[YAW] += x * scale;
		}

		engine->SetViewAngles( viewangles );
		// NVNT check spectator nav.
		if(	( ( GetTeamNumber() == TEAM_SPECTATOR ) || ( !this->IsAlive() ) ) ) {
				if(!inSpectating_Haptics)
				{
					if ( haptics )
						haptics->SetNavigationClass("spectate");
					inSpectating_Haptics = true;
				}
		}else{
			if(inSpectating_Haptics) {
				if ( haptics )
					haptics->SetNavigationClass("on_foot");
				inSpectating_Haptics = false;
			}
		}

		// NVNT check grenade things ( -- this is here to avoid modificaions to the grenade class -- )
		C_WeaponDODBaseGrenade *heldGrenade = dynamic_cast<C_WeaponDODBaseGrenade*>(GetActiveDODWeapon());
		if(heldGrenade)
		{
			if(!s_holdingGrenade)
			{
				s_holdingGrenade = true;
			}
			bool pinPulled = heldGrenade->m_bPinPulled;
			if(pinPulled != s_grenadePinPulled) {
				if(pinPulled)
				{
					if ( haptics )
						haptics->ProcessHapticEvent(3, "Weapons", heldGrenade->GetClassname(), "PinPulled");
				}else {
					if ( haptics )
						haptics->ProcessHapticEvent(3, "Weapons", heldGrenade->GetClassname(), "PinReplaced");
				}
				s_grenadePinPulled = pinPulled;
			}
			bool grenadeArmed = heldGrenade->m_bArmed;
			if(grenadeArmed != s_grenadeArmed) {
				if(grenadeArmed)
				{
					if ( haptics )
						haptics->ProcessHapticEvent(3, "Weapons", heldGrenade->GetClassname(), "Armed");
				}else {
					if ( haptics )
						haptics->ProcessHapticEvent(3, "Weapons", heldGrenade->GetClassname(), "Unarmed");
				}
				s_grenadeArmed = grenadeArmed;
			}
		}else{
			if( s_holdingGrenade ) 
			{
				if(s_grenadeArmed && s_grenadePinPulled) {
					if ( haptics )
						haptics->ProcessHapticEvent(3, "Weapons", "Grenades", "Thrown");
				}
				s_holdingGrenade = false;
				s_grenadeArmed = false;
				s_grenadePinPulled = false;
			}
			C_DODBaseBombWeapon *heldBomb = dynamic_cast<C_DODBaseBombWeapon*>(GetActiveDODWeapon());

			if(heldBomb) {
				bool isPlanting = heldBomb->IsPlanting();
				if(isPlanting!=s_bombPlanting) {
					if(isPlanting) {
						if(!s_bombPlanting) {
							s_bombPlanting = true;
							if ( haptics )
								haptics->ProcessHapticEvent(3, "Weapons", heldBomb->GetClassname(), "Plant");
						}
					}else{
						if(s_bombPlanting) {
							if ( haptics )
								haptics->ProcessHapticEvent(3, "Weapons", heldBomb->GetClassname(), "StopPlant");
						}
					}
				}
			}else if(s_bombPlanting) {
				s_bombPlanting = false;
				if ( haptics )
					haptics->ProcessHapticEvent(3, "Weapons", "Bomb", "Complete");
			}
		}
		
	}
	else
	{
		// Cold breath.
		UpdateColdBreath();
	}
}

// Start or stop the stamina breathing sound if necessary
void C_DODPlayer::StaminaSoundThink( void )
{
	if ( m_bPlayingLowStaminaSound )
	{
		if ( !IsAlive() || m_Shared.GetStamina() >= LOW_STAMINA_THRESHOLD )
		{
			// stop the sprint sound
			CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
			controller.SoundFadeOut( m_pStaminaSound, 1.0, true );

			// SoundFadeOut will destroy this sound, so we will have to create another one
			// if we go below the threshold again soon
			m_pStaminaSound = NULL;

			m_bPlayingLowStaminaSound = false;
		}
	}
	else
	{
		if ( IsAlive() && m_Shared.GetStamina() < LOW_STAMINA_THRESHOLD )
		{
			// we are alive and have low stamina
			CLocalPlayerFilter filter;

			CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();

			if ( !m_pStaminaSound )
                m_pStaminaSound = controller.SoundCreate( filter, entindex(), "Player.Sprint" );

			controller.Play( m_pStaminaSound, 0.0, 100 );
			controller.SoundChangeVolume( m_pStaminaSound, 1.0, 2.0 );

			m_bPlayingLowStaminaSound = true;
		}
	}
}

void C_DODPlayer::OnDataChanged( DataUpdateType_t type )
{
	BaseClass::OnDataChanged( type );

	if ( type == DATA_UPDATE_CREATED )
	{
		SetNextClientThink( CLIENT_THINK_ALWAYS );
	}

	UpdateVisibility();
}

void C_DODPlayer::PostDataUpdate( DataUpdateType_t updateType )
{
	// C_BaseEntity assumes we're networking the entity's angles, so pretend that it
	// networked the same value we already have.
	SetNetworkAngles( GetLocalAngles() );

	BaseClass::PostDataUpdate( updateType );

	if( m_bSpawnInterpCounter != m_bSpawnInterpCounterCache )
	{
		if ( IsLocalPlayer() )
		{
			LocalPlayerRespawn();
		}

		m_bSpawnInterpCounterCache = m_bSpawnInterpCounter.m_Value;
	}
}

// Called every time the player respawns
void C_DODPlayer::LocalPlayerRespawn( void )
{
	MoveToLastReceivedPosition( true );
	ResetLatched();

	ResetToneMapping(1.0);

	m_Shared.m_bForceProneChange = true;

	m_flLastRespawnTime = gpGlobals->curtime;
}

class C_FadingPhysPropClientside : public C_PhysPropClientside
{
public:	
	DECLARE_CLASS( C_FadingPhysPropClientside, C_PhysPropClientside );

	// if we wake, extend fade time

	virtual void ImpactTrace( trace_t *pTrace, int iDamageType, const char *pCustomImpactName )
	{
		// If we haven't started fading
		if( GetRenderColor().a >= 255 )
		{
			// delay the fade
			StartFadeOut( 10.0 );

			// register the impact
			BaseClass::ImpactTrace( pTrace, iDamageType, pCustomImpactName );
		}		
	}
};

void C_DODPlayer::PopHelmet( Vector vecDir, Vector vecForceOrigin, int iModel )
{
	if ( IsDormant() )
		return;	// We can't see them anyway, just bail

	C_FadingPhysPropClientside *pEntity = new C_FadingPhysPropClientside();

	if ( !pEntity )
		return;

	const model_t *model = modelinfo->GetModel( iModel );

	if ( !model )
	{
		DevMsg("CTempEnts::PhysicsProp: model index %i not found\n", iModel );
		return;
	}

	Vector vecHead;
	QAngle angHeadAngles;

	{
		C_BaseAnimating::AutoAllowBoneAccess boneaccess( true, false );
		int iAttachment = LookupAttachment( "head" );
		GetAttachment( iAttachment, vecHead, angHeadAngles );	//attachment 1 is the head attachment
	}

	pEntity->SetModelName( modelinfo->GetModelName(model) );
	pEntity->SetAbsOrigin( vecHead );
	pEntity->SetAbsAngles( angHeadAngles );
	pEntity->SetPhysicsMode( PHYSICS_MULTIPLAYER_CLIENTSIDE );

	if ( !pEntity->Initialize() )
	{
		pEntity->Release();
		return;
	}

	IPhysicsObject *pPhysicsObject = pEntity->VPhysicsGetObject();

	if( pPhysicsObject )
	{
#ifdef DEBUG
		if( vecForceOrigin == vec3_origin )
		{
			vecForceOrigin = GetAbsOrigin();
		}
#endif

		Vector vecForce = vecDir;		
		Vector vecOffset = vecForceOrigin - pEntity->GetAbsOrigin();
		pPhysicsObject->ApplyForceOffset( vecForce, vecOffset );
	}
	else
	{
		// failed to create a physics object
		pEntity->Release();
		return;
	}

	pEntity->StartFadeOut( 10.0 );
}

void C_DODPlayer::ReceiveMessage( int classID, bf_read &msg )
{
	if ( classID != GetClientClass()->m_ClassID )
	{
		// message is for subclass
		BaseClass::ReceiveMessage( classID, msg );
		return;
	}

	int messageType = msg.ReadByte();
	switch( messageType )
	{
	case DOD_PLAYER_POP_HELMET:
		{
			Vector	vecDir, vecForceOffset;
			msg.ReadBitVec3Coord( vecDir );
			msg.ReadBitVec3Coord( vecForceOffset );

			int model = msg.ReadShort();

			PopHelmet( vecDir, vecForceOffset, model );
		}
		break;
	case DOD_PLAYER_REMOVE_DECALS:
		{
			RemoveAllDecals();
		}
		break;
	default:
		break;
	}
}

//-----------------------------------------------------------------------------
// Purpose: Update this client's target entity
//-----------------------------------------------------------------------------
void C_DODPlayer::UpdateIDTarget()
{
	Assert( IsLocalPlayer() );

	// Clear old target and find a new one
 	m_iIDEntIndex = 0;

	// don't show IDs in chase spec mode
	if ( GetObserverMode() == OBS_MODE_CHASE || 
		 GetObserverMode() == OBS_MODE_DEATHCAM )
		 return;

	trace_t tr;
	Vector vecStart, vecEnd;
	VectorMA( MainViewOrigin(), 1500, MainViewForward(), vecEnd );
	VectorMA( MainViewOrigin(), 10,   MainViewForward(), vecStart );
	UTIL_TraceLine( vecStart, vecEnd, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );

	C_BaseEntity *pEntity = NULL;
	if ( !tr.startsolid && tr.DidHitNonWorldEntity() )
	{
		pEntity = tr.m_pEnt;

		if ( pEntity && (pEntity != this) )
		{
			m_iIDEntIndex = pEntity->entindex();
		}
	}

	// if we haven't done the weapon hint, and this entity is a weapon,
	// show the weapon hint
	if ( m_Hints.HasPlayedHint( HINT_PICK_UP_WEAPON ) == false && pEntity )
	{
		Vector vecDist = vecStart - tr.endpos;

		// if m_iIDEntIndex is a CWeaponDODBase, show pick up hint
		CWeaponDODBase *pWpn = dynamic_cast<CWeaponDODBase *>( pEntity );
		if ( pWpn && vecDist.Length() < 100 )
		{
			HintMessage( HINT_PICK_UP_WEAPON );
		}
	}
}

bool C_DODPlayer::ShouldAutoReload( void )
{
	return cl_autoreload.GetBool();
}

bool C_DODPlayer::ShouldAutoRezoom( void )
{
	return cl_autorezoom.GetBool();
}

void C_DODPlayer::CheckGrenadeHint( Vector vecGrenadeOrigin )
{
	if ( m_Hints.HasPlayedHint( HINT_PICK_UP_GRENADE ) == false )
	{
		// if its within 500 units
		float flDist = ( vecGrenadeOrigin - GetAbsOrigin() ).Length2D();

		if ( flDist < 500 )
		{
			m_Hints.HintMessage( HINT_PICK_UP_GRENADE );
		}
	}
}

void C_DODPlayer::CheckBombTargetPlantHint( void )
{
	if ( m_Hints.HasPlayedHint( HINT_BOMB_TARGET ) == false )
	{
		m_Hints.HintMessage( HINT_BOMB_TARGET );
	}
}

void C_DODPlayer::CheckBombTargetDefuseHint( void )
{
	if ( m_Hints.HasPlayedHint( HINT_DEFUSE_BOMB ) == false )
	{
		m_Hints.HintMessage( HINT_DEFUSE_BOMB );
	}
}

void C_DODPlayer::LowerWeapon( void )
{
	m_bWeaponLowered = true;
}

void C_DODPlayer::RaiseWeapon( void )
{
	m_bWeaponLowered = false;
}

bool C_DODPlayer::IsWeaponLowered( void )
{
	if ( GetMoveType() == MOVETYPE_LADDER )
		return true;

	CWeaponDODBase *pWeapon = GetActiveDODWeapon();

	if ( !pWeapon )
		return false;

	// Lower when underwater ( except if its melee )
	if ( GetWaterLevel() > 2 && pWeapon->GetDODWpnData().m_WeaponType != WPN_TYPE_MELEE )
		return true;

	if ( m_Shared.IsProne() && GetAbsVelocity().LengthSqr() > 1 )
		return true;

	if ( m_Shared.IsGoingProne() || m_Shared.IsGettingUpFromProne() )
		return true;

	if ( m_Shared.IsJumping() )
		return true;

	if ( m_Shared.IsDefusing() )
		return true;

	// Lower losing team's weapons in bonus round
	int state = DODGameRules()->State_Get();

	if ( state == STATE_ALLIES_WIN && GetTeamNumber() == TEAM_AXIS )
		return true;

	if ( state == STATE_AXIS_WIN && GetTeamNumber() == TEAM_ALLIES )
		return true;

	if ( m_Shared.IsBazookaDeployed() )
		return false;

	Vector vel = GetAbsVelocity();
	if ( vel.Length2D() < 50 )
		return false;

	if ( m_nButtons & IN_SPEED && ( m_nButtons & IN_FORWARD ) &&
		m_Shared.GetStamina() >= 5 &&
		!m_Shared.IsDucking() )
		return true;

	return m_bWeaponLowered;
}

// Shadows

ConVar cl_blobbyshadows( "cl_blobbyshadows", "0", FCVAR_CLIENTDLL );
ShadowType_t C_DODPlayer::ShadowCastType( void ) 
{
	if ( !IsVisible() )
		return SHADOWS_NONE;

	C_DODPlayer *pLocalPlayer = C_DODPlayer::GetLocalDODPlayer();

	// if we're first person spectating this player
	if ( pLocalPlayer && 
		pLocalPlayer->GetObserverTarget() == this &&
		pLocalPlayer->GetObserverMode() == OBS_MODE_IN_EYE )
	{
		return SHADOWS_NONE;		
	}

	if( cl_blobbyshadows.GetBool() )
		return SHADOWS_SIMPLE;

	return SHADOWS_RENDER_TO_TEXTURE_DYNAMIC;
}

float g_flFattenAmt = 4;
void C_DODPlayer::GetShadowRenderBounds( Vector &mins, Vector &maxs, ShadowType_t shadowType )
{
	if ( shadowType == SHADOWS_SIMPLE )
	{
		// Don't let the render bounds change when we're using blobby shadows, or else the shadow
		// will pop and stretch.
		mins = CollisionProp()->OBBMins();
		maxs = CollisionProp()->OBBMaxs();
	}
	else
	{
		GetRenderBounds( mins, maxs );

		// We do this because the normal bbox calculations don't take pose params into account, and 
		// the rotation of the guy's upper torso can place his gun a ways out of his bbox, and 
		// the shadow will get cut off as he rotates.
		//
		// Thus, we give it some padding here.
		mins -= Vector( g_flFattenAmt, g_flFattenAmt, 0 );
		maxs += Vector( g_flFattenAmt, g_flFattenAmt, 0 );
	}
}


void C_DODPlayer::GetRenderBounds( Vector& theMins, Vector& theMaxs )
{
	// TODO POSTSHIP - this hack/fix goes hand-in-hand with a fix in CalcSequenceBoundingBoxes in utils/studiomdl/simplify.cpp.
	// When we enable the fix in CalcSequenceBoundingBoxes, we can get rid of this.
	//
	// What we're doing right here is making sure it only uses the bbox for our lower-body sequences since,
	// with the current animations and the bug in CalcSequenceBoundingBoxes, are WAY bigger than they need to be.
	C_BaseAnimating::GetRenderBounds( theMins, theMaxs );
}


bool C_DODPlayer::GetShadowCastDirection( Vector *pDirection, ShadowType_t shadowType ) const
{ 
	if ( shadowType == SHADOWS_SIMPLE )
	{
		// Blobby shadows should sit directly underneath us.
		pDirection->Init( 0, 0, -1 );
		return true;
	}
	else
	{
		return BaseClass::GetShadowCastDirection( pDirection, shadowType );
	}
}

ConVar cl_muzzleflash_dlight_3rd( "cl_muzzleflash_dlight_3rd", "1" );

void C_DODPlayer::ProcessMuzzleFlashEvent()
{
	CBasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();

	bool bInToolRecordingMode = ToolsEnabled() && clienttools->IsInRecordingMode();

	// Reenable when the weapons have muzzle flash attachments in the right spot.
	if ( this == pLocalPlayer && !bInToolRecordingMode )
		return; // don't show own world muzzle flashs in for localplayer

	if ( pLocalPlayer && pLocalPlayer->GetObserverMode() == OBS_MODE_IN_EYE )
	{
		// also don't show in 1st person spec mode
		if ( pLocalPlayer->GetObserverTarget() == this )
			return;
	}

	CWeaponDODBase *pWeapon = GetActiveDODWeapon();
	if ( !pWeapon )
		return;

	int nModelIndex = pWeapon->GetModelIndex();
	int nWorldModelIndex = pWeapon->GetWorldModelIndex();
	if ( bInToolRecordingMode && nModelIndex != nWorldModelIndex )
	{
		pWeapon->SetModelIndex( nWorldModelIndex );
	}

	Vector vecOrigin;
	QAngle angAngles;

	//MATTTODO - use string names of the weapon
	const static int iMuzzleFlashAttachment = 1;
	const static int iEjectBrassAttachment = 2;

	// If we have an attachment, then stick a light on it.
	if ( cl_muzzleflash_dlight_3rd.GetBool() && pWeapon->GetAttachment( iMuzzleFlashAttachment, vecOrigin, angAngles ) )
	{
		// Muzzleflash light
		dlight_t *el = effects->CL_AllocDlight( LIGHT_INDEX_MUZZLEFLASH );
		el->origin = vecOrigin;
		el->radius = 70; 

		if ( pWeapon->GetDODWpnData().m_WeaponType == WPN_TYPE_SNIPER )
			el->radius = 150;

		el->decay = el->radius / 0.05f;
		el->die = gpGlobals->curtime + 0.05f;
		el->color.r = 255;
		el->color.g = 192;
		el->color.b = 64;
		el->color.exponent = 5;

		if ( bInToolRecordingMode )
		{
			Color clr( el->color.r, el->color.g, el->color.b );

			KeyValues *msg = new KeyValues( "TempEntity" );

			msg->SetInt( "te", TE_DYNAMIC_LIGHT );
			msg->SetString( "name", "TE_DynamicLight" );
			msg->SetFloat( "time", gpGlobals->curtime );
			msg->SetFloat( "duration", el->die );
			msg->SetFloat( "originx", el->origin.x );
			msg->SetFloat( "originy", el->origin.y );
			msg->SetFloat( "originz", el->origin.z );
			msg->SetFloat( "radius", el->radius );
			msg->SetFloat( "decay", el->decay );
			msg->SetColor( "color", clr );
			msg->SetInt( "exponent", el->color.exponent );
			msg->SetInt( "lightindex", LIGHT_INDEX_MUZZLEFLASH );

			ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg );
			msg->deleteThis();
		}
	}

	const char *pszMuzzleFlashEffect = NULL;

	switch( pWeapon->GetDODWpnData().m_iMuzzleFlashType )
	{
	case DOD_MUZZLEFLASH_PISTOL:
		pszMuzzleFlashEffect = "muzzle_pistols";
		break;
	case DOD_MUZZLEFLASH_AUTO:
		pszMuzzleFlashEffect = "muzzle_fullyautomatic";
		break;
	case DOD_MUZZLEFLASH_RIFLE:
		pszMuzzleFlashEffect = "muzzle_rifles";
		break;
	case DOD_MUZZLEFLASH_ROCKET:
		pszMuzzleFlashEffect = "muzzle_rockets";
		break;
	case DOD_MUZZLEFLASH_MG42:
		pszMuzzleFlashEffect = "muzzle_mg42";
		break;
	default:
		break;
	}

	if ( pszMuzzleFlashEffect )
	{
		DispatchParticleEffect( pszMuzzleFlashEffect, PATTACH_POINT_FOLLOW, pWeapon, 1 );
	}

	if( pWeapon->ShouldAutoEjectBrass() )
	{
		// shell eject
		if( pWeapon->GetAttachment( iEjectBrassAttachment, vecOrigin, angAngles ) )
		{
			int shellType = pWeapon->GetEjectBrassShellType();

			CEffectData data;
			data.m_nHitBox = shellType;
			data.m_vOrigin = vecOrigin;
			data.m_vAngles = angAngles;
			DispatchEffect( "DOD_EjectBrass", data );
		}
	}

	if ( bInToolRecordingMode && nModelIndex != nWorldModelIndex )
	{
		pWeapon->SetModelIndex( nModelIndex );
	}
}

void C_DODPlayer::NotifyShouldTransmit( ShouldTransmitState_t state )
{
	// Remove all effects if we go out of the PVS.
	if ( state == SHOULDTRANSMIT_END )
	{
		if( m_pFlashlightBeam != NULL )
		{
			ReleaseFlashlight();
		}
	}

	BaseClass::NotifyShouldTransmit( state );
}

void C_DODPlayer::Simulate( void )
{
	if( this != C_BasePlayer::GetLocalPlayer() )
	{
		if ( IsEffectActive( EF_DIMLIGHT ) )
		{
			QAngle eyeAngles = m_angEyeAngles;
			Vector vForward;
			AngleVectors( eyeAngles, &vForward );

			int iAttachment = LookupAttachment( "anim_attachment_RH" );

			Vector vecOrigin;
			QAngle dummy;
			GetAttachment( iAttachment, vecOrigin, dummy );

			trace_t tr;
			UTIL_TraceLine( vecOrigin, vecOrigin + (vForward * 200), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );

			if( !m_pFlashlightBeam )
			{
				BeamInfo_t beamInfo;
				beamInfo.m_nType = TE_BEAMPOINTS;
				beamInfo.m_vecStart = tr.startpos;
				beamInfo.m_vecEnd = tr.endpos;
				beamInfo.m_pszModelName = "sprites/glow01.vmt";
				beamInfo.m_pszHaloName = "sprites/glow01.vmt";
				beamInfo.m_flHaloScale = 3.0;
				beamInfo.m_flWidth = 8.0f;
				beamInfo.m_flEndWidth = 35.0f;
				beamInfo.m_flFadeLength = 300.0f;
				beamInfo.m_flAmplitude = 0;
				beamInfo.m_flBrightness = 60.0;
				beamInfo.m_flSpeed = 0.0f;
				beamInfo.m_nStartFrame = 0.0;
				beamInfo.m_flFrameRate = 0.0;
				beamInfo.m_flRed = 255.0;
				beamInfo.m_flGreen = 255.0;
				beamInfo.m_flBlue = 255.0;
				beamInfo.m_nSegments = 8;
				beamInfo.m_bRenderable = true;
				beamInfo.m_flLife = 0.5;
				beamInfo.m_nFlags = FBEAM_FOREVER | FBEAM_ONLYNOISEONCE | FBEAM_NOTILE | FBEAM_HALOBEAM;

				m_pFlashlightBeam = beams->CreateBeamPoints( beamInfo );
			}

			if( m_pFlashlightBeam )
			{
				BeamInfo_t beamInfo;
				beamInfo.m_vecStart = tr.startpos;
				beamInfo.m_vecEnd = tr.endpos;
				beamInfo.m_flRed = 255.0;
				beamInfo.m_flGreen = 255.0;
				beamInfo.m_flBlue = 255.0;

				beams->UpdateBeamInfo( m_pFlashlightBeam, beamInfo );

				dlight_t *el = effects->CL_AllocDlight( 0 );
				el->origin = tr.endpos;
				el->radius = 50; 
				el->color.r = 200;
				el->color.g = 200;
				el->color.b = 200;
				el->die = gpGlobals->curtime + 0.1;
			}
		}
		else if ( m_pFlashlightBeam )
		{
			ReleaseFlashlight();
		}
	}

	BaseClass::Simulate();
}

void C_DODPlayer::ReleaseFlashlight( void )
{
	if( m_pFlashlightBeam )
	{
		m_pFlashlightBeam->flags = 0;
		m_pFlashlightBeam->die = gpGlobals->curtime - 1;

		m_pFlashlightBeam = NULL;
	}
}

bool C_DODPlayer::SetFOV( CBaseEntity *pRequester, int FOV, float zoomRate /* = 0.0f */ )
{
	/*
	if( FOV < 30 )
	{
		// fade in
		ScreenFade_t sf;
		memset( &sf, 0, sizeof( sf ) );
		sf.a = 255;
		sf.r = 0;
		sf.g = 0;
		sf.b = 0;
		sf.duration = (unsigned short)((float)(1<<SCREENFADE_FRACBITS) * 2.5f );
		sf.fadeFlags = FFADE_IN;
		vieweffects->Fade( sf );
	}
	else
	{
		//cancel the fade if its active
		ScreenFade_t sf;
		memset( &sf, 0, sizeof( sf ) );
		sf.fadeFlags = FFADE_IN | FFADE_PURGE;
		vieweffects->Fade( sf );
	}
	*/

	return true;
}

void C_DODPlayer::CalcObserverView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov )
{
	if( GetObserverMode() == OBS_MODE_DEATHCAM )
	{
		CalcDODDeathCamView( eyeOrigin, eyeAngles, fov );
	}
	else
		BaseClass::CalcObserverView( eyeOrigin, eyeAngles, fov );
}

static Vector WALL_MIN(-WALL_OFFSET,-WALL_OFFSET,-WALL_OFFSET);
static Vector WALL_MAX(WALL_OFFSET,WALL_OFFSET,WALL_OFFSET);

void C_DODPlayer::CalcDODDeathCamView(Vector& eyeOrigin, QAngle& eyeAngles, float& fov)
{
	CBaseEntity	* killer = GetObserverTarget();

	//float interpolation = ( gpGlobals->curtime - m_flDeathTime ) / DEATH_ANIMATION_TIME;

	// Interpolate very quickly to the killer and follow
	float interpolation = ( gpGlobals->curtime - m_flDeathTime ) / 0.2f;
	interpolation = clamp( interpolation, 0.0f, 1.0f );

	m_flObserverChaseDistance += gpGlobals->frametime*48.0f;
	m_flObserverChaseDistance = clamp( m_flObserverChaseDistance, CHASE_CAM_DISTANCE_MIN, CHASE_CAM_DISTANCE_MAX );

	QAngle aForward = eyeAngles = EyeAngles();
	Vector origin = EyePosition();			

	IRagdoll *pRagdoll = GetRepresentativeRagdoll();
	if ( pRagdoll )
	{
		origin = pRagdoll->GetRagdollOrigin();
		origin.z += VEC_DEAD_VIEWHEIGHT_SCALED( this ).z; // look over ragdoll, not through
	}

	if ( killer && (killer != this) ) 
	{
		Vector vecKiller = killer->GetAbsOrigin();
		
		C_DODPlayer *player = ToDODPlayer( killer );
		if ( player && player->IsAlive() )
		{
			if ( player->m_Shared.IsProne() )
			{
				VectorAdd( vecKiller, VEC_PRONE_VIEW_SCALED( this ), vecKiller );
			}
			else if( player->GetFlags() & FL_DUCKING )
			{
				VectorAdd( vecKiller, VEC_DUCK_VIEW_SCALED( this ), vecKiller );
			}
			else
			{
				VectorAdd( vecKiller, VEC_VIEW_SCALED( this ), vecKiller );
			}
		}

		Vector vecToKiller = vecKiller - origin;
		QAngle aKiller;
		VectorAngles( vecToKiller, aKiller );
		InterpolateAngles( aForward, aKiller, eyeAngles, interpolation );
	}

	Vector vForward; AngleVectors( eyeAngles, &vForward );

	VectorNormalize( vForward );

	VectorMA( origin, -m_flObserverChaseDistance, vForward, eyeOrigin );

	trace_t trace; // clip against world
	C_BaseEntity::PushEnableAbsRecomputations( false ); // HACK don't recompute positions while doing RayTrace
	UTIL_TraceHull( origin, eyeOrigin, WALL_MIN, WALL_MAX, MASK_SOLID, this, COLLISION_GROUP_NONE, &trace );
	C_BaseEntity::PopEnableAbsRecomputations();

	if (trace.fraction < 1.0)
	{
		eyeOrigin = trace.endpos;
		m_flObserverChaseDistance = VectorLength(origin - eyeOrigin);
	}

	fov = GetFOV();
}

void C_DODPlayer::CalcChaseCamView(Vector& eyeOrigin, QAngle& eyeAngles, float& fov)
{
	C_BaseEntity *target = GetObserverTarget();

	if ( !target ) 
	{
		// just copy a save in-map position
		VectorCopy( EyePosition(), eyeOrigin );
		VectorCopy( EyeAngles(), eyeAngles );
		return;
	};

	Vector forward, viewpoint;

	// GetRenderOrigin() returns ragdoll pos if player is ragdolled
	Vector origin = target->GetRenderOrigin();

	C_DODPlayer *player = ToDODPlayer( target );

	if ( player && player->IsAlive() )
	{
		if ( player->m_Shared.IsProne() )
		{
			VectorAdd( origin, VEC_PRONE_VIEW_SCALED( this ), origin );
		}
		else if( player->GetFlags() & FL_DUCKING )
		{
			VectorAdd( origin, VEC_DUCK_VIEW_SCALED( this ), origin );
		}
		else
		{
			VectorAdd( origin, VEC_VIEW_SCALED( this ), origin );
		}
	}
	else
	{
		// assume it's the players ragdoll
		VectorAdd( origin, VEC_DEAD_VIEWHEIGHT_SCALED( this ), origin );
	}

	QAngle viewangles;

	if ( IsLocalPlayer() )
	{
		engine->GetViewAngles( viewangles );
	}
	else
	{
		viewangles = EyeAngles();
	}

	m_flObserverChaseDistance += gpGlobals->frametime*48.0f;
	m_flObserverChaseDistance = clamp( m_flObserverChaseDistance, CHASE_CAM_DISTANCE_MIN, CHASE_CAM_DISTANCE_MAX );

	AngleVectors( viewangles, &forward );

	VectorNormalize( forward );

	VectorMA(origin, -m_flObserverChaseDistance, forward, viewpoint );

	trace_t trace;
	C_BaseEntity::PushEnableAbsRecomputations( false ); // HACK don't recompute positions while doing RayTrace
	UTIL_TraceHull( origin, viewpoint, WALL_MIN, WALL_MAX, MASK_SOLID, target, COLLISION_GROUP_NONE, &trace );
	C_BaseEntity::PopEnableAbsRecomputations();

	if (trace.fraction < 1.0)
	{
		viewpoint = trace.endpos;
		m_flObserverChaseDistance = VectorLength(origin - eyeOrigin);
	}

	VectorCopy( viewangles, eyeAngles );
	VectorCopy( viewpoint, eyeOrigin );

	fov = GetFOV();
}

extern ConVar spec_freeze_traveltime;
extern ConVar spec_freeze_time;
extern ConVar cl_dod_freezecam;

//-----------------------------------------------------------------------------
// Purpose: Calculate the view for the player while he's in freeze frame observer mode
//-----------------------------------------------------------------------------
void C_DODPlayer::CalcFreezeCamView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov )
{
	C_BaseEntity *pTarget = GetObserverTarget();
	if ( !pTarget || !cl_dod_freezecam.GetBool() )
	{
		CalcDeathCamView( eyeOrigin, eyeAngles, fov );
		return;
	}

	// Zoom towards our target
	float flCurTime = (gpGlobals->curtime - m_flFreezeFrameStartTime);
	float flBlendPerc = clamp( flCurTime / spec_freeze_traveltime.GetFloat(), 0, 1 );
	flBlendPerc = SimpleSpline( flBlendPerc );

	// Find the position we would like to be lookin at
	Vector vecCamDesired = pTarget->GetObserverCamOrigin();	// Returns ragdoll origin if they're ragdolled
	VectorAdd( vecCamDesired, GetChaseCamViewOffset( pTarget ), vecCamDesired );
	Vector vecCamTarget = vecCamDesired;
	if ( !pTarget->IsAlive() )
	{
		vecCamTarget.z += pTarget->GetBaseAnimating() ? VEC_DEAD_VIEWHEIGHT_SCALED( pTarget->GetBaseAnimating() ).z : VEC_DEAD_VIEWHEIGHT.z;	// look over ragdoll, not through
	}

	// Figure out a view position in front of the target
	Vector vecEyeOnPlane = eyeOrigin;
	vecEyeOnPlane.z = vecCamTarget.z;
	Vector vecTargetPos = vecCamTarget;
	Vector vecToTarget = vecTargetPos - vecEyeOnPlane;
	VectorNormalize( vecToTarget );

	// Stop a few units away from the target, and shift up to be at the same height
	vecTargetPos = vecCamTarget - (vecToTarget * m_flFreezeFrameDistance);
	float flEyePosZ = pTarget->EyePosition().z;
	vecTargetPos.z = flEyePosZ + m_flFreezeZOffset;

	// Now trace out from the target, so that we're put in front of any walls
	trace_t trace;
	C_BaseEntity::PushEnableAbsRecomputations( false ); // HACK don't recompute positions while doing RayTrace
	UTIL_TraceLine( vecCamTarget, vecTargetPos, MASK_SOLID, pTarget, COLLISION_GROUP_NONE, &trace );
	C_BaseEntity::PopEnableAbsRecomputations();
	if (trace.fraction < 1.0 )
	{
		// The camera's going to be really close to the target. So we don't end up
		// looking at someone's chest, aim close freezecams at the target's eyes.
		vecTargetPos = trace.endpos;
		vecCamTarget = vecCamDesired;

		// To stop all close in views looking up at character's chins, move the view up.
		vecTargetPos.z += fabs(vecCamTarget.z - vecTargetPos.z) * 0.85;
		C_BaseEntity::PushEnableAbsRecomputations( false ); // HACK don't recompute positions while doing RayTrace
		UTIL_TraceLine( vecCamTarget, vecTargetPos, MASK_SOLID, pTarget, COLLISION_GROUP_NONE, &trace );
		C_BaseEntity::PopEnableAbsRecomputations();
		vecTargetPos = trace.endpos;
	}

	// Look directly at the target
	vecToTarget = vecCamTarget - vecTargetPos;
	VectorNormalize( vecToTarget );
	VectorAngles( vecToTarget, eyeAngles );

	VectorLerp( m_vecFreezeFrameStart, vecTargetPos, flBlendPerc, eyeOrigin );

	if ( flCurTime >= spec_freeze_traveltime.GetFloat() && !m_bSentFreezeFrame )
	{
		IGameEvent *pEvent = gameeventmanager->CreateEvent( "freezecam_started" );
		if ( pEvent )
		{
			gameeventmanager->FireEventClientSide( pEvent );
		}

		m_bSentFreezeFrame = true;
		view->FreezeFrame( spec_freeze_time.GetFloat() );
	}
}

const Vector& C_DODPlayer::GetRenderOrigin( void )
{
	if ( !IsAlive() && m_hRagdoll.Get() )
		return m_hRagdoll.Get()->GetRenderOrigin();

	return BaseClass::GetRenderOrigin();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
Vector C_DODPlayer::GetChaseCamViewOffset( CBaseEntity *target )
{
	C_DODPlayer *pPlayer = ToDODPlayer( target );

	if ( pPlayer && pPlayer->IsAlive() )
	{
		if ( pPlayer->m_Shared.IsProne() )
		{
			return VEC_PRONE_VIEW;
		}
	}

	return BaseClass::GetChaseCamViewOffset( target );
}

const QAngle& C_DODPlayer::EyeAngles()
{
	if ( IsLocalPlayer() && g_nKillCamMode == OBS_MODE_NONE )
	{
		return BaseClass::EyeAngles();
	}
	else
	{
		return m_angEyeAngles;
	}
}

// Cold breath defines.
#define COLDBREATH_EMIT_MIN				2.0f
#define COLDBREATH_EMIT_MAX				3.0f
#define COLDBREATH_EMIT_SCALE			0.35f
#define COLDBREATH_PARTICLE_LIFE_MIN	0.25f
#define COLDBREATH_PARTICLE_LIFE_MAX	1.0f
#define COLDBREATH_PARTICLE_LIFE_SCALE  0.75
#define COLDBREATH_PARTICLE_SIZE_MIN	1.0f
#define COLDBREATH_PARTICLE_SIZE_MAX	4.0f
#define COLDBREATH_PARTICLE_SIZE_SCALE	1.1f
#define COLDBREATH_PARTICLE_COUNT		1
#define COLDBREATH_DURATION_MIN			0.0f
#define COLDBREATH_DURATION_MAX			1.0f
#define COLDBREATH_ALPHA_MIN			0.0f
#define COLDBREATH_ALPHA_MAX			0.3f
#define COLDBREATH_ENDSCALE_MIN			0.1f
#define COLDBREATH_ENDSCALE_MAX			0.4f

static ConVar cl_coldbreath_forcestamina( "cl_coldbreath_forcestamina", "0", FCVAR_CHEAT );
static ConVar cl_coldbreath_enable( "cl_coldbreath_enable", "1" );

//-----------------------------------------------------------------------------
// Purpose: Create the emitter of cold breath particles
//-----------------------------------------------------------------------------
bool C_DODPlayer::CreateColdBreathEmitter( void )
{
	// Check to see if we are in a cold breath scenario.
	if ( !GetClientWorldEntity()->m_bColdWorld )
		return false;

	// Set cold breath to true.
	m_bColdBreathOn = true;

	// Create a cold breath emitter if one doesn't already exist.
	if ( !m_hColdBreathEmitter )
	{
		m_hColdBreathEmitter = ColdBreathEmitter::Create( "ColdBreath" );
		if ( !m_hColdBreathEmitter )
			return false;

		// Get the particle material.
		m_hColdBreathMaterial = m_hColdBreathEmitter->GetPMaterial( "sprites/frostbreath" );
		Assert( m_hColdBreathMaterial != INVALID_MATERIAL_HANDLE );

		// Cache off the head attachment for setting up cold breath.
		m_iHeadAttach = LookupAttachment( "head" );		
	}

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Destroy the cold breath emitter
//-----------------------------------------------------------------------------
void C_DODPlayer::DestroyColdBreathEmitter( void )
{
#if 0
	if ( m_hColdBreathEmitter.IsValid() )
	{
		UTIL_Remove( m_hColdBreathEmitter );
	}
#endif
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_DODPlayer::UpdateColdBreath( void )
{
	if ( !cl_coldbreath_enable.GetBool() )
		return;

	// Check to see if the cold breath emitter has been created.
	if ( !m_hColdBreathEmitter.IsValid() )
	{
		if ( !CreateColdBreathEmitter() )
			return;
	}

	// Cold breath updates.
	if ( !m_bColdBreathOn )
		return;

	// Don't emit breath if we are dead.
	if ( !IsAlive() || IsDormant() )
		return;

	// Check player speed, do emit cold breath when moving quickly.
	float flSpeed = GetAbsVelocity().Length();
	if ( flSpeed > 60.0f )
		return;

	if ( m_flColdBreathTimeStart < gpGlobals->curtime )
	{
		// Spawn cold breath particles.
		EmitColdBreathParticles();

		// Update the timer.
		if ( m_flColdBreathTimeEnd < gpGlobals->curtime )
		{
			// Check stamina and modify the time accordingly.
			if ( m_Shared.m_flStamina < LOW_STAMINA_THRESHOLD || cl_coldbreath_forcestamina.GetBool() )
			{
				m_flColdBreathTimeStart = gpGlobals->curtime + RandomFloat( COLDBREATH_EMIT_MIN * COLDBREATH_EMIT_SCALE, COLDBREATH_EMIT_MAX * COLDBREATH_EMIT_SCALE );
				float flDuration = RandomFloat( COLDBREATH_DURATION_MIN, COLDBREATH_DURATION_MAX );
				m_flColdBreathTimeEnd = m_flColdBreathTimeStart + flDuration;
			}
			else
			{
				m_flColdBreathTimeStart = gpGlobals->curtime + RandomFloat( COLDBREATH_EMIT_MIN, COLDBREATH_EMIT_MAX );
				float flDuration = RandomFloat( COLDBREATH_DURATION_MIN, COLDBREATH_DURATION_MAX );
				m_flColdBreathTimeEnd = m_flColdBreathTimeStart + flDuration;
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_DODPlayer::CalculateIKLocks( float currentTime )
{
	if (!m_pIk) 
		return;

	int targetCount = m_pIk->m_target.Count();
	if ( targetCount == 0 )
		return;

	// In TF, we might be attaching a player's view to a walking model that's using IK. If we are, it can
	// get in here during the view setup code, and it's not normally supposed to be able to access the spatial
	// partition that early in the rendering loop. So we allow access right here for that special case.
	SpatialPartitionListMask_t curSuppressed = partition->GetSuppressedLists();
	partition->SuppressLists( PARTITION_ALL_CLIENT_EDICTS, false );
	CBaseEntity::PushEnableAbsRecomputations( false );

	for (int i = 0; i < targetCount; i++)
	{
		trace_t trace;
		CIKTarget *pTarget = &m_pIk->m_target[i];

		if (!pTarget->IsActive())
			continue;

		switch( pTarget->type)
		{
		case IK_GROUND:
			{
				pTarget->SetPos( Vector( pTarget->est.pos.x, pTarget->est.pos.y, GetRenderOrigin().z ));
				pTarget->SetAngles( GetRenderAngles() );
			}
			break;

		case IK_ATTACHMENT:
			{
				C_BaseEntity *pEntity = NULL;
				float flDist = pTarget->est.radius;

				// FIXME: make entity finding sticky!
				// FIXME: what should the radius check be?
				for ( CEntitySphereQuery sphere( pTarget->est.pos, 64 ); ( pEntity = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() )
				{
					C_BaseAnimating *pAnim = pEntity->GetBaseAnimating( );
					if (!pAnim)
						continue;

					int iAttachment = pAnim->LookupAttachment( pTarget->offset.pAttachmentName );
					if (iAttachment <= 0)
						continue;

					Vector origin;
					QAngle angles;
					pAnim->GetAttachment( iAttachment, origin, angles );

					// debugoverlay->AddBoxOverlay( origin, Vector( -1, -1, -1 ), Vector( 1, 1, 1 ), QAngle( 0, 0, 0 ), 255, 0, 0, 0, 0 );

					float d = (pTarget->est.pos - origin).Length();

					if ( d >= flDist)
						continue;

					flDist = d;
					pTarget->SetPos( origin );
					pTarget->SetAngles( angles );
					// debugoverlay->AddBoxOverlay( pTarget->est.pos, Vector( -pTarget->est.radius, -pTarget->est.radius, -pTarget->est.radius ), Vector( pTarget->est.radius, pTarget->est.radius, pTarget->est.radius), QAngle( 0, 0, 0 ), 0, 255, 0, 0, 0 );
				}

				if (flDist >= pTarget->est.radius)
				{
					// debugoverlay->AddBoxOverlay( pTarget->est.pos, Vector( -pTarget->est.radius, -pTarget->est.radius, -pTarget->est.radius ), Vector( pTarget->est.radius, pTarget->est.radius, pTarget->est.radius), QAngle( 0, 0, 0 ), 0, 0, 255, 0, 0 );
					// no solution, disable ik rule
					pTarget->IKFailed( );
				}
			}
			break;
		}
	}

	CBaseEntity::PopEnableAbsRecomputations();
	partition->SuppressLists( curSuppressed, true );
}


//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_DODPlayer::EmitColdBreathParticles( void )
{
	// Get the position to emit from - look into caching this off we are doing redundant work in the case
	// of allies (see dod_headiconmanager.cpp).
	Vector vecOrigin; 
	QAngle vecAngle;
	GetAttachment( m_iHeadAttach, vecOrigin, vecAngle );
	Vector vecForward, vecRight, vecUp;
	AngleVectors( vecAngle, &vecUp, &vecForward, &vecRight );

	vecOrigin += ( vecForward * 8.0f );

	SimpleParticle *pParticle = static_cast<SimpleParticle*>( m_hColdBreathEmitter->AddParticle( sizeof( SimpleParticle ),m_hColdBreathMaterial, vecOrigin ) );
	if ( pParticle )
	{
		pParticle->m_flLifetime	= 0.0f;
		pParticle->m_flDieTime = RandomFloat( COLDBREATH_PARTICLE_LIFE_MIN, COLDBREATH_PARTICLE_LIFE_MAX );
		if ( m_Shared.m_flStamina < LOW_STAMINA_THRESHOLD || cl_coldbreath_forcestamina.GetBool() )
		{
			pParticle->m_flDieTime *= COLDBREATH_PARTICLE_LIFE_SCALE;
		}

		// Add just a little movement.
		if ( m_Shared.m_flStamina < LOW_STAMINA_THRESHOLD || cl_coldbreath_forcestamina.GetBool() )
		{
			pParticle->m_vecVelocity = ( vecForward * RandomFloat( 10.0f, 30.0f ) ) + ( vecRight * RandomFloat( -2.0f, 2.0f ) ) +
				                       ( vecUp * RandomFloat( 0.0f, 0.5f ) );
		}
		else
		{
			pParticle->m_vecVelocity = ( vecForward * RandomFloat( 10.0f, 20.0f ) ) + ( vecRight * RandomFloat( -2.0f, 2.0f ) ) +
				                       ( vecUp * RandomFloat( 0.0f, 1.5f ) );
		}
				
		pParticle->m_uchColor[0] = 200;
		pParticle->m_uchColor[1] = 200;
		pParticle->m_uchColor[2] = 210;
				
		float flParticleSize = RandomFloat( COLDBREATH_PARTICLE_SIZE_MIN, COLDBREATH_PARTICLE_SIZE_MAX );
		float flParticleScale = RandomFloat( COLDBREATH_ENDSCALE_MIN, COLDBREATH_ENDSCALE_MAX );
		if ( m_Shared.m_flStamina < LOW_STAMINA_THRESHOLD || cl_coldbreath_forcestamina.GetBool() )
		{
			pParticle->m_uchEndSize = flParticleSize * COLDBREATH_PARTICLE_SIZE_SCALE;
			flParticleScale *= COLDBREATH_PARTICLE_SIZE_SCALE;
		}
		else
		{
			pParticle->m_uchEndSize = flParticleSize;
		}
		pParticle->m_uchStartSize = ( flParticleSize * flParticleScale );
				
		float flAlpha = RandomFloat( COLDBREATH_ALPHA_MIN, COLDBREATH_ALPHA_MAX );
		pParticle->m_uchStartAlpha = flAlpha * 255; 
		pParticle->m_uchEndAlpha = 0;
				
		pParticle->m_flRoll	= RandomInt( 0, 360 );
		pParticle->m_flRollDelta = RandomFloat( 0.0f, 1.25f );
	}
}

void C_DODPlayer::ComputeWorldSpaceSurroundingBox( Vector *pVecWorldMins, Vector *pVecWorldMaxs )
{
	m_Shared.ComputeWorldSpaceSurroundingBox( pVecWorldMins, pVecWorldMaxs );
}

//-----------------------------------------------------------------------------
// Purpose: Try to steer away from any players and objects we might interpenetrate
//-----------------------------------------------------------------------------
#define DOD_AVOID_MAX_RADIUS_SQR		5184.0f			// Based on player extents and max buildable extents.
#define DOD_OO_AVOID_MAX_RADIUS_SQR		0.00019f

#define DOD_MAX_SEPARATION_FORCE		256

extern ConVar cl_forwardspeed;
extern ConVar cl_backspeed;
extern ConVar cl_sidespeed;

void C_DODPlayer::AvoidPlayers( CUserCmd *pCmd )
{
	// Don't test if the player is dead.
	if ( IsAlive() == false )
		return;

	C_Team *pTeam = ( C_Team * )GetTeam();
	if ( !pTeam )
		return;

	// Up vector.
	static Vector vecUp( 0.0f, 0.0f, 1.0f );

	Vector vecDODPlayerCenter = GetAbsOrigin();
	Vector vecDODPlayerMin = GetPlayerMins();
	Vector vecDODPlayerMax = GetPlayerMaxs();
	float flZHeight = vecDODPlayerMax.z - vecDODPlayerMin.z;
	vecDODPlayerCenter.z += 0.5f * flZHeight;
	VectorAdd( vecDODPlayerMin, vecDODPlayerCenter, vecDODPlayerMin );
	VectorAdd( vecDODPlayerMax, vecDODPlayerCenter, vecDODPlayerMax );

	// Find an intersecting player or object.
	int nAvoidPlayerCount = 0;
	C_DODPlayer *pAvoidPlayerList[MAX_PLAYERS];

	C_DODPlayer *pIntersectPlayer = NULL;
	float flAvoidRadius = 0.0f;

	Vector vecAvoidCenter, vecAvoidMin, vecAvoidMax;
	for ( int i = 0; i < pTeam->GetNumPlayers(); ++i )
	{
		C_DODPlayer *pAvoidPlayer = static_cast< C_DODPlayer * >( pTeam->GetPlayer( i ) );
		if ( pAvoidPlayer == NULL )
			continue;
		// Is the avoid player me?
		if ( pAvoidPlayer == this )
			continue;

		// Save as list to check against for objects.
		pAvoidPlayerList[nAvoidPlayerCount] = pAvoidPlayer;
		++nAvoidPlayerCount;

		// Check to see if the avoid player is dormant.
		if ( pAvoidPlayer->IsDormant() )
			continue;

		// Is the avoid player solid?
		if ( pAvoidPlayer->IsSolidFlagSet( FSOLID_NOT_SOLID ) )
			continue;

		Vector t1, t2;

		vecAvoidCenter = pAvoidPlayer->GetAbsOrigin();
		vecAvoidMin = pAvoidPlayer->GetPlayerMins();
		vecAvoidMax = pAvoidPlayer->GetPlayerMaxs();
		flZHeight = vecAvoidMax.z - vecAvoidMin.z;
		vecAvoidCenter.z += 0.5f * flZHeight;
		VectorAdd( vecAvoidMin, vecAvoidCenter, vecAvoidMin );
		VectorAdd( vecAvoidMax, vecAvoidCenter, vecAvoidMax );

		if ( IsBoxIntersectingBox( vecDODPlayerMin, vecDODPlayerMax, vecAvoidMin, vecAvoidMax ) )
		{
			// Need to avoid this player.
			if ( !pIntersectPlayer )
			{
				pIntersectPlayer = pAvoidPlayer;
				break;
			}
		}
	}

	// Anything to avoid?
	if ( !pIntersectPlayer )
	{
		return;
	}

	// Calculate the push strength and direction.
	Vector vecDelta;

	// Avoid a player - they have precedence.
	if ( pIntersectPlayer )
	{
		VectorSubtract( pIntersectPlayer->WorldSpaceCenter(), vecDODPlayerCenter, vecDelta );

		Vector vRad = pIntersectPlayer->WorldAlignMaxs() - pIntersectPlayer->WorldAlignMins();
		vRad.z = 0;

		flAvoidRadius = vRad.Length();
	}

	float flPushStrength = RemapValClamped( vecDelta.Length(), flAvoidRadius, 0, 0, DOD_MAX_SEPARATION_FORCE ); //flPushScale;

	//Msg( "PushScale = %f\n", flPushStrength );

	// Check to see if we have enough push strength to make a difference.
	if ( flPushStrength < 0.01f )
		return;

	Vector vecPush;
	if ( GetAbsVelocity().Length2DSqr() > 0.1f )
	{
		Vector vecVelocity = GetAbsVelocity();
		vecVelocity.z = 0.0f;
		CrossProduct( vecUp, vecVelocity, vecPush );
		VectorNormalize( vecPush );
	}
	else
	{
		// We are not moving, but we're still intersecting.
		QAngle angView = pCmd->viewangles;
		angView.x = 0.0f;
		AngleVectors( angView, NULL, &vecPush, NULL );
	}

	// Move away from the other player/object.
	Vector vecSeparationVelocity;
	if ( vecDelta.Dot( vecPush ) < 0 )
	{
		vecSeparationVelocity = vecPush * flPushStrength;
	}
	else
	{
		vecSeparationVelocity = vecPush * -flPushStrength;
	}

	// Don't allow the max push speed to be greater than the max player speed.
	float flMaxPlayerSpeed = MaxSpeed();
	float flCropFraction = 1.33333333f;

	if ( ( GetFlags() & FL_DUCKING ) && ( GetGroundEntity() != NULL ) )
	{	
		flMaxPlayerSpeed *= flCropFraction;
	}	

	float flMaxPlayerSpeedSqr = flMaxPlayerSpeed * flMaxPlayerSpeed;

	if ( vecSeparationVelocity.LengthSqr() > flMaxPlayerSpeedSqr )
	{
		vecSeparationVelocity.NormalizeInPlace();
		VectorScale( vecSeparationVelocity, flMaxPlayerSpeed, vecSeparationVelocity );
	}

	QAngle vAngles = pCmd->viewangles;
	vAngles.x = 0;
	Vector currentdir;
	Vector rightdir;

	AngleVectors( vAngles, &currentdir, &rightdir, NULL );

	Vector vDirection = vecSeparationVelocity;

	VectorNormalize( vDirection );

	float fwd = currentdir.Dot( vDirection );
	float rt = rightdir.Dot( vDirection );

	float forward = fwd * flPushStrength;
	float side = rt * flPushStrength;

	//Msg( "fwd: %f - rt: %f - forward: %f - side: %f\n", fwd, rt, forward, side );

	pCmd->forwardmove	+= forward;
	pCmd->sidemove		+= side;

	// Clamp the move to within legal limits, preserving direction. This is a little
	// complicated because we have different limits for forward, back, and side

	//Msg( "PRECLAMP: forwardmove=%f, sidemove=%f\n", pCmd->forwardmove, pCmd->sidemove );

	float flForwardScale = 1.0f;
	if ( pCmd->forwardmove > fabs( cl_forwardspeed.GetFloat() ) )
	{
		flForwardScale = fabs( cl_forwardspeed.GetFloat() ) / pCmd->forwardmove;
	}
	else if ( pCmd->forwardmove < -fabs( cl_backspeed.GetFloat() ) )
	{
		flForwardScale = fabs( cl_backspeed.GetFloat() ) / fabs( pCmd->forwardmove );
	}

	float flSideScale = 1.0f;
	if ( fabs( pCmd->sidemove ) > fabs( cl_sidespeed.GetFloat() ) )
	{
		flSideScale = fabs( cl_sidespeed.GetFloat() ) / fabs( pCmd->sidemove );
	}

	float flScale = MIN( flForwardScale, flSideScale );
	pCmd->forwardmove *= flScale;
	pCmd->sidemove *= flScale;

	//Msg( "Pforwardmove=%f, sidemove=%f\n", pCmd->forwardmove, pCmd->sidemove );
}


//-----------------------------------------------------------------------------
// Purpose: Returns whether this player is the nemesis of the local player
//-----------------------------------------------------------------------------
bool C_DODPlayer::IsNemesisOfLocalPlayer()
{
	C_DODPlayer *pLocalPlayer = C_DODPlayer::GetLocalDODPlayer();
	if ( pLocalPlayer )
	{
		// return whether this player is dominating the local player
		return m_Shared.IsPlayerDominated( pLocalPlayer->entindex() );
	}		
	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Returns whether we should show the nemesis icon for this player
//-----------------------------------------------------------------------------
bool C_DODPlayer::ShouldShowNemesisIcon()
{
	/*
	// we should show the nemesis effect on this player if he is the nemesis of the local player,
	// and is not dead, cloaked or disguised
	if ( IsNemesisOfLocalPlayer() && g_DODPR && g_PR->IsConnected( entindex() ) )
	{
		if ( IsAlive() )
			return true;
	}
	*/
	return false;
}

int C_DODPlayer::GetActiveAchievementAward( void )
{
	int iAward = ACHIEVEMENT_AWARDS_NONE;

	int iClassBit = m_Shared.PlayerClass() + 1;

	if ( m_iAchievementAwardsMask & (1<<ACHIEVEMENT_AWARDS_ALL_PACK_1) )
	{
		iAward = ACHIEVEMENT_AWARDS_ALL_PACK_1;
	}
	else if ( m_iAchievementAwardsMask & ( 1<<iClassBit ) )
	{
		iAward = iClassBit;
	}

	return iAward;
}

IMaterial *C_DODPlayer::GetHeadIconMaterial( void )
{
	const char *pszMaterial = "";

	int iAchievementAward = GetActiveAchievementAward();

	if ( iAchievementAward >= 0 && iAchievementAward < NUM_ACHIEVEMENT_AWARDS )
	{
		switch ( GetTeamNumber() )
		{
		case TEAM_ALLIES:
			pszMaterial = g_pszAchievementAwardMaterials_Allies[iAchievementAward];
			break;
		case TEAM_AXIS:
			pszMaterial = g_pszAchievementAwardMaterials_Axis[iAchievementAward];
			break;
		default:
			break;
		}
	}

	IMaterial *pMaterial = NULL;
	if ( pszMaterial )
	{
		pMaterial = materials->FindMaterial( pszMaterial, TEXTURE_GROUP_VGUI );
	}	

	// clear the old one if its different
	if ( m_pHeadIconMaterial != pMaterial )
	{
		if ( m_pHeadIconMaterial )
		{
			m_pHeadIconMaterial->DecrementReferenceCount();
		}

		m_pHeadIconMaterial = pMaterial;
		m_pHeadIconMaterial->IncrementReferenceCount();
	}

	return m_pHeadIconMaterial;
}