//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: TF2's player object.
//
// $NoKeywords: $
//=============================================================================//

#include "cbase.h"
#include <stdarg.h>
#include "player.h"
#include "tf_player.h"
#include "gamerules.h"
#include "trains.h"
#include "entitylist.h"
#include "menu_base.h"
#include "basecombatweapon.h"
#include "controlzone.h"
#include "tf_shareddefs.h"
#include "AmmoDef.h"
#include "techtree.h"
#include "in_buttons.h"
#include "tf_team.h"
#include "client.h"
#include "baseviewmodel.h"
#include "tf_gamerules.h"
#include "tf_obj.h"
#include "weapon_builder.h"
#include "orders.h"
#include "decals.h"
#include "tf_func_resource.h"
#include "resource_chunk.h"
#include "team_messages.h"
#include "tier0/dbg.h"
#include "tf_obj_respawn_station.h"
#include "tf_obj_resourcepump.h"
#include "tf_class_commando.h"
#include "tf_class_defender.h"
#include "tf_class_escort.h"
#include "tf_class_infiltrator.h"
#include "tf_class_medic.h"
#include "tf_class_recon.h"
#include "tf_class_sniper.h"
#include "tf_class_support.h"
#include "tf_class_sapper.h"
#include "sendproxy.h"
#include "ragdoll_shadow.h"
#include "vstdlib/random.h"
#include "engine/IEngineSound.h"
#include "bone_setup.h"
#include "weapon_combatshield.h"
#include "weapon_twohandedcontainer.h"
#include "NDebugOverlay.h"
#include "tier1/strtools.h"
#include "IEffects.h"
#include "info_act.h"
#include "ai_basehumanoid.h"
#include "tf_stats.h"
#include "iservervehicle.h"
#include "tf_vehicle_teleport_station.h"
#include "globals.h"

#define MAX_EXPLOSIVE_VELOCITY	600.0f

extern ConVar tf_knockdowntime;

extern ConVar inv_demo;

ConVar tf_autoteam( "tf_autoteam", "1", 0, "Automatically place players on the team with the least players." );
ConVar tf_destroyobjects( "tf_destroyobjects", "1", FCVAR_CHEAT, "Destroy objects when players change class or team." );

IMPLEMENT_SERVERCLASS_ST(CBaseTFPlayer, DT_BaseTFPlayer)
	SendPropDataTable(SENDINFO_DT(m_TFLocal), &REFERENCE_SEND_TABLE(DT_TFLocal), SendProxy_SendLocalDataTable),

	SendPropInt(SENDINFO(m_iPlayerClass), 4, SPROP_UNSIGNED),
	
	// Class Data Tables
	SendPropDataTable( SENDINFO_DT( m_PlayerClasses ), &REFERENCE_SEND_TABLE( DT_AllPlayerClasses ), SendProxy_SendLocalDataTable ),

	SendPropEHandle( SENDINFO( m_hSelectedMCV ) ),
	SendPropInt( SENDINFO(m_iCurrentZoneState ), 3 ),
	SendPropInt( SENDINFO(m_iMaxHealth ), 8, SPROP_UNSIGNED ),
	SendPropInt( SENDINFO(m_TFPlayerFlags), TF_PLAYER_NUMFLAGS, SPROP_UNSIGNED ),
	SendPropInt( SENDINFO( m_bUnderAttack ), 1, SPROP_UNSIGNED ),
	SendPropInt( SENDINFO( m_bIsBlocking ), 1, SPROP_UNSIGNED ),

	// Sniper - will get moved to a class data table
	SendPropVector( SENDINFO(m_vecDeployedAngles), -1, SPROP_COORD ),
	SendPropInt( SENDINFO( m_bDeployed ), 1, SPROP_UNSIGNED ),
	SendPropInt( SENDINFO( m_bDeploying ), 1, SPROP_UNSIGNED ),
	SendPropInt( SENDINFO( m_bUnDeploying ), 1, SPROP_UNSIGNED ),

	// Infiltrator - will get moved to a class data table
	SendPropFloat( SENDINFO( m_flCamouflageAmount ), 7, SPROP_ROUNDDOWN, 0.0f, 100.0f ),

	SendPropEHandle(SENDINFO(m_hSpawnPoint)),

	SendPropExclude( "DT_BaseAnimating" , "m_flPoseParameter" ),
	SendPropExclude( "DT_BaseAnimating" , "m_flPlaybackRate" ),

END_SEND_TABLE()

LINK_ENTITY_TO_CLASS( player, CBaseTFPlayer );
PRECACHE_REGISTER(player);

BEGIN_DATADESC( CBaseTFPlayer )

	DEFINE_INPUTFUNC( FIELD_VOID, "Respawn", InputRespawn ),

	// Function Pointers
	DEFINE_THINKFUNC( TFPlayerDeathThink ),

END_DATADESC()


BEGIN_PREDICTION_DATA_NO_BASE( CTFPlayerLocalData )
END_PREDICTION_DATA()

BEGIN_PREDICTION_DATA( CBaseTFPlayer )
END_PREDICTION_DATA()


bool IsSpawnPointValid( CBaseEntity *pPlayer, CBaseEntity *pSpot );
void respawn( CBaseEntity *pEdict, bool fCopyCorpse );
int TrainSpeed(int iSpeed, int iMax);
void BulletWizz( Vector vecSrc, Vector vecEndPos, edict_t *pShooter, bool isTracer );

extern float	g_flNextReinforcementTime;
extern short	g_sModelIndexFireball;
extern CBaseEntity	*g_pLastSpawn;

//-----------------------------------------------------------------------------
// Purpose: Don't do anything for now
// Input  : *pFormat - 
//			... - 
// Output : static void
//-----------------------------------------------------------------------------
void StatusPrintf( bool clear, int destination, char *pFormat, ... )
{
	return;

	/*
	va_list marker;
	char msg[8192];

	va_start(marker, pFormat);
	Q_vsnprintf(msg, sizeof( msg ), pFormat, marker);
	va_end(marker);	
	
	Msg( msg );
	*/
}

#pragma warning( disable : 4355 )

//=====================================================================
// PLAYER HANDLING
//=====================================================================
CBaseTFPlayer::CBaseTFPlayer() :
	m_PlayerClasses( this ), m_PlayerAnimState( this )
{
	// HACK because player's have pev set in baseclass constructor
	// which triggers an assert that we want to keep.
	{
		edict_t *savepev = edict();
		NetworkProp()->SetEdict( NULL );
		UseClientSideAnimation();
		NetworkProp()->SetEdict( savepev );
	}

	m_bWasMoving = false;

	m_iLastSecondsToGo = -1;
	m_TFLocal.m_nInTacticalView = 0;
	m_TFLocal.m_pPlayer = this;
	m_bSwitchingView = false;
	ClearActiveWeapon();
	
	m_iPlayerClass = TFCLASS_UNDECIDED;
	SetPlayerClass( TFCLASS_UNDECIDED );
	m_pCurrentMenu = NULL;
	m_TFPlayerFlags = 0;
	m_bDeploying = false;
	m_bDeployed = false;
	m_bUnDeploying = false;
	m_flFinishedDeploying = 0;
	SetOrder( NULL );
	
	m_nPreferredTechnology = -1;
	m_nMedicDamageBoosts = 0;

	m_hSpawnPoint = NULL;
	m_flLastTimeDamagedByEnemy = -1000;

	int i;
	for ( i = 0; i < MOMENTUM_MAXSIZE; i++ )
	{
		m_aMomentum[ i ] = 1.0f;
	}
}

void CBaseTFPlayer::UpdateOnRemove( void )
{
	if ( m_hSelectedOrder )
	{
		GetTFTeam()->RemoveOrdersToPlayer( this );
		Assert( !m_hSelectedOrder.Get() );
	}

	ClearPlayerClass();

	ClearClientRagdoll( false );

	// Chain at end to mimic destructor unwind order
	BaseClass::UpdateOnRemove();
}

CBaseTFPlayer::~CBaseTFPlayer()
{
	SetPlayerClass( (TFClass)-1 );
}

bool CBaseTFPlayer::IsHidden() const
{
	return (m_TFPlayerFlags & TF_PLAYER_HIDDEN) != 0;
}

void CBaseTFPlayer::SetHidden( bool bHidden )
{
	if ( bHidden )
		m_TFPlayerFlags |= TF_PLAYER_HIDDEN;
	else
		m_TFPlayerFlags &= ~TF_PLAYER_HIDDEN;
}

//-----------------------------------------------------------------------------
// Purpose: Called everytime the player's respawned
//-----------------------------------------------------------------------------
void CBaseTFPlayer::Spawn( void )
{
	m_bUnderAttack = false;
	m_pCurrentZone = NULL;
	ClearClientRagdoll( false );

	g_pNotify->ReportNamedEvent( this, "PlayerSpawned" );

	DeactivateMovementConstraint();

	if ( IsInAVehicle() )
	{
		LeaveVehicle();
	}

	// If the player doesn't have a spawn station set, find one
	if ( m_hSpawnPoint == NULL || !InSameTeam( m_hSpawnPoint ) )
	{
		m_hSpawnPoint = GetInitialSpawnPoint();
	}

	if ( inv_demo.GetBool() )
	{
		if ( !GetPlayerClass() )
		{
			ChangeClass( TFCLASS_MEDIC );
			m_Local.m_iHideHUD |= HIDEHUD_MISCSTATUS;
			engine->ServerCommand("r_DispEnableLOD 0\n");
		}
	}

	// Must be done before baseclass spawn, so it's correct for when we find a spawnpoint
	if ( GetPlayerClass()  )
	{
		GetPlayerClass()->SetPlayerHull();
	}

	// Use human commando model until we know our class
	SetModel( "models/player/human_commando.mdl" );

	BaseClass::Spawn();

	m_flFractionalBoost = 0.0f;

	// Create second view model ( for support/commando, etc )
	CreateViewModel( 1 );

	// Tell the PlayerClass that this player's just respawned
	if ( GetPlayerClass()  )
	{
		RemoveFlag( FL_NOTARGET );
		RemoveSolidFlags( FSOLID_NOT_SOLID );

		GetPlayerClass()->RespawnClass();
		if ( GetActiveWeapon() )
		{
			// Holster weapon immediately, to allow it to cleanup
//			GetActiveWeapon()->Holster( ); // NJS: test

			if (GetActiveWeapon()->HasAnyAmmo())
			{
				Weapon_Switch( GetActiveWeapon() );
			}
			else
			{
				SwitchToNextBestWeapon( GetActiveWeapon() );
			}
		}
		else
		{
			SwitchToNextBestWeapon( NULL );
		}

		SetPlayerModel();

		// Make sure they're not deployed
		FinishUnDeploying();

		// Remove my personal orders
		if ( GetTFTeam() )
		{
			GetTFTeam()->RemoveOrdersToPlayer( this );
		}

		RemoveAllDecals();
	}
	else
	{
		// No class? can't target this dude
		AddFlag( FL_NOTARGET );

		// Remove everything
		RemoveAllItems( false );

		// Set/unset m_bHidden instead to hide the tf player
		SetHidden( true );

		AddSolidFlags( FSOLID_NOT_SOLID );
		SetMoveType( MOVETYPE_NONE );

		SetModel( "models/player/human_commando.mdl" );

		// If they're not in a team, bring up the Team Menu
		if ( !IsInAnyTeam() )
		{
			if ( tf_autoteam.GetFloat() )
			{
				// Autoteam the player
				PlacePlayerInTeam();
				ForceRespawn();
			}
			else
			{
				// Let players choose their team
				m_pCurrentMenu = gMenus[MENU_TEAM];
			}
		}
		else // Bring up the Class Menu
		{
			m_pCurrentMenu = gMenus[MENU_CLASS];
		}

		m_MenuRefreshTime = gpGlobals->curtime;
		
		m_nPreferredTechnology = -1;
	}

	SetCantMove( false );


	m_TFLocal.m_nInTacticalView = 0;
	m_flLastTimeDamagedByEnemy = -1000;

	// Purge resource chunks
	for ( int i=0; i < m_TFLocal.m_iResourceAmmo.Count(); i++ )
		m_TFLocal.m_iResourceAmmo.Set( i, 0 );

	ResetKnockdown();
	SetGagged( false );
	SetUsingThermalVision( false );
	ClearCamouflage();
	SetIDEnt( NULL );
	m_iPowerups = 0;

	// MUST set the right player hull before placing the player somewhere.
	if ( GetPlayerClass() )
		GetPlayerClass()->SetPlayerHull();

	g_pGameRules->GetPlayerSpawnSpot( this );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::CleanupOnActStart( void )
{
	// Tell all our weapons
	for ( int i = 0; i < WeaponCount(); i++ ) 
	{
		if ( GetWeapon(i) ) 
		{
			((CBaseTFCombatWeapon*)GetWeapon(i))->CleanupOnActStart();
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::RecalculateSpeed( void )
{
	if ( GetPlayerClass()  )
	{
		GetPlayerClass()->SetMaxSpeed( GetPlayerClass()->GetMaxSpeed() );
	}
}

//-----------------------------------------------------------------------------
// Purpose: I just killed another player
//-----------------------------------------------------------------------------
void CBaseTFPlayer::KilledPlayer( CBaseTFPlayer *pVictim )
{
	TFStats()->IncrementPlayerStat( this, TF_PLAYER_STAT_KILL_COUNT, 1 );
	TFStats()->IncrementTeamStat( GetTeamNumber(), TF_TEAM_STAT_KILL_COUNT, 1 );

	// Am I in a rampage?
	if ( HasPowerup( POWERUP_RUSH ) && IsInRampage() )
	{
		// Extend my rush
		AttemptToPowerup( POWERUP_RUSH, ADRENALIN_RAMPAGE_EXTEND );

		// Let 'em know
		EmitSound( "BaseTFPlayer.BloodSportKiller" );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Called only the first time a player's placed in the map
//-----------------------------------------------------------------------------
void CBaseTFPlayer::InitialSpawn( void )
{
	BaseClass::InitialSpawn();
	SetWeaponBuilder( NULL );

	m_bFirstTeamSpawn = true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::Precache( void )
{
	//!! hack for radar
	BaseClass::Precache();

	PrecacheScriptSound( "BaseTFPlayer.BloodSportKiller" );
	PrecacheScriptSound( "Humans.Death" );
	PrecacheScriptSound( "AlienCommando.Death" );
	PrecacheScriptSound( "AlienMedic.Death" );
	PrecacheScriptSound( "AlienDefender.Death" );
	PrecacheScriptSound( "AlienEscort.Death" );
	PrecacheScriptSound( "BaseTFPlayer.StartDeploying" );
	PrecacheScriptSound( "BaseTFPlayer.StartUnDeploying" );
	PrecacheScriptSound( "BaseTFPlayer.KnockedDown" );
	PrecacheScriptSound( "BaseTFPlayer.ThermalOn" );
	PrecacheScriptSound( "BaseTFPlayer.ThermalOff" );
	PrecacheScriptSound( "BaseTFPlayer.PickupResources" );
	PrecacheScriptSound( "BaseTFPlayer.DonateResources" );

	// Class specific sounds
	PrecacheScriptSound( "Commando.BootHit" );
	PrecacheScriptSound( "Commando.BootSwing" );
	PrecacheScriptSound( "Commando.BullRushScream" );
	PrecacheScriptSound( "Commando.BullRushFlesh" );

}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::UpdateClientData( void )
{
	CTeam *pTeam = GetTeam();
	if ( pTeam )
		pTeam->UpdateClientData( this );

	BaseClass::UpdateClientData();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::ForceClientDllUpdate( void )
{
	BaseClass::ForceClientDllUpdate();

	// Force any active menu to be reset
	m_MenuRefreshTime = 0;
}

//-----------------------------------------------------------------------------
// Purpose: Forces an immediate respawn of the player
//-----------------------------------------------------------------------------
void CBaseTFPlayer::ForceRespawn( void )
{
	Spawn();
}


//-----------------------------------------------------------------------------
// Purpose: Input handler that forces a respawn of the player.
//-----------------------------------------------------------------------------
void CBaseTFPlayer::InputRespawn( inputdata_t &inputdata )
{
	ForceRespawn();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::InitHUD( void )
{
	CSingleUserRecipientFilter user( this );
	user.MakeReliable();

	// If we're in an act, tell it to update the client
	if ( g_hCurrentAct )
	{
		g_hCurrentAct->UpdateClient( this );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Player has just tried to switch to a new weapon
//-----------------------------------------------------------------------------
void CBaseTFPlayer::SelectItem( const char *pstr, int iSubType )
{
	// can't change weapon while deployed
	if ( IsPlayerLockedInPlace() || IsDeployed() || IsDeploying() )
		return;

	// Pass through to CBaseCombatWeapon code
	BaseClass::SelectItem( pstr, iSubType );
}

//-----------------------------------------------------------------------------
// Purpose: Put the player in the specified team
//-----------------------------------------------------------------------------
void CBaseTFPlayer::ChangeTeam( int iTeamNum )
{
	// If we're changing team, clear my order
	if ( iTeamNum != GetTeamNumber() )
	{
		SetOrder(NULL);
		if ( tf_destroyobjects.GetFloat() )
		{
			RemoveAllObjects( false );
		}
	}

	// Force full tech tree update
	for ( int i = 0 ; i < MAX_TECHNOLOGIES; i++ )
	{
		m_rgClientTechAvail[ i ].m_nAvailable = -1;
	}

	BaseClass::ChangeTeam( iTeamNum );

	// Now handle resources:
	//  - If it's the first spawn ever, give the player the team's currently calculated resource amount
	//  - If the player has more resources than the team's joining amount, drop his resources to that amount. Otherwise, he can keep his current.
	if ( GetGlobalTFTeam( iTeamNum ) )
	{
		float flJoiningResources = GetGlobalTFTeam( iTeamNum )->GetJoiningPlayerResources();
		if ( m_bFirstTeamSpawn )
		{
			m_bFirstTeamSpawn = false;
			SetBankResources( flJoiningResources );
		}
		else
		{
			if ( flJoiningResources < GetBankResources() )
			{
				SetBankResources( flJoiningResources );
			}
		}
	}

	// Clear the client ragdoll, when changing teams.
	ClearClientRagdoll( false );
}

//-----------------------------------------------------------------------------
// Purpose: Automatically place the player in the most appropriate team
//-----------------------------------------------------------------------------
void CBaseTFPlayer::PlacePlayerInTeam( void )
{
	CTFTeam *pTargetTeam = NULL;

	// Find the team with the least players in it
	for ( int i = 0; i < MAX_TF_TEAMS; i++ )
	{
		CTFTeam *pTeam = GetGlobalTFTeam(i);

		if ( pTargetTeam )
		{
			if ( pTeam->GetNumPlayers() < pTargetTeam->GetNumPlayers() )
				pTargetTeam = pTeam;
		}
		else
		{
			pTargetTeam = pTeam;
		}
	}

	ChangeTeam( pTargetTeam->GetTeamNumber() );
}

//-----------------------------------------------------------------------------
// Purpose: Return true if the specified class is available to this player
//-----------------------------------------------------------------------------
bool CBaseTFPlayer::IsClassAvailable( TFClass iClass )
{
	char str[128];
	Q_snprintf( str, sizeof( str ), "class_%s", GetTFClassInfo( iClass )->m_pClassName );
	return HasNamedTechnology( str );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::ChangeClass( TFClass iClass )
{
	// If they've got a playerclass, kill it
	if ( GetPlayerClass()  )
	{
		if ( tf_destroyobjects.GetFloat() )
		{
			RemoveAllObjects( false, iClass );
		}

		ClearPlayerClass();
	}

	// can't change class if we have no team
	if ( !IsInAnyTeam() )
		return;

	// Make sure client .dll can find out about it.
	SetPlayerClass( iClass );

	// Clear out current vote....
	CTFTeam *pTFTeam = GetTFTeam();
	SetPreferredTechnology( pTFTeam->m_pTechnologyTree, -1 );

	// Force a respawn if they're alive
	if ( IsAlive() )
	{
		ForceRespawn();
	}
}

//-----------------------------------------------------------------------------
// Purpose: Reset player class
//-----------------------------------------------------------------------------
void CBaseTFPlayer::ClearPlayerClass( void )
{
	// Remove all weapons & items
	if ( GetPlayerClass() )
	{
		RemoveAllItems( false );
		m_hWeaponCombatShield = NULL;
	}

	m_iPowerups = 0;
	SetPlayerClass( TFCLASS_UNDECIDED );
}

//-----------------------------------------------------------------------------
// Purpose: Set the player's model to the correct one, taking into account
//			class, gender, team, and disguise.
//-----------------------------------------------------------------------------
void CBaseTFPlayer::SetPlayerModel( void )
{
	if (!GetPlayerClass())
	{
		SetHidden( true );
		return;
	}

	string_t sModel = GetPlayerClass()->GetClassModel( GetTeamNumber() );

	// If they don't have a model, make the player invisible
	if ( !sModel )
	{
		SetHidden( true );
		return;
	}

	// Make the player visible
	SetHidden( false );

	// Set the model
	SetModel( STRING( sModel ) );

	if ( GetFlags() & FL_DUCKING ) 
		UTIL_SetSize(this, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX);
	else
		UTIL_SetSize(this, VEC_HULL_MIN, VEC_HULL_MAX);
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::PlayerRespawn( void )
{
	m_nButtons = 0;
	m_iRespawnFrames = 0;

	// don't copy a corpse if we're in deathcam.
	respawn( this, !IsObserver() );
	SetThink( NULL );
}

//-----------------------------------------------------------------------------
// Purpose: Play a sound when we die
//-----------------------------------------------------------------------------
void CBaseTFPlayer::DeathSound( const CTakeDamageInfo &info )
{
	if ( GetTeamNumber() == TEAM_HUMANS )
	{
		EmitSound( "Humans.Death" );
	}
	else if ( GetTeamNumber() == TEAM_ALIENS )
	{
		switch( PlayerClass() )
		{
		case TFCLASS_COMMANDO:
			EmitSound( "AlienCommando.Death" );
			break;

		case TFCLASS_MEDIC:
			EmitSound( "AlienMedic.Death" );
			break;

		case TFCLASS_DEFENDER:
			EmitSound( "AlienDefender.Death" );
			break;

		case TFCLASS_ESCORT:
			EmitSound( "AlienEscort.Death" );
			break;

		default:
			break;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::ItemPostFrame()
{
	// Don't process items while in a vehicle.
	if ( IsInAVehicle() )
	{
		IServerVehicle *pVehicle = GetVehicle();
		Assert( pVehicle );

		// NOTE: We *have* to do this before ItemPostFrame because ItemPostFrame
		// may dump us out of the vehicle
		int nRole = pVehicle->GetPassengerRole( this );
		bool bUsingStandardWeapons = pVehicle->IsPassengerUsingStandardWeapons( nRole );

		pVehicle->ItemPostFrame( this );

		// Fall through and check weapons, etc. if we're using them 
		if (!bUsingStandardWeapons || !IsInAVehicle())
			return;
	}

	// If we're attaching a sapper, handle player use only
	if ( m_TFLocal.m_bAttachingSapper )
	{
		PlayerUse();
		return;
	}

	BaseClass::ItemPostFrame();

	if ( GetPlayerClass()  )
	{
		GetPlayerClass()->ItemPostFrame();	// Let the player class handle it.
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::Jump( void )
{
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::PreThink(void)
{
	CheckBuffs();

	// Riding a vehicle?
	if ( IsInAVehicle() )
	{
		BaseClass::PreThink();
		return;
	}

	CheckDeployFinish();
	CheckKnockdown();
	CheckCamouflage();
	CheckSapperAttaching();

	// Update reinforcement state
	if (m_lifeState >= LIFE_DYING)
	{
		// After 3 seconds, move them to the Tactical Map
		if ( (gpGlobals->curtime - m_flTimeOfDeath) > 3.0 )
		{
			if ( m_TFLocal.m_nInTacticalView == false )
			{
				ShowTacticalView( 1 );
			}
		}

		// ROBIN: Maps will define whether or not teams reinforce
		/*
		// Aliens respawn in waves
		if ( GetTeamNumber() == TEAM_ALIENS )
		{
			int iSecondsToGo = (int)(g_flNextReinforcementTime - gpGlobals->curtime);
			if ( iSecondsToGo != m_iLastSecondsToGo && iSecondsToGo >= 1 )
			{
				m_iLastSecondsToGo = iSecondsToGo;
				ClientPrint( this, HUD_PRINTCENTER, UTIL_VarArgs("\nReinforcing in %d %s\n", iSecondsToGo, iSecondsToGo > 1 ? "seconds" : "second" ) );
			}
		}
		*/

		TFPlayerDeathThink();
	}

	// Update zone state
	if ( m_pCurrentZone )
	{
		m_iCurrentZoneState = m_pCurrentZone->GetControllingTeam();
		if ( m_iCurrentZoneState != ZONE_CONTESTED )
		{
			// Set the Zone state to the correct one
			if ( m_iCurrentZoneState == GetTeamNumber() )
				m_iCurrentZoneState = ZONE_FRIENDLY;
			else
				m_iCurrentZoneState = ZONE_ENEMY;
		}
	}
	else
	{
		m_iCurrentZoneState = 0;
	}

	BaseClass::PreThink();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::PostThink()
{
	BaseClass::PostThink();

	// Make sure we have a valid MCV id.
	CVehicleTeleportStation *pMCV = GetSelectedMCV();
	if ( !pMCV || !pMCV->IsDeployed() )
	{
		m_hSelectedMCV = CVehicleTeleportStation::GetFirstDeployedMCV( GetTeamNumber() );
	}

	// Tell the client if our damage is boosted so it can do a smurfy effect on the weapon.
	if ( GetAttackDamageScale( NULL ) == 1 )
		m_TFPlayerFlags &= ~TF_PLAYER_DAMAGE_BOOST;
	else
		m_TFPlayerFlags |= TF_PLAYER_DAMAGE_BOOST;

	m_PlayerAnimState.Update();
//	SetLocalAngles( m_PlayerAnimState.GetRenderAngles() );

	float flTimeSinceAttacked = gpGlobals->curtime - LastTimeDamagedByEnemy();
	m_bUnderAttack = ((flTimeSinceAttacked >= 0.0f) && (flTimeSinceAttacked < 1.0f));

	// TODO: This collision hull is set in the base class PostThink (so this is
	// redundant), but I don't wanna re-write the whole thing at this point.
	// We will just have to deal with a little redundancy for now.
	if ( GetPlayerClass()  )
	{
		GetPlayerClass()->SetPlayerHull();
	}

	// Menus
	MenuDisplay();

	// Player class Think
	if (GetPlayerClass())
	{
		GetPlayerClass()->ClassThink();
	}

	if ( m_bSwitchingView )
	{
		m_bSwitchingView = false;
		SetMoveType( m_TFLocal.m_nInTacticalView ? MOVETYPE_ISOMETRIC : MOVETYPE_WALK );
	}

	FollowClientRagdoll();
}

//-----------------------------------------------------------------------------
// Purpose: selects a valid point that the player can spawn at
// Output : edict_t - the point in the world to spawn at
//-----------------------------------------------------------------------------
CBaseEntity *CBaseTFPlayer::EntSelectSpawnPoint( void )
{
	// If we're in a team, ask the team for a spawnpoint
	if ( GetTeam() )
	{
		CBaseEntity *entity = NULL;
		if ( GetPlayerClass()  )
		{
			// Let individual player classes override the respawn point
			entity = GetPlayerClass()->SelectSpawnPoint();
			if ( entity )
			{
				return entity;
			}

			// Do we have a selected spawn point (from a respawn station)?
			entity = m_hSpawnPoint;
			if (entity && (entity->GetTeam() == GetTeam()))
			{
				PlayRespawnEffect( entity );
				return entity;
			}
		}

		entity = GetTeam()->SpawnPlayer( this );
		if ( entity )
			return entity;
	}

	// If we're not in a team, or the team didn't have a spawnpoint for us,
	// fall back to the basic spawnpoint code.
	return BaseClass::EntSelectSpawnPoint();
}

void CBaseTFPlayer::RemoveShieldOverlays( void )
{
	RemoveGesture( ACT_OVERLAY_SHIELD_UP );
	RemoveGesture( ACT_OVERLAY_SHIELD_DOWN );
	RemoveGesture( ACT_OVERLAY_SHIELD_UP_IDLE );
	RemoveGesture( ACT_OVERLAY_SHIELD_ATTACK );
	RemoveGesture( ACT_OVERLAY_SHIELD_KNOCKBACK );
}

static bool IsShieldOverlay( Activity activity )
{
	switch ( activity )
	{
	default:
		return false;
	case ACT_OVERLAY_SHIELD_UP:
	case ACT_OVERLAY_SHIELD_DOWN:
	case ACT_OVERLAY_SHIELD_UP_IDLE:
	case ACT_OVERLAY_SHIELD_ATTACK:
	case ACT_OVERLAY_SHIELD_KNOCKBACK:
		return true;
	}
	return false;
}

int CBaseTFPlayer::RemoveShieldOverlaysExcept( Activity activity, bool addifnotpresent /*= true */ )
{
	int skip = FindGestureLayer( activity );

	int i;
	for ( i = 0; i < CBaseAnimatingOverlay::MAX_OVERLAYS; i++ )
	{
		if ( i == skip )
			continue;

		if ( IsShieldOverlay( GetLayerActivity( i ) ) )
		{
			RemoveLayer( i, 0.0, 0.0f );
		}
	}
	
	// Add it in if it's not present already
	if ( addifnotpresent && ( skip == -1 ) )
	{
		return AddGesture( activity );
	}
	else
	{
		return skip;
	}
}	

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : activity - i/o :  can be changed to a new activity
//			overlayindex - o:  if an overlay is picked, this gets changed
// Rest of paramters are input only
//			moving - is player moving
//			ducked - is player ducking
//			overlay - animation choices for this state (either full body crouch/stand, or overlay on top of base crouch/stand )
//			crouch - 
//			normal - 
// Overlay parameters
//			autokill - if false, overlay will loop indefinitely
//			blendin - amount of time over which to blend in (0.0f for snap)
//			blendout - same but for blending out instead
//-----------------------------------------------------------------------------
void CBaseTFPlayer::PickShieldAnimation( Activity& activity, int& overlayindex, bool moving, bool ducked, 
	Activity overlay, Activity crouch, Activity normal, 
	bool autokill /*=true*/, float blendin /*=0.0f*/, float blendout /*=0.0f*/ )
{
	if ( moving )
	{
		overlayindex = RemoveShieldOverlaysExcept( overlay );
		if ( overlayindex != -1 )
		{
			if ( blendin > 0.0f )
			{
				SetLayerBlendIn( overlayindex, blendin );
			}	

			if ( blendout > 0.0f )
			{
				SetLayerBlendOut( overlayindex, blendout );
			}

			if ( !autokill )
			{
				SetLayerAutokill( overlayindex, false );
			}
		}
	}
	else
	{
		activity = ducked ? crouch : normal;
	}
}

Activity CBaseTFPlayer::ShieldTranslateActivity( Activity activity )
{
	CWeaponTwoHandedContainer *container = dynamic_cast< CWeaponTwoHandedContainer * >( GetActiveWeapon() );
	if ( !container )
		return activity;

	CWeaponCombatShield *pShield = dynamic_cast< CWeaponCombatShield * >( container->GetLeftWeapon() );
	if ( !pShield )
	{
		pShield =  dynamic_cast< CWeaponCombatShield * >( container->GetRightWeapon() );
		if ( !pShield )
		{
			return activity;
		}
	}

	float speed = GetAbsVelocity().Length2D();
	bool isMoving = speed != 0 ? true : false;
	//bool isRunning = speed > 75 ? true : false;
	bool isDucked = ( GetFlags() & FL_DUCKING ) ? true : false;

	int shieldState = pShield->GetShieldState();

	float startframe = 0.0f;

	bool movechanged = isMoving ^ m_bWasMoving;
	if ( movechanged)
	{
		// Grab frame from overlay
		if ( !isMoving )
		{
			for ( int i = 0; i < MAX_OVERLAYS; i++ )
			{
				if ( IsShieldOverlay( GetLayerActivity( i ) ) )
				{
					startframe = GetLayerCycle( i );
				}
			}

			RemoveShieldOverlays();
		}
		else
		{
			switch ( GetActivity() )
			{
			case ACT_SHIELD_UP:
			case ACT_SHIELD_DOWN:
			case ACT_SHIELD_UP_IDLE:
			case ACT_SHIELD_ATTACK:
			//case ACT_SHIELD_KNOCKBACK:
			case ACT_CROUCHING_SHIELD_UP:
			case ACT_CROUCHING_SHIELD_DOWN:
			case ACT_CROUCHING_SHIELD_UP_IDLE:
			case ACT_CROUCHING_SHIELD_ATTACK:
			//case ACT_CROUCHING_SHIELD_KNOCKBACK:
				startframe = GetCycle();
				break;
			default:
				break;
			}
		}
	}

	// Asume we should fix up animation based on move/stationary state change
	bool fixup = true;
	// Assume no overlay
	int idx = -1;

	switch ( shieldState )
	{
	default:
	case SS_DOWN:
	case SS_UNAVAILABLE:
		RemoveShieldOverlays();
		// By default, remove shield overlays and don't do fixup
		fixup = false;
		break;
	case SS_LOWERING:
		{
			PickShieldAnimation( activity, idx, isMoving, isDucked,
				ACT_OVERLAY_SHIELD_DOWN, ACT_CROUCHING_SHIELD_DOWN, ACT_SHIELD_DOWN,
				true, 0.0f, 0.2f );
		}
		break;
	case SS_RAISING:
		{
			PickShieldAnimation( activity, idx, isMoving, isDucked,
				ACT_OVERLAY_SHIELD_UP, ACT_CROUCHING_SHIELD_UP, ACT_SHIELD_UP,
				true, 0.2f, 0.0f );
		}
		break;
	case SS_UP:
		{
			PickShieldAnimation( activity, idx, isMoving, isDucked,
				ACT_OVERLAY_SHIELD_UP_IDLE, ACT_CROUCHING_SHIELD_UP_IDLE, ACT_SHIELD_UP_IDLE,
				false );
		}
		break;
	case SS_PARRYING:
		{
			PickShieldAnimation( activity, idx, isMoving, isDucked,
				ACT_OVERLAY_SHIELD_ATTACK, ACT_CROUCHING_SHIELD_ATTACK, ACT_SHIELD_ATTACK,
				true, 0.1f, 0.1f );
		}
		break;
	}

	// If started or stopped moving and still using shield, match the cycle to/from the overlay/base animation
	//  being used beforehand
	if ( movechanged && fixup )
	{
		// Fixup overlay frame
		if ( idx != -1 )
		{
			SetLayerCycle( idx, startframe );
		}
		else
		{
			// Force animation blend
			ResetSequenceInfo();

			// Match start frame
			SetCycle( startframe );
		}
	}

	// Remember previous state
	m_bWasMoving = isMoving;

	// Return translated activity
	return activity;
}

void CBaseTFPlayer::StoreCycle( void )
{
	m_flStoredCycle = GetCycle(); // !!!!!
}

float CBaseTFPlayer::RetrieveCycle( void )
{
	return m_flStoredCycle;
}

//-----------------------------------------------------------------------------
// Purpose: Certain activities have matched cycles
// Input  : newActivity - 
//			currentActivity - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CBaseTFPlayer::ShouldMatchCycles( Activity newActivity, Activity currentActivity )
{
	if ( ( newActivity == ACT_WALK || newActivity == ACT_RUN ) &&
		 ( currentActivity == ACT_WALK || currentActivity == ACT_RUN ) )
	{
		// Don't blend either
		IncrementInterpolationFrame();
		return true;
	}
	return false;
}

#define ARBITRARY_RUN_SPEED 75.0f

//-----------------------------------------------------------------------------
// Purpose: Set the activity based on an event or current state
//-----------------------------------------------------------------------------
void CBaseTFPlayer::SetAnimation( PLAYER_ANIM playerAnim )
{
	// Assume no change
	Activity idealActivity = GetActivity();
	int animDesired = GetSequence();

	float speed = GetAbsVelocity().Length2D();

	bool isMoving = ( speed != 0.0f ) ? true : false;
	bool isRunning = false;
	
	if ( GetPlayerClass()  )
	{
// FIXME: TF2 makes no distinction between walking and running for now,
//  use the run animation always
		if ( speed > 10.0f )
		{
			isRunning = true;
		}
	}
	else
	{
		if ( speed > ARBITRARY_RUN_SPEED )
		{
			isRunning = true;
		}
	}

	bool isDucked = ( GetFlags() & FL_DUCKING ) ? true : false;
	bool isStillJumping = !( GetFlags() & FL_ONGROUND ) && ( GetActivity() == ACT_HOP );

	StoreCycle();

	// Decide upon an animation activity based upon the desired Player animation
	switch ( playerAnim )
	{
	default:
	case PLAYER_RELOAD:
	case PLAYER_ATTACK1:
	case PLAYER_IDLE:
	case PLAYER_WALK:
		// Are we still jumping?
		// If so, keep playing the jump animation.
		if ( !isStillJumping )
		{
			idealActivity = ACT_WALK;

			if ( isDucked )
			{
				idealActivity = !isMoving ? ACT_CROUCHIDLE : ACT_CROUCH;
			}
			else
			{

				if ( isRunning )
				{
					idealActivity = ACT_RUN;
				}
				else
				{
					idealActivity = isMoving ? ACT_WALK : ACT_IDLE;
				}
			}

			// Allow shield to override
			idealActivity = ShieldTranslateActivity( idealActivity );
			// Allow body yaw to override for standing and turning in place
			idealActivity = m_PlayerAnimState.BodyYawTranslateActivity( idealActivity );
		}
		break;

	case PLAYER_IN_VEHICLE:
		// For now, use manned gun pose for all vehicles
		idealActivity = ACT_RIDE_MANNED_GUN;
		break;

	case PLAYER_JUMP:
		idealActivity = ACT_HOP;
		break;

	case PLAYER_DIE:
		// Uses Ragdoll now???
		idealActivity = ACT_DIESIMPLE;
		break;

	// FIXME:  Use overlays for reload, start/leave aiming, attacking
	case PLAYER_START_AIMING:
	case PLAYER_LEAVE_AIMING:
		idealActivity = ACT_WALK;
		break;
	}

	// No change requested?
	if ( ( GetActivity() == idealActivity ) && ( GetSequence() != -1 ) )
		return;

	bool useStoredCycle = ShouldMatchCycles( idealActivity, GetActivity() );

	animDesired = SelectWeightedSequence( idealActivity );

	SetActivity( idealActivity );

	// Already using the desired animation?
	if ( GetSequence() == animDesired )
		return;

	ResetSequence( animDesired );

	// Reset to first frame of desired animation or match previous animation if activities are
	//  meant to synchronize
	SetCycle( useStoredCycle ? RetrieveCycle() : 0 );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::CheatImpulseCommands( int iImpulse )
{
	switch(iImpulse)
	{
		case 101:
			if ( GetPlayerClass() )
			{
				GetPlayerClass()->ResupplyAmmo( 1.0f, RESUPPLY_ALL_FROM_STATION );
			}
			break;

		case 150:
			if ( GetTFTeam() )
				GetTFTeam()->PostMessage( TEAMMSG_REINFORCEMENTS_ARRIVED );
			break;

		default:
			BaseClass::CheatImpulseCommands(iImpulse);
			break;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::SetRespawnStation( CBaseEntity* pRespawnStation )
{
	// This can happen because the object may get killed and its index reused
	// between time the message was sent and the 
	if( !pRespawnStation || !FClassnameIs( pRespawnStation, "obj_respawn_station" ) )
		return;

	// Team could have changed (stolen object)
	if ( GetTeam() != pRespawnStation->GetTeam() )
		return;

	// If the respawn station is the same one, then unselect!
	if ( pRespawnStation != m_hSpawnPoint )
	{
		// Make sure the respawn station is a respawn station; it could be some
		m_hSpawnPoint = pRespawnStation;
	}
	else
	{
		m_hSpawnPoint = 0;
	}
}


//-----------------------------------------------------------------------------
// Purpose: Find a starting respawn station
//-----------------------------------------------------------------------------
CBaseEntity *CBaseTFPlayer::GetInitialSpawnPoint( void )
{
	if ( !GetTFTeam() )
		return NULL;

	CBaseEntity *pFirstStation = NULL;

	// Cycle through all the respawn stations on my team
	for ( int i = 0; i < GetTFTeam()->GetNumObjects(); i++ )
	{
		CBaseObject *pObject = GetTFTeam()->GetObject(i);
		if ( pObject->GetType() == OBJ_RESPAWN_STATION )
		{
			// Store off the first station we find
			if ( !pFirstStation )
			{
				pFirstStation = pObject;
			}

			// Map specified initial spawnpoint?
			if ( ((CObjectRespawnStation*)pObject)->IsInitialSpawnPoint() )
				return pObject;
		}
	}

	return pFirstStation;
}



CBaseEntity *FindEntityForward( CBasePlayer *pMe, bool fHull );
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CBaseTFPlayer::ClientCommand( const CCommand &args )
{
	if( HasClass() )
	{
		if ( GetPlayerClass()->ClientCommand( args ) )
			return true;

		const char *cmd = args[0];
		if ( FStrEq( cmd, "emp" ) )
		{
			Msg( "Self-inflicted EMP:  testing\n" );
			float flTime = 10;
			if ( args.ArgC() == 2 )
			{
				flTime = atof( args[ 1 ] );
			}

			AttemptToPowerup( POWERUP_EMP, flTime );
			return true;
		}

		if ( FStrEq( cmd, "emp_target" ) )
		{
			CBaseEntity *pEntity = FindEntityForward( this, true );
			if ( pEntity && pEntity->CanBePoweredUp() )
			{
				float flTime = 10;
				if ( args.ArgC() == 2 )
				{
					flTime = atof( args[ 1 ] );
				}
				pEntity->AttemptToPowerup( POWERUP_EMP, flTime );
			}
			return true;
		}

		if ( FStrEq( cmd, "dmg_target" ) )
		{
			CBaseEntity *pEntity = FindEntityForward( this, true );
			if ( pEntity && pEntity->m_takedamage )
			{
				float flDamage = 1;
				if ( args.ArgC() == 2 )
				{
					flDamage = atof( args[ 1 ] );
				}
				CBaseEntity *world = CBaseEntity::Instance( engine->PEntityOfEntIndex( 0 ) );
				if ( world )
				{
					pEntity->OnTakeDamage( CTakeDamageInfo( world, world, flDamage, DMG_GENERIC ) );
				}
			}
			return true;
		}
	}

	if ( !stricmp( cmd, "kd" ) )
	{
		Vector force( 0, 0, 0 );
		if ( args.ArgC() == 1 )
		{
			force.x = random->RandomFloat( 0.5, 1.0 );
			force.y = random->RandomFloat( 0.5, 1.0 );

			if ( random->RandomFloat( 0, 1 ) > 0.5 )
			{
				force.x *= -1.0f;
			}
			if ( random->RandomFloat( 0, 1 ) > 0.5 )
			{
				force.y *= -1.0f;
			}
			force.z = random->RandomFloat( 0.5, 1.0 );
		}
		else
		{
			Vector fwd;
			Vector right;
			AngleVectors( GetAbsAngles(), &fwd, &right, NULL );

			if ( !stricmp( args[ 1 ], "f" ) )
			{
				force = fwd * -1.0f;
			}
			else if ( !stricmp( args[ 1 ], "b" ) )
			{
				force = fwd;
			}
			else if ( !stricmp( args[ 1 ], "r" ) )
			{
				force = right * -1.0f;
			}			
			else if ( !stricmp( args[ 1 ], "l" ) )
			{
				force = right;
			}
			else if ( !stricmp( args[ 1 ], "fr" ) )
			{
				force = fwd * -1.0f;
				force += right * -1.0f;
			}
			else if ( !stricmp( args[ 1 ], "br" ) )
			{
				force = fwd;
				force += right * -1.0f;
			}
			else if ( !stricmp( args[ 1 ], "fl" ) )
			{
				force = fwd * -1.0f;
				force += right;
			}			
			else if ( !stricmp( args[ 1 ], "bl" ) )
			{
				force = fwd;
				force += right;
			}

			force.z = 0.8f;
			VectorNormalize( force );
		}

		KnockDownPlayer( force, 500.0f, 3.0f );
		return true;
	}

	if ( FStrEq( cmd, "veryweak" ) )
	{
		int ouch = m_iHealth - 1;

		CBaseEntity *world = CBaseEntity::Instance( engine->PEntityOfEntIndex( 0 ) );
		if ( world )
		{
			OnTakeDamage( CTakeDamageInfo( world, world, (float)ouch, DMG_GENERIC ) );
		}

		return true;
	}

	if ( FStrEq( cmd, "ragdoll" ) )
	{
		bool on = true;
		
		if ( args.ArgC() >= 2 )
		{
			on = atoi( args[ 1 ] ) ? true : false;
		}

		if ( on )
		{
			Vector force = RandomVector( -500, 500 );
			force.z = fabs( force.z );
			force.z = MIN( 200.0f, force.z );

			BecomeRagdollOnClient( force );
		}
		else
		{
			ClearClientRagdoll( true );
		}
		return true;
	}

	if ( FStrEq( cmd, "hbset" ) )
	{
		if ( args.ArgC() >= 2 )
		{
			SetHitboxSet( atoi( args[ 1 ] ) );
			Msg( "Hitboxset forced to %i %s\n", GetHitboxSet(), GetHitboxSetName() );
		}

		return true;
	}

	return BaseClass::ClientCommand( args );
}


//=========================================================
// Purpose: Override base TraceAttack
//=========================================================
void CBaseTFPlayer::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr )
{
	if ( m_takedamage )
	{
		// Prevent team damage here so blood doesn't appear
		if ( info.GetAttacker() )
		{
			// Take damage from myself
			if ( InSameTeam( info.GetAttacker() ) && info.GetAttacker() != this )
				return;
		}

		// If we hit our shield, ignore the damage
		float flDamage = info.GetDamage();
		if ( IsHittingShield( vecDir, &flDamage ))
			return;

		// Shield may have blocked some
		CTakeDamageInfo subInfo = info;
		subInfo.SetDamage( flDamage );

		SetLastHitGroup( ptr->hitgroup );

		// Hit groups aren't evaluated here, like base TraceAttack.
		// Weapons factor hit location into flDamage before it gets here
/*		//SpawnBlood( ptr->endpos - (vecDir * 5), BloodColor(), subInfo.GetDamage() );
		//TraceBleed( subInfo.GetDamage(), vecDir, ptr, subInfo.GetDamageType() );

		// Show the personal shield effect.
		// What we do here is collide the trace line with an ellipse that is slightly larger
		// than the player and put the effect there.
		
		// Translate the line so the player's (and the ellipse's) center is at the origin.
		Vector vCenter = Center();
		Vector vStart = ptr->startpos - vCenter;
		Vector vEnd = ptr->endpos - vCenter;

		// Figure out the ellipse dimensions.
		Vector vDims = (WorldAlignMaxs() - WorldAlignMins()) * 0.5f;
		Vector vEllipse = vDims * 1.5;
		
		// Squash the line we're testing so we're testing against a sphere of radius 1 at the origin.
		vStart /= vEllipse;
		vEnd /= vEllipse;

		// See where the line hits the sphere.
		Vector vLineDir = vEnd - vStart;
		float f1, f2;
		if ( IntersectInfiniteRayWithSphere( vStart, vLineDir, vec3_origin, 1, &f1, &f2 ) )
		{
			// Use the closest hit point on the sphere.
			float fMin = MIN( f1, f2 );
			Vector vPos = vStart + vLineDir * fMin;

			// Unsquash back to the ellipse's dimensions.
			vPos *= vEllipse;

			ShowPersonalShieldEffect( vPos, vecDir, subInfo.GetDamage() );
		}
*/
		
		AddMultiDamage( subInfo, this );
	}
}


//-----------------------------------------------------------------------------
// Applies a force on the player when he takes damage
//-----------------------------------------------------------------------------
void CBaseTFPlayer::ApplyDamageForce( const CTakeDamageInfo &info, int nDamageToDo )
{
	if (nDamageToDo <= 0)
		return;

	if ( (info.GetDamageType() & (DMG_ENERGYBEAM | DMG_BLAST)) == 0 )
		return;

	if ( !info.GetInflictor() || (info.GetInflictor() == this) || info.GetAttacker()->IsSolidFlagSet(FSOLID_TRIGGER) )
		return;

	// Don't blow ragdolls around
	if ( IsClientRagdoll() )
		return;

	// Don't bother with crouched players, or classes that have other rules about it
	if ( GetFlags() & FL_DUCKING )
		return;

	if (!GetPlayerClass() || !GetPlayerClass()->ShouldApplyDamageForce( info ))
		return;

	Vector vecDir;
	// If the inflictor isn't moving, use the delta between it & me. If it's moving, use it's velocity.
	Vector vecInflictorVelocity;
	info.GetInflictor()->GetVelocity( &vecInflictorVelocity, NULL );

	// Explosives never use the velocity of the inflictor
	if ( !(info.GetDamageType() & DMG_BLAST) && vecInflictorVelocity != vec3_origin )
	{
		vecDir = vecInflictorVelocity;
	}
	else
	{
		vecDir = WorldSpaceCenter( );
		vecDir -= info.GetInflictor()->WorldSpaceCenter( );
	}
	VectorNormalize( vecDir );

	float flForce = (nDamageToDo * 2) + 20;
	if (flForce > 1000.0) 
		flForce = 1000.0;

	// Escorts get knocked half as far
	if ( PlayerClass() == TFCLASS_ESCORT )
	{
		flForce *= 0.5;
	}
	
	vecDir *= flForce;

	if ( (GetMoveType() != MOVETYPE_FLY) && (GetMoveType() != MOVETYPE_FLYGRAVITY) && ((GetFlags() & FL_ONGROUND) != 0) )
	{
		// Need large x-y component to overcome walking	friction
		vecDir.x *= 3;
		vecDir.y *= 3;
	}

	Vector vecNewVelocity = GetAbsVelocity();
	vecNewVelocity += vecDir;

	Vector vecTestVel = vecNewVelocity;
	float flLen = VectorNormalize( vecTestVel );
	if (flLen > MAX_EXPLOSIVE_VELOCITY)
		VectorMultiply( vecTestVel, MAX_EXPLOSIVE_VELOCITY, vecNewVelocity );

	SetAbsVelocity( vecNewVelocity );
}


//-----------------------------------------------------------------------------
// Purpose: Deal damage to the player
//-----------------------------------------------------------------------------
int CBaseTFPlayer::OnTakeDamage( const CTakeDamageInfo &info )
{
	if ( !IsAlive() )
		return 0;

	//if ( GetFlags() & FL_GODMODE )
		//return 0;

	// Generate a global order event.
	COrderEvent_PlayerDamaged event;
	event.m_pPlayerDamaged = this;
	event.m_TakeDamageInfo = info;
	GlobalOrderEvent( &event );	

	// Don't do damage if the player's in a vehicle, in a non-damagable spot.
	if ( IsInAVehicle() && m_hVehicle.Get() )
	{
		IServerVehicle* pVehicle = m_hVehicle.Get()->GetServerVehicle();
		Assert( pVehicle );
		int nRole = pVehicle->GetPassengerRole(this);

		if( ( nRole < 0 ) 
		 || !pVehicle->IsPassengerVisible(nRole)
		 || !pVehicle->IsPassengerDamagable(nRole) )
		{
			return 0;
		}
	}

	// Check teams
	CBaseTFPlayer *pPlayer = (CBaseTFPlayer*)info.GetAttacker();
	if ( pPlayer )
	{
		// Take damage from myself
		if ( pPlayer != this )
		{
			if ( InSameTeam(pPlayer) )
			{
				return 0;
			}
			else
			{
				// Store off the last time we were damaged by an enemy so commandos can 
				// get orders to assist.
				m_flLastTimeDamagedByEnemy = gpGlobals->curtime;
			}
		}
	}

	CTakeDamageInfo subInfo = info;

	// Let the playerclass at it
	if ( GetPlayerClass()  )
	{
		subInfo.SetDamage( GetPlayerClass()->OnTakeDamage( subInfo ) );
	}

	if ( !subInfo.GetDamage() )
		return 0;

	//Msg( "Weapon did: %f\n", flDamage );

	int iDamageToDo = Ceil2Int( subInfo.GetDamage() );

	if ( !(GetFlags() & FL_GODMODE) )
	{
		// Only certain damage types knock players around
		ApplyDamageForce( info, iDamageToDo );

		m_iHealth = MAX(0, m_iHealth - iDamageToDo);
	}

	//Msg( "m_iHealth: %d\n\n", m_iHealth );

	// Dead?
	if ( m_iHealth < 1 )
	{
		Event_Killed( subInfo );
	}

	// Let the client know
	// Try and figure out where the damage is coming from
	Vector vecDamageOrigin = info.GetReportedPosition();
	// If we didn't get an origin to use, try using the attacker's origin
	if ( vecDamageOrigin == vec3_origin && info.GetAttacker() )
	{
		vecDamageOrigin = info.GetAttacker()->GetAbsOrigin();
	}

	CSingleUserRecipientFilter user( this );
	UserMessageBegin( user, "Damage" );
		WRITE_BYTE( clamp( iDamageToDo, 0, 255 ) );
		WRITE_FLOAT( vecDamageOrigin.x );	// BUG: Should be fixed point (to hud) not floats
		WRITE_FLOAT( vecDamageOrigin.y );	// BUG: However, the HUD does _not_ implement bitfield messages (yet)
		WRITE_FLOAT( vecDamageOrigin.z );	// BUG: We use WRITE_VEC3COORD for everything else
	MessageEnd();

	// Do special explosion damage effect
	if ( info.GetDamageType() & DMG_BLAST )
	{
		OnDamagedByExplosion( info );
	}

	return iDamageToDo;
}


void CBaseTFPlayer::ShowPersonalShieldEffect( 
	const Vector &vOffsetFromEnt, 
	const Vector &vIncomingDirection, 
	float flDamage )
{
	Vector vNormalized = vIncomingDirection;
	VectorNormalize( vNormalized );

	EntityMessageBegin( this );
		WRITE_BYTE( PLAYER_MSG_PERSONAL_SHIELD );
		WRITE_VEC3COORD( vOffsetFromEnt );
		WRITE_VEC3NORMAL( vNormalized );
		WRITE_SHORT( (short)flDamage );
	MessageEnd();
}


//-----------------------------------------------------------------------------
// Purpose: Player is being healed
//-----------------------------------------------------------------------------
int CBaseTFPlayer::TakeHealth( float flHealth, int bitsDamageType )
{
	if ( m_iHealth == m_iMaxHealth )
		return 0;

	// Heal the location
	float flAmountToHeal = flHealth;
	if ( flAmountToHeal > (m_iMaxHealth - m_iHealth) )
		flAmountToHeal = (m_iMaxHealth - m_iHealth);
	m_iHealth += flAmountToHeal;

	//Msg( "Health: %d\n", m_iHealth );

	return flAmountToHeal;
}


//=====================================================================
// MENU HANDLING
//=====================================================================
void CBaseTFPlayer::MenuDisplay( void )	
{
	if ( !m_pCurrentMenu )
	{
		m_MenuRefreshTime = 0;
		return;
	}

	if ( m_MenuRefreshTime > gpGlobals->curtime )
	{
		// guard against sudden clock changes
		m_MenuRefreshTime = MIN( m_MenuRefreshTime, gpGlobals->curtime + MENU_UPDATETIME );
		return;
	}

	m_MenuRefreshTime = gpGlobals->curtime + MENU_UPDATETIME;

	if ( m_pCurrentMenu )
		m_pCurrentMenu->Display( this );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CBaseTFPlayer::MenuInput( int iInput )
{
	if ( m_pCurrentMenu )
	{
		return m_pCurrentMenu->Input( this, iInput );
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::MenuReset( void )
{
	CSingleUserRecipientFilter user( this );
	user.MakeReliable();

	UserMessageBegin( user, "ShowMenu" );
		WRITE_SHORT( 0 );  
		WRITE_CHAR( 0 );		// display time (-1 means unlimited)
		WRITE_BYTE( false );	// is there more message to come? no
		WRITE_STRING( "" );
	MessageEnd();

	Q_strncpy( m_MenuStringBuffer, "" ,  sizeof(m_MenuStringBuffer) );
	m_MenuRefreshTime = m_MenuDisplayTime = 0;

	m_pCurrentMenu = NULL;
};

//-----------------------------------------------------------------------------
// Purpose: Enables/disables tactical/map view for the player
// Input  : bTactical - true == enable it
//-----------------------------------------------------------------------------
void CBaseTFPlayer::ShowTacticalView( bool bTactical )
{
	// TODO:  Decide if we are going to keep the tactical view in TF2
	if ( !inv_demo.GetBool() )
		return;

	m_bSwitchingView	= true;
	m_TFLocal.m_nInTacticalView = bTactical ? 1 : 0;
}

//-----------------------------------------------------------------------------
// returns true if we're in tactical view
//-----------------------------------------------------------------------------
bool CBaseTFPlayer::IsInTacticalView( void ) const
{
	return m_TFLocal.m_nInTacticalView;
}

int CBaseTFPlayer::UpdateTransmitState()
{
	return SetTransmitState( FL_EDICT_FULLCHECK );
}

//-----------------------------------------------------------------------------
// Purpose: Note, an entity can override the send table ( e.g., to send less data or to send minimal data for
//  objects ( prob. players ) that are not in the pvs.
// Input  : **ppSendTable - 
//			*recipient - 
//			*pvs - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
int CBaseTFPlayer::ShouldTransmit( const CCheckTransmitInfo *pInfo )
{
	// Don't transmit if we have no team or class
	if ((PlayerClass() == TFCLASS_UNDECIDED) || (GetTeamNumber() == 0))
		return FL_EDICT_DONTSEND;

	// Thermal vision in effect, if so, cull some players who are too far away
	CBaseTFPlayer *pPlayer = ( ( CBaseTFPlayer * )CBaseEntity::Instance( pInfo->m_pClientEnt ) );
	if ( pPlayer )
	{
		if ( pPlayer->IsUsingThermalVision() )
		{
			// Do a radius check, and force sending of guys nearby (so we can see them through walls)
			Vector dist = GetAbsOrigin() - pPlayer->GetAbsOrigin();
			if ( dist.Length() < THERMAL_VISION_RADIUS )
				return FL_EDICT_ALWAYS;
		}

		// If the player we might see is camouflaged and not on our team, we can preclude based
		//  on distance
		if ( IsCamouflaged() && !InSameTeam( pPlayer ) )
		{
			Vector dist = GetAbsOrigin() - pPlayer->GetAbsOrigin();
			if ( dist.Length() > CAMO_OUTER_RADIUS )
			{
				return FL_EDICT_ALWAYS;
			}
		}
	}

	// Use default pvs etc. rules
	return BaseClass::ShouldTransmit( pInfo );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CTechnologyTree *CBaseTFPlayer::GetTechTree( void )
{
	CTFTeam *pTeam = GetTFTeam();
	if ( pTeam )
		return pTeam->m_pTechnologyTree;

	return NULL;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CBaseTFPlayer::PlayerClass( void )
{
	return m_iPlayerClass;
}

CPlayerClass *CBaseTFPlayer::GetPlayerClass()
{
	return m_PlayerClasses.GetPlayerClass( PlayerClass() );
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *name - 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::SetPreferredTechnology( CTechnologyTree *pTechnologyTree, int iTechIndex )
{
	Assert( pTechnologyTree );

	// Have to be on a team to vote for tech
	CTFTeam *pTeam = GetTFTeam();
	if ( !pTeam )
		return;

	if ( iTechIndex == -1 )
	{
		m_nPreferredTechnology = -1;
	}
	else
	{
		if ( iTechIndex < 0 || iTechIndex >= pTechnologyTree->GetNumberTechnologies() )
		{
			Msg( "%s tried to set voting preference to unknown technology index : %d\n",
				GetPlayerName(), iTechIndex );
			return;
		}
		CBaseTechnology *tech = pTechnologyTree->GetTechnology( iTechIndex );
		if ( !tech )
			return;

		// Has the tech got incomplete dependancies?
		if ( tech->HasInactiveDependencies() )
			return;
		// Already have it?
		if ( tech->GetAvailable() )
			return;
		// Can't prefer a hidden tech
		if ( tech->IsHidden() )
			return;

		m_nPreferredTechnology = iTechIndex;
	}

	pTeam->RecomputePreferences();
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : int
//-----------------------------------------------------------------------------
int CBaseTFPlayer::GetPreferredTechnology( void )
{
	return m_nPreferredTechnology;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *name - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CBaseTFPlayer::HasNamedTechnology( const char *name )
{
	if ( GetTFTeam() == NULL )
		return false;

	return GetTFTeam()->HasNamedTechnology( name );
}

//-----------------------------------------------------------------------------
// Purpose: Networking is about to update this player, let it override and specify it's own pvs
// Input  : **pvs - 
//			**pas - 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::SetupVisibility( CBaseEntity *pViewEntity, unsigned char *pvs, int pvssize )
{
	// Normal PVS
	BaseClass::SetupVisibility( pViewEntity, pvs, pvssize );

	// PVS has an additional origin
	if ( m_vecAdditionalPVSOrigin != vec3_origin )
	{
		// Add an additional origin to the pvs
		engine->AddOriginToPVS( m_vecAdditionalPVSOrigin );
	}

	if ( m_vecCameraPVSOrigin != vec3_origin )
	{
		engine->AddOriginToPVS( m_vecCameraPVSOrigin );
	}
	
	// If in tactical mode, merge in pvs from all of our teammates, too
	// send all the others team info
	if ( m_TFLocal.m_nInTacticalView )
	{
		int i;
		for ( i = 1; i <= gpGlobals->maxClients; i++ )
		{
			CBaseTFPlayer *plr = ToBaseTFPlayer( UTIL_PlayerByIndex(i) );
			if ( plr && 
				( plr != this ) && 
				( plr->TeamID() == TeamID() ) )
			{
				Vector org;
				org = plr->EyePosition();

				engine->AddOriginToPVS( org );
			}
		}
	}
}

//--------------------------------------------------------------------------------------------------------------
// ORDERS
//-----------------------------------------------------------------------------
// Purpose: Assign the player to the specified order
//-----------------------------------------------------------------------------
void CBaseTFPlayer::SetOrder( COrder *pOrder )
{
	if ( m_hSelectedOrder.Get() && m_hSelectedOrder != pOrder )
	{
		m_hSelectedOrder->SetOwner( NULL );	
	}
	m_hSelectedOrder = pOrder;
}


int CBaseTFPlayer::GetNumResourceZoneOrders()
{
	return GetTFTeam()->CountOrders( COUNTORDERS_TYPE | COUNTORDERS_OWNER, ORDER_ATTACK, 0, this ) +
		GetTFTeam()->CountOrders( COUNTORDERS_TYPE | COUNTORDERS_OWNER, ORDER_DEFEND, 0, this ) +
		GetTFTeam()->CountOrders( COUNTORDERS_TYPE | COUNTORDERS_OWNER, ORDER_CAPTURE, 0, this );
}


void CBaseTFPlayer::KillResourceZoneOrders()
{
	if( GetNumResourceZoneOrders() )
		GetTFTeam()->RemoveOrdersToPlayer( this );
}


//--------------------------------------------------------------------------------------------------------------
// DEPLOYMENT
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::StartDeploying( void )
{
	if ( !GetPlayerClass()  )
		return;

	m_bDeploying = true;
	m_vecDeployedAngles = GetLocalAngles();

	// No pitch or roll, though
	m_vecDeployedAngles.SetX( 0 );
	m_vecDeployedAngles.SetZ( 0 );

	SetCantMove( true );
	m_flFinishedDeploying = gpGlobals->curtime + GetPlayerClass()->GetDeployTime();

	SetAnimation( PLAYER_START_AIMING );

	EmitSound( "BaseTFPlayer.StartDeploying" );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::StartUnDeploying( void )
{
	if ( !GetPlayerClass()  )
		return;

	m_bUnDeploying = true;
	SetCantMove( true );
	m_flFinishedDeploying = gpGlobals->curtime + GetPlayerClass()->GetDeployTime();
	SetAnimation( PLAYER_LEAVE_AIMING );

	EmitSound( "BaseTFPlayer.StartUnDeploying" );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::CheckDeployFinish( void )
{
	// Check to see if deployment has finished
	if ( m_bDeploying )
	{
		if ( gpGlobals->curtime > m_flFinishedDeploying )
		{
			FinishDeploying();
		}
		return;
	}

	// Check to see if un-deployment has finished
	if ( m_bUnDeploying )
	{
		if ( gpGlobals->curtime > m_flFinishedDeploying )
		{
			FinishUnDeploying();
		}
		return;
	}

	// Check to see if the player's trying to move while deployed
	if ( IsAlive() && m_bDeployed )
	{
		if ( m_nButtons & (IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT ) )
		{
			StartUnDeploying();
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::FinishDeploying( void )
{
	m_bDeploying = false;
	m_bDeployed = true;
	SetCantMove( true );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::FinishUnDeploying( void )
{
	m_bUnDeploying = false;
	m_bDeployed = false;
	SetCantMove( false );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CBaseTFPlayer::IsDeployed( void )
{
	return m_bDeployed;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CBaseTFPlayer::IsDeploying( void )
{
	return (m_bDeploying || m_bUnDeploying);
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CBaseTFPlayer::IsUnDeploying( void )
{
	return m_bUnDeploying;
}

void CBaseTFPlayer::OnVehicleStart()
{
	// Do any class-specific stuff
	if (GetPlayerClass())
	{
		GetPlayerClass()->OnVehicleStart();
	}

	IServerVehicle *pVehicle = GetVehicle();
	CBaseCombatWeapon *weapon = GetActiveWeapon();
	if ( pVehicle && weapon )
	{
		// Get Role for this player
		int role = pVehicle->GetPassengerRole( this );
		bool allowweapons = pVehicle->IsPassengerUsingStandardWeapons( role );
		if ( !allowweapons )
		{
			weapon->Holster();
		}
	}
}

void CBaseTFPlayer::OnVehicleEnd( Vector &playerDestPosition )
{
	// Do any class-specific stuff
	if (GetPlayerClass())
	{
		GetPlayerClass()->OnVehicleEnd();
	}

	Vector vNewPos;
	if ( !EntityPlacementTest( this, playerDestPosition, vNewPos, true) )
	{
		Warning("Can't find valid place to exit vehicle.\n");
		return;
	}

	// Move the player up a bit to be safe
	playerDestPosition = vNewPos + Vector(0,0,16);

	CBaseCombatWeapon *weapon = GetActiveWeapon();
	if ( weapon )
	{
		weapon->Deploy();
	}
}

//--------------------------------------------------------------------------------------------------------------
// Purpose:
//--------------------------------------------------------------------------------------------------------------
bool CBaseTFPlayer::CanGetInVehicle( void )
{
	// Class-specific?
	if ( GetPlayerClass() )
	{
		return GetPlayerClass()->CanGetInVehicle();
	}

	return true;
}


CVehicleTeleportStation* CBaseTFPlayer::GetSelectedMCV() const
{
	return dynamic_cast< CVehicleTeleportStation* >( m_hSelectedMCV.Get() );
}


void CBaseTFPlayer::SetSelectedMCV( CVehicleTeleportStation *pMCV )
{
	m_hSelectedMCV = pMCV;
}


//-----------------------------------------------------------------------------
// Purpose: Restore this player's ammo count to it's starting state
//-----------------------------------------------------------------------------
bool CBaseTFPlayer::ResupplyAmmo( float flPercentage, ResupplyReason_t reason )
{
	if ( !GetPlayerClass()  )
		return false;

	return GetPlayerClass()->ResupplyAmmo(flPercentage, reason);
}


//-----------------------------------------------------------------------------
// Purpose: Medic has provided this player with a health boost
//-----------------------------------------------------------------------------
void CBaseTFPlayer::TakeHealthBoost( int iHealthBoost, int iTarget, int iDuration )
{
	m_iHealth += iHealthBoost;
	m_iHealthBoostTarget = iTarget;
	m_flHealthBoostDecrement = ceil((m_iHealth - iTarget) / (float)iDuration);

	// Start the health ticking down
	m_flHealthBoostTime = gpGlobals->curtime + 1.0;

	if ( iTarget >= m_iMaxHealth )
	{
		m_bBuffHealthBoost = true;
	}
}

//-----------------------------------------------------------------------------
// Purpose: Remove health buffs
//-----------------------------------------------------------------------------
void CBaseTFPlayer::RemoveHealthBoost( void )
{
	m_bBuffHealthBoost = false;
	m_iHealthBoostTarget = 0;
	m_flHealthBoostTime = 0;

	if ( m_iHealth > m_iMaxHealth )
	{
		m_iHealth = m_iMaxHealth;
	}
}

//-----------------------------------------------------------------------------
// Purpose: Check the state of all buffs on this player
//-----------------------------------------------------------------------------
void CBaseTFPlayer::CheckBuffs( void )
{
	// Health boost?
	if ( m_bBuffHealthBoost )
	{
		// Dropped below normal max health?
		if ( m_iHealth <= m_iHealthBoostTarget )
		{
			RemoveHealthBoost();
		}
		else
		{
			if ( m_flHealthBoostTime < gpGlobals->curtime )
			{
				// Ticking down from a boost? or suffering poison damage?
				if ( m_iHealth > m_iMaxHealth )
				{
					// Drop back to normal health in 20 seconds
					m_iHealth -= m_flHealthBoostDecrement;
				}

				m_flHealthBoostTime = gpGlobals->curtime + 1.0;
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Powerup has just started
//-----------------------------------------------------------------------------
void CBaseTFPlayer::PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier )
{
	Assert( iPowerup >= 0 && iPowerup < MAX_POWERUPS );

	switch( iPowerup )
	{
	case POWERUP_BOOST:
		{
			m_hLastBoostEntity = pAttacker;

			// Power up their shield
			if ( GetCombatShield() )
			{
				GetCombatShield()->AddShieldHealth( 0.06 ); 
			}

			// Let their playerclass know
			GetPlayerClass()->PowerupStart( iPowerup, flAmount, pAttacker, pDamageModifier );
		}
		break;

	case POWERUP_EMP:
		{
			// Let the playerclass know about it
			GetPlayerClass()->PowerupStart( iPowerup, flAmount, pAttacker, pDamageModifier );
		}
		break;

	case POWERUP_RUSH:
		{
			// Speed up
			// We need to set this here so RecalculateSpeed() can check HasPowerup(POWERUP_RUSH)
			m_iPowerups |= (1 << iPowerup);
			RecalculateSpeed();
		}
		break;

	default:
		break;
	}

	BaseClass::PowerupStart( iPowerup, flAmount, pAttacker, pDamageModifier );
}

//-----------------------------------------------------------------------------
// Purpose: Powerup has just started
//-----------------------------------------------------------------------------
void CBaseTFPlayer::PowerupEnd( int iPowerup )
{
	switch( iPowerup )
	{
	case POWERUP_EMP:
		{
			GetPlayerClass()->PowerupEnd(iPowerup);
		}
		break;

	case POWERUP_RUSH:
		{
			// Slow down
			RecalculateSpeed();
		}
		break;

	default:
		break;
	}

	BaseClass::PowerupEnd( iPowerup );
}

//--------------------------------------------------------------------------------------------------------------
// OBJECTS
//-----------------------------------------------------------------------------
// Purpose: Return true if this player's allowed to build another one of the specified object
//-----------------------------------------------------------------------------
int CBaseTFPlayer::CanBuild( int iObjectType )
{
	if ( iObjectType < 0 || iObjectType >= OBJ_LAST )
		return CB_NOT_RESEARCHED;

	if ( GetPlayerClass()  )
	{
		return GetPlayerClass()->CanBuild( iObjectType );
	}

	return CB_NOT_RESEARCHED;
}

//-----------------------------------------------------------------------------
// Purpose: Get the number of objects of the specified type that this player has
//-----------------------------------------------------------------------------
int CBaseTFPlayer::GetNumObjects( int iObjectType )
{
	int iCount = 0;
	for (int i = 0; i < GetObjectCount(); i++)
	{
		if ( !GetObject(i) )
			continue;

		if ( GetObject(i)->GetType() == iObjectType )
		{
			iCount++;
		}
	}

	return iCount;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int	CBaseTFPlayer::GetObjectCount( void )
{
	return m_TFLocal.m_aObjects.Count();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CBaseObject	*CBaseTFPlayer::GetObject( int index )
{
	return m_TFLocal.m_aObjects[index].Get();
}

//-----------------------------------------------------------------------------
// Purpose: Returns true if this player is building something
//-----------------------------------------------------------------------------
bool CBaseTFPlayer::IsBuilding( void )
{
	CWeaponBuilder *pBuilder = GetWeaponBuilder();
	if ( pBuilder )
		return pBuilder->IsBuilding();

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Object built by this player has been destroyed
//-----------------------------------------------------------------------------
void CBaseTFPlayer::OwnedObjectDestroyed( CBaseObject *pObject )
{
	TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseTFPlayer::OwnedObjectDestroyed player %s object %p:%s\n", gpGlobals->curtime, 
		GetPlayerName(),
		pObject,
		pObject->GetClassname() ) );

	if ( GetPlayerClass()  )
	{
		GetPlayerClass()->OwnedObjectDestroyed( pObject );
	}

	RemoveObject( pObject );

	// Tell our builder weapon so it recalculates the state of the build icons
	CWeaponBuilder *pBuilder = GetWeaponBuilder();
	if ( pBuilder )
	{
		pBuilder->GainedNewTechnology( NULL );
	}
}


//-----------------------------------------------------------------------------
// Removes an object from the player
//-----------------------------------------------------------------------------
void CBaseTFPlayer::RemoveObject( CBaseObject *pObject )
{
	TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseTFPlayer::RemoveObject %p:%s from player %s\n", gpGlobals->curtime, 
		pObject,
		pObject->GetClassname(),
		GetPlayerName() ) );

	Assert( pObject );
	for (int i = m_TFLocal.m_aObjects.Count(); --i >= 0; )
	{
		// Also, while we're at it, remove all other bogus ones too...
		if ((!m_TFLocal.m_aObjects[i].Get()) || (m_TFLocal.m_aObjects[i] == pObject))
		{
			m_TFLocal.m_aObjects.FastRemove(i);
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pObject - 
//			*pNewOwner - 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::OwnedObjectChangeTeam( CBaseObject *pObject, CBaseTFPlayer *pNewOwner )
{
	TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseTFPlayer::OwnedObjectChangeTeam player %s object %p:%s new player %s\n", gpGlobals->curtime, 
		GetPlayerName(),
		pObject,
		pObject->GetClassname(),
		pNewOwner->GetPlayerName() ) );

	if ( pNewOwner && pNewOwner->GetPlayerClass() )
	{
		pNewOwner->GetPlayerClass()->OwnedObjectChangeFromTeam( pObject, this );
	}

	// Remove from my list of objects
	RemoveObject( pObject );

	// Add to new team
	if ( pNewOwner )
	{
		pNewOwner->AddObject( pObject );
	}

	if ( GetPlayerClass()  )
	{
		GetPlayerClass()->OwnedObjectChangeToTeam( pObject, pNewOwner );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pZone - 
// Output : int
//-----------------------------------------------------------------------------
int CBaseTFPlayer::NumPumpsOnResourceZone( CResourceZone *pZone )
{
	int ret = 0;

	for( int iObj=0; iObj < GetObjectCount(); iObj++ )
	{
		CBaseObject *pObj = GetObject(iObj);

		if( pObj->GetType() == OBJ_RESOURCEPUMP )
		{
			CObjectResourcePump *pPump = (CObjectResourcePump*)pObj;

			// Ok, this guy already has a pump here.
			if( pPump->GetResourceZone() == pZone )
				++ret;
		}
	}

	return ret;
}



//-----------------------------------------------------------------------------
// Purpose: Remove all the player's objects
//			If bForceAll is set, remove all of them immediately.
//			Otherwise, make them all deteriorate over time.
//			If iClass is passed in, don't remove any objects that can be built 
//			by that class. If bReturnResources is set, the cost of any destroyed 
//			objects will be returned to the player.
//-----------------------------------------------------------------------------
void CBaseTFPlayer::RemoveAllObjects( bool bForceAll, int iClass, bool bReturnResources )
{
	// Remove all the player's objects
	int iSize = GetObjectCount();
	for (int i = iSize-1; i >= 0; i--)
	{
		CBaseObject *obj = GetObject(i);
		Assert( obj );
		if ( !obj )
		{
			continue;
		}

		if ( !bForceAll )
		{
			if ( iClass )
			{
				// Can our new class build this object?
				if ( ClassCanBuild( iClass, obj->GetType() ) )
					continue;
			}

			// Vehicles don't deteriorate when their owner changes teams/leaves.
			// They'll deteriorate naturally if they're unused for a while.
			if ( IsObjectAVehicle(obj->GetType()) )
			{
				RemoveObject( obj );

				// Just remove it from my list
				obj->SetBuilder( NULL );
				continue;
			}
		}

		// Return the cost of the object?
		if ( bReturnResources )
		{
			GetPlayerClass()->PickupObject( obj );
		}

		// Remove or deteriorate?
		if ( bForceAll )
		{
			UTIL_Remove( obj );
		}
		else
		{
			OwnedObjectDestroyed( obj );
			obj->StartDeteriorating();
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::StopPlacement( void )
{
	// Tell our builder weapon
	CWeaponBuilder *pBuilder = GetWeaponBuilder();
	if ( pBuilder )
	{
		pBuilder->StopPlacement();
	}
}

//-----------------------------------------------------------------------------
// Purpose: Player has started building an object
//-----------------------------------------------------------------------------
int	CBaseTFPlayer::StartedBuildingObject( int iObjectType )
{
	// Tell our playerclass
	if ( GetPlayerClass()  )
		return GetPlayerClass()->StartedBuildingObject( iObjectType );

	return 0;
}

//-----------------------------------------------------------------------------
// Purpose: Player has aborted building something
//-----------------------------------------------------------------------------
void CBaseTFPlayer::StoppedBuilding( int iObjectType )
{
	// Tell our playerclass
	if ( GetPlayerClass()  )
	{
		GetPlayerClass()->StoppedBuilding( iObjectType );
	}

	// Tell our builder weapon
	CWeaponBuilder *pBuilder = GetWeaponBuilder();
	if ( pBuilder )
	{
		pBuilder->StoppedBuilding( iObjectType );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Object has been built by this player
//-----------------------------------------------------------------------------
void CBaseTFPlayer::FinishedObject( CBaseObject *pObject )
{
	AddObject( pObject );

	// Tell our playerclass
	if ( GetPlayerClass()  )
	{
		GetPlayerClass()->FinishedObject( pObject );
	}

	// Tell our builder weapon
	CWeaponBuilder *pBuilder = GetWeaponBuilder();
	if ( pBuilder )
	{
		pBuilder->FinishedObject();
	}
}

//-----------------------------------------------------------------------------
// Purpose: Add the specified object to this player's object list.
//-----------------------------------------------------------------------------
void CBaseTFPlayer::AddObject( CBaseObject *pObject )
{
	TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseTFPlayer::AddObject adding object %p:%s to player %s\n", gpGlobals->curtime, pObject, pObject->GetClassname(), GetPlayerName() ) );

	// Make a handle out of it
	CHandle<CBaseObject> hObject;
	hObject = pObject;

	// Make sure it's not in the list already
	bool alreadyInList = (m_TFLocal.m_aObjects.Find( hObject ) != -1);
	Assert( !alreadyInList );
	if ( !alreadyInList )
	{
		m_TFLocal.m_aObjects.AddToTail( hObject );
	}

	// Stop it deterioating, if it is
	pObject->StopDeteriorating();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::SetWeaponBuilder( CWeaponBuilder *pBuilder )
{
	m_hWeaponBuilder = pBuilder;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CWeaponBuilder *CBaseTFPlayer::GetWeaponBuilder( void )
{
	return m_hWeaponBuilder;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *attacker - 
//			sourceDir - 
//			duration - 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::KnockDownPlayer( const Vector& sourceDir, float magnitude, float duration )
{
	// Already knocked down
	if ( m_TFLocal.m_bKnockedDown )
		return;
	// In a vehicle
	if ( IsInAVehicle() )
		return;

	m_TFLocal.m_bKnockedDown = true;

	// Randomize it a bit
	Vector jitter( 0, 0, 0 );
	//jitter.Random( -0.1, 0.1 );

	Vector dir = sourceDir + jitter;

	VectorNormalize( dir );

	Vector force = dir * magnitude;
	ApplyAbsVelocityImpulse( force );

	VectorAngles( dir, m_TFLocal.m_vecKnockDownDir.GetForModify() );

	QAngle ang = GetAbsAngles();
	Vector forward, right;
	AngleVectors( ang, &forward, &right, NULL );

	float dotFwd = dir.Dot( forward );
	float dotRight = dir.Dot( right );

	if ( dotFwd >= 0)
	{
		// if get hit from behind, pitch down a bit
		m_TFLocal.m_vecKnockDownDir.SetX( dotFwd * 20.0f );
		// look in the direction you fell
		m_TFLocal.m_vecKnockDownDir.SetZ( dotRight * 80.0f );
	}
	else
	{
		//Invert knock yaw if hit from front, so you are looking straight up at the direction
		// the hit cam efrom
		m_TFLocal.m_vecKnockDownDir += QAngle( 0, 180, 0 );
		// Look up in the air
		m_TFLocal.m_vecKnockDownDir.SetX( fabs( dotFwd ) * -60.0f );
		// And a bit to the side the hit came from
		m_TFLocal.m_vecKnockDownDir.SetZ( dotRight * 20.0f );
	}

	m_flKnockdownEndTime = gpGlobals->curtime + duration;

	// Play some kind of knockdown sound
	EmitSound( "BaseTFPlayer.KnockedDown" );

	if ( BecomeRagdollOnClient( force ) )
	{
		// We we are using ragdoll flight, then don't change underlying player
		//  velocity
		ApplyAbsVelocityImpulse( -force );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::ResetKnockdown( void )
{
	// Don't get up if I'm dead
	if ( IsAlive() )
	{
		if ( !ClearClientRagdoll( true ) )
			return;
	}

	m_TFLocal.m_bKnockedDown = false;
	m_TFLocal.m_vecKnockDownDir.Init();
	m_flKnockdownEndTime = 0.0f;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CBaseTFPlayer::IsKnockedDown( void )
{
	return m_TFLocal.m_bKnockedDown;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::CheckKnockdown( void )
{
	if ( !m_TFLocal.m_bKnockedDown )
		return;

	if ( gpGlobals->curtime < m_flKnockdownEndTime )
		return;

	// Remove knockdown
	ResetKnockdown();
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CBaseTFPlayer::IsGagged( void )
{
	return m_bGagged;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : gag - 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::SetGagged( bool gag )
{
	m_bGagged = gag;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CBaseTFPlayer::CanSpeak( void )
{
	return !IsGagged();
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CBaseTFPlayer::IsUsingThermalVision( void )
{
	return m_TFLocal.m_bThermalVision;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::SetIDEnt( CBaseEntity *pEntity )
{
	if ( pEntity )
		m_TFLocal.m_iIDEntIndex = pEntity->entindex();
	else
		m_TFLocal.m_iIDEntIndex = 0;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : thermal - 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::SetUsingThermalVision( bool thermal )
{
	// Play sounds if we're changing
	if ( m_TFLocal.m_bThermalVision != thermal )
	{
		if ( thermal )
		{
			EmitSound( "BaseTFPlayer.ThermalOn" );
		}
		else
		{
			EmitSound( "BaseTFPlayer.ThermalOff" );
		}
	}

	m_TFLocal.m_bThermalVision = thermal;
}


//-----------------------------------------------------------------------------
// Purpose: Add the specified number of resource chunks to the player. Return true if he can carry it all.
//-----------------------------------------------------------------------------
bool CBaseTFPlayer::AddResourceChunks( int iChunks, bool bProcessed )
{
	// Am I allowed to carry any more chunks?
	int iCurrentCount = GetTotalResourceChunks();
	// Somewhat hacky
	int iIndex = GetAmmoDef()->Index("ResourceChunks");
	int iMax = GetAmmoDef()->MaxCarry( iIndex );
	if ( iCurrentCount >= iMax )
	{
		bool bSwapped = false;

		// If this is a processed chunk, see if we can swap it for an unprocessed chunk
		if ( bProcessed )
		{
			if ( m_TFLocal.m_iResourceAmmo[ NORMAL_RESOURCES ] )
			{
				// Drop this unprocessed chunk
				Vector vecVelocity = Vector( random->RandomFloat( -250,250 ), random->RandomFloat( -250,250 ), random->RandomFloat( 200,450 ) );
				CResourceChunk::Create( false, GetAbsOrigin() + Vector(0,0,32), vecVelocity );
				RemoveResourceChunks( 1, false );
				bSwapped = true;
			}
		}

		if ( !bSwapped )
			return false;
	}

	m_TFLocal.m_iResourceAmmo.Set( bProcessed, MIN( iMax, m_TFLocal.m_iResourceAmmo[ bProcessed ] + iChunks ) );
	SetAmmoCount( GetTotalResourceChunks(), iIndex );
	CPASAttenuationFilter filter( this,"BaseTFPlayer.PickupResources" );
	EmitSound( filter, entindex(),"BaseTFPlayer.PickupResources" );
	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Remove the specified number of resources chunks from the player.
//-----------------------------------------------------------------------------
void CBaseTFPlayer::RemoveResourceChunks( int iChunks, bool bProcessed )
{
	// Remove the amount
	m_TFLocal.m_iResourceAmmo.Set( bProcessed, MAX( 0, m_TFLocal.m_iResourceAmmo[ bProcessed ] - iChunks ) );
	int iIndex = GetAmmoDef()->Index("ResourceChunks");
	SetAmmoCount( GetTotalResourceChunks(), iIndex );
}

//-----------------------------------------------------------------------------
// Purpose: Get the number of resource chunks of this type the player's carrying
//-----------------------------------------------------------------------------
int CBaseTFPlayer::GetResourceChunkCount( bool bProcessed )
{
	return m_TFLocal.m_iResourceAmmo[ bProcessed ];
}

//-----------------------------------------------------------------------------
// Purpose: Get the total number of resource chunks being carried by the player
//-----------------------------------------------------------------------------
int CBaseTFPlayer::GetTotalResourceChunks( void )
{
	int iCurrentCount = 0;
	for ( int i = 0; i < RESOURCE_TYPES; i++ )
	{
		iCurrentCount += m_TFLocal.m_iResourceAmmo[i];
	}

	return iCurrentCount;
}


//-----------------------------------------------------------------------------
// Purpose: Drop some resource chunks
//-----------------------------------------------------------------------------
void CBaseTFPlayer::DropAllResourceChunks( void )
{
	Vector vecOrigin = GetAbsOrigin() + Vector(0,0,32);

	TFStats()->IncrementTeamStat( GetTeamNumber(), TF_TEAM_STAT_RESOURCE_CHUNKS_DROPPED, resource_chunk_value.GetFloat() );

	// Drop a resource chunk
	Vector vecVelocity = Vector( random->RandomFloat( -250,250 ), random->RandomFloat( -250,250 ), random->RandomFloat( 200,450 ) );
	CResourceChunk *pChunk = CResourceChunk::Create( FALSE, vecOrigin, vecVelocity );
	pChunk->ChangeTeam( GetTeamNumber() );
}


//------------------------------------------------------------------------------------------------------------------
// RESOURCE BANK
//-----------------------------------------------------------------------------
// Purpose: Get the amount of a resource in this player's bank
//-----------------------------------------------------------------------------
int CBaseTFPlayer::GetBankResources( void )
{
	return m_TFLocal.ResourceCount();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::SetBankResources( int iAmount )
{
	int nOldAmount = m_TFLocal.ResourceCount();

	TFStats()->IncrementPlayerStat( this, TF_PLAYER_STAT_RESOURCES_ACQUIRED, iAmount - nOldAmount );

	m_TFLocal.SetResources( iAmount );

	// Tell the player's builder weapon to update
	CWeaponBuilder *pBuilder = GetWeaponBuilder();
	if ( pBuilder )
	{
		pBuilder->GainedNewTechnology( NULL );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Add resources to this player's Bank
//-----------------------------------------------------------------------------
void CBaseTFPlayer::AddBankResources( int iAmount )
{
	m_TFLocal.AddResources( iAmount );

	// Tell the player's builder weapon to update
	CWeaponBuilder *pBuilder = GetWeaponBuilder();
	if ( pBuilder )
	{
		pBuilder->GainedNewTechnology( NULL );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Remove resources to this player's Bank
//-----------------------------------------------------------------------------
void CBaseTFPlayer::RemoveBankResources( int iAmount, bool bSpent )
{
	m_TFLocal.RemoveResources( iAmount );

	// Tell the player's builder weapon to update
	CWeaponBuilder *pBuilder = GetWeaponBuilder();
	if ( pBuilder )
	{
		pBuilder->GainedNewTechnology( NULL );
	}

	if (bSpent)
	{
		TFStats()->IncrementPlayerStat( this, TF_PLAYER_STAT_RESOURCES_SPENT, iAmount );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CBaseTFPlayer::IsCamouflaged( void )
{
	return ( m_flCamouflageAmount > 0.0f ) ? true : false;
}

//-----------------------------------------------------------------------------
// Purpose: Change state over time
//-----------------------------------------------------------------------------
void CBaseTFPlayer::CheckCamouflage( void )
{
	if ( m_flCamouflageAmount == m_flGoalCamouflageAmount )
		 return;

	float remaining = m_flGoalCamouflageAmount - m_flCamouflageAmount;
	float maxstep = m_flGoalCamouflageChangeRate * gpGlobals->frametime;

	if ( remaining > 0.0f )
	{
		m_flCamouflageAmount += MIN( remaining, maxstep );
	}
	else
	{
		remaining = -remaining;
		m_flCamouflageAmount -= MIN( remaining, maxstep );
	}

	m_flCamouflageAmount = MAX( 0.0f, m_flCamouflageAmount );
	m_flCamouflageAmount = MIN( 100.0f, m_flCamouflageAmount );
}

//-----------------------------------------------------------------------------
// Purpose: Goal % and rate in percent/second to achieve the goal
// Input  : percentage - 
//			changerate - 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::SetCamouflaged( int percentage, float changerate )
{
	m_flGoalCamouflageAmount = (float)percentage;
	m_flGoalCamouflageChangeRate = changerate;
}

//-----------------------------------------------------------------------------
// Purpose: Remove the player's camo
//-----------------------------------------------------------------------------
void CBaseTFPlayer::ClearCamouflage( void )
{
	SetCamouflaged( 0, 1000 );

	// Tell the playerclass
	if ( GetPlayerClass()  )
	{
		GetPlayerClass()->ClearCamouflage();
	}
}


//-----------------------------------------------------------------------------
// Purpose: Confirm powerup durations
//-----------------------------------------------------------------------------
float CBaseTFPlayer::PowerupDuration( int iPowerup, float flTime )
{
	// Medics are never EMPed for long
	if ( PlayerClass() == TFCLASS_MEDIC && iPowerup == POWERUP_EMP )
		return 0.2;

	return BaseClass::PowerupDuration( iPowerup, flTime );
}

//-----------------------------------------------------------------------------
// Purpose: Return the player's anim speed multiplier. Used for speeding up viewmodels while rushed.
//-----------------------------------------------------------------------------
float CBaseTFPlayer::GetDefaultAnimSpeed( void )
{
	if ( HasPowerup( POWERUP_RUSH ) )
		return ADRENALIN_ANIM_SPEED;

	// Weapons may modify animation times
	if ( GetActiveWeapon() )
		return GetActiveWeapon()->GetDefaultAnimSpeed();

	return 1.0;
}


//-----------------------------------------------------------------------------
// Donate resources to a teammate
//-----------------------------------------------------------------------------
void CBaseTFPlayer::DonateResources( CBaseTFPlayer *pTarget, int pCount )
{
	Assert( pTarget );

	int nTotalCountDonated = 0;
	int nDonationCount = GetBankResources();
	if ( pCount < nDonationCount )
		nDonationCount = pCount;

	if (nDonationCount)
	{
		RemoveBankResources( nDonationCount, false );
		pTarget->AddBankResources( nDonationCount );
		nTotalCountDonated += nDonationCount;
	}

	if (nTotalCountDonated > 0)
	{
		char buf[1024];
		Q_snprintf( buf, sizeof( buf ), "%s has donated %d resources to you\n",
			GetPlayerName(), nTotalCountDonated );
		ClientPrint( pTarget, HUD_PRINTCENTER, buf );

		CSingleUserRecipientFilter filter( this );
		EmitSound( filter, entindex(), "BaseTFPlayer.DonateResources" );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Infilitrator's can +use a corpse to consume it
//-----------------------------------------------------------------------------
void CBaseTFPlayer::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
	if ( pActivator->IsPlayer() )
	{
		CBaseTFPlayer *pPlayer = static_cast<CBaseTFPlayer*>(pActivator);

		if ( InSameTeam( pActivator ))
		{
			// Resource donation
			pPlayer->DonateResources( this, 25 );
		}
	}

	BaseClass::Use( pActivator, pCaller, useType, value );
}


//-----------------------------------------------------------------------------
// The player's usable...
//-----------------------------------------------------------------------------
int CBaseTFPlayer::ObjectCaps( void ) 
{ 
	return ( (BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_IMPULSE_USE ); 
}


//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CBaseTFPlayer::CantMove( void )
{
	return m_bCantMove;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::SetCantMove( bool bCantMove )
{
	m_bCantMove = bCantMove;
	RecalculateSpeed();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::ResetViewOffset( void )
{
	if ( GetPlayerClass()  )
	{
		GetPlayerClass()->ResetViewOffset();
	}
	else
	{
		SetViewOffset( VEC_VIEW_SCALED( this ) );
	}
}

//-----------------------------------------------------------------------------
// Purpose: If ragdolling, move the player along the path that the ragdoll takes
//-----------------------------------------------------------------------------
void CBaseTFPlayer::FollowClientRagdoll( void )
{
	if (( m_hRagdollShadow == NULL ) || ( GetPlayerClass() == NULL ))
		return;

	Vector vecMin, vecMax;
	GetPlayerClass()->GetPlayerHull( ( ( GetFlags() & FL_DUCKING ) == 1 ), vecMin, vecMax );

	// Follow shadow object
	trace_t tr;

	UTIL_TraceHull( 
		m_hRagdollShadow->GetAbsOrigin() + Vector(0,0,18), 
		m_hRagdollShadow->GetAbsOrigin(), 
		vecMin,
		vecMax,
		MASK_PLAYERSOLID, 
		m_hRagdollShadow, 
		COLLISION_GROUP_NONE, 
		&tr );

	// Only move if we can find a valid spot under where shadow rolled
	if ( !tr.allsolid )
	{
		UTIL_SetOrigin( this, tr.endpos );
		VectorCopy( tr.endpos, m_vecLastGoodRagdollPos );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Stop being a ragdoll
// Input  : moveplayertofinalspot - 
// Output : return whether or not the ragdoll was cleared
//-----------------------------------------------------------------------------
bool CBaseTFPlayer::ClearClientRagdoll( bool moveplayertofinalspot )
{
	if ( m_hRagdollShadow )
	{
		if ( GetContainingEntity( edict() ) )
		{
			if ( moveplayertofinalspot )
			{
				// Move player to resting spot of shadow object
				FollowClientRagdoll();

				// Check for a valid standing position.  If an entity is blocking impart some
				// velocity to them and check again.
				trace_t trace;
				if ( CheckRagdollToStand( trace ) )
				{
					// Switch back to normal movement and kill off ragdoll bone setup on client
					SetMoveType( MOVETYPE_WALK );
					m_nRenderFX = kRenderFxNone;
					//RemoveSolidFlags( FSOLID_NOTSOLID );
					Assert( GetPlayerClass() != NULL );
					Vector vecMin, vecMax;
					GetPlayerClass()->GetPlayerHull( ( ( GetFlags() & FL_DUCKING ) == 1 ), vecMin, vecMax );
					UTIL_SetSize( this, vecMin, vecMax );
				}
				else
				{
					CBaseEntity *pEntity = trace.m_pEnt;
					if ( pEntity != GetContainingEntity( INDEXENT( 0 ) ) )
					{
						// Check for a physics object and apply force!
						IPhysicsObject *pPhysObject = pEntity->VPhysicsGetObject();
						if ( pPhysObject )
						{
							Vector vecDirection( random->RandomFloat( 0.0f, 1.0f ),
								random->RandomFloat( 0.0f, 1.0f ),
								random->RandomFloat( 0.0f, 1.0f ) );
							vecDirection *= 40000.0f;
							pPhysObject->ApplyForceCenter( vecDirection );
						}
						
						return false;
					}
					else
					{
						UTIL_SetOrigin( this, Vector( m_vecLastGoodRagdollPos.x, m_vecLastGoodRagdollPos.y, m_vecLastGoodRagdollPos.z + 18.0f ) );
						return false;
					}
				}
			}
		}
		// Kill the shadow object
		UTIL_Remove( m_hRagdollShadow );
		m_hRagdollShadow = NULL;
	}

	return true;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CBaseTFPlayer::CheckRagdollToStand( trace_t &trace )
{
	Assert( GetPlayerClass() != NULL );
	Vector vecMin, vecMax;
	GetPlayerClass()->GetPlayerHull( ( ( GetFlags() & FL_DUCKING ) == 1 ), vecMin, vecMax );

	// Write this better -- this is just a test to get things started.
	UTIL_TraceHull( 
		m_vecLastGoodRagdollPos + Vector( 0, 0, 18 ),
		m_vecLastGoodRagdollPos,
		vecMin, 
		vecMax, 
		MASK_PLAYERSOLID, 
		m_hRagdollShadow, 
		COLLISION_GROUP_NONE, 
		&trace );

	if ( !trace.allsolid )
		return true;

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Start being a ragdoll, creates client ragdoll object and server
//  physics shadow object
// Input  : &force - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CBaseTFPlayer::BecomeRagdollOnClient( const Vector &force )
{
	// Defender doesn't support it yet
	if ( PlayerClass() == TFCLASS_INFILTRATOR )
		return false;

	// Initialize the good ragdoll position.
	VectorCopy( GetAbsOrigin(), m_vecLastGoodRagdollPos );

	bool bret = BaseClass::BecomeRagdollOnClient( force );

	// ROBIN: Disabled ragdoll shadows for now.
	//		  We'll re-enable them if we need to know the end position again
	//		  If we re-enable them, we need to fix the ragdoll shadow not having the correct mass
	return bret;

	AddSolidFlags( FSOLID_NOT_SOLID );

	// Clear any old shadow object ( should never occur )
	ClearClientRagdoll( false );

	// Create new shadow object
	m_hRagdollShadow = CRagdollShadow::Create( this, force );

	return bret;
}

//=========================================================
// AddGesture - add a gesture into the animation queue
//=========================================================
int CBaseTFPlayer::AddGesture( Activity activity, bool autokill /*= true*/ )
{
	int layer = BaseClass::AddGesture( activity, autokill );
	SetLayerBlendIn( layer, 0.0 );
	SetLayerBlendOut( layer, 0.0 );
	return layer;
}

//-----------------------------------------------------------------------------
// Purpose: Class specific touch functionality!
//-----------------------------------------------------------------------------
void CBaseTFPlayer::ClassTouch( CBaseEntity *pTouched )
{
	if ( m_pfnClassTouch && HasClass() )
	{
		(GetPlayerClass()->*m_pfnClassTouch)( pTouched );
	}
}

const char* CBaseTFPlayer::GetClassModelString( int iClass, int iTeam )
{
	return m_PlayerClasses.GetPlayerClass( iClass )->GetClassModelString( iTeam );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : bRampage - 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::SetRampage( bool bRampage )
{ 
	m_bRampage = bRampage; 
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CBaseTFPlayer::IsInRampage( void ) 
{ 
	return m_bRampage; 
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::SetPlayerClass( TFClass iClass )
{
	if ( m_iPlayerClass != iClass )
	{
		m_Timer.End();

		if ( m_iPlayerClass >= 0 && m_iPlayerClass < TFCLASS_CLASS_COUNT )
		{
			void AddPlayerClassTime( int classnum, float seconds );

			AddPlayerClassTime( m_iPlayerClass, m_Timer.GetDuration().GetSeconds() );
		}
	}

	if ( m_iPlayerClass >= 0 )
	{
		if ( GetPlayerClass() )
		{
			GetPlayerClass()->ClassDeactivate();
		}
	}

	m_iPlayerClass = iClass;

	if ( m_iPlayerClass >= 0 )
	{
		SetPlayerModel();

		m_Timer.Start();
		
		if ( GetPlayerClass() )
		{
			GetPlayerClass()->ClassActivate();
			// Setup the class on initial spawn
			GetPlayerClass()->CreateClass();
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CBaseTFPlayer::ClassCostAdjustment( ResupplyBuyType_t nType )
{
	if ( m_iPlayerClass >= 0 )
	{
		return GetPlayerClass()->ClassCostAdjustment( nType );
	}

	return 0;
}

//=============================================================================
//
// Player Physics Shadow Code
//
class CPhysicsTFPlayerCallback : public IPhysicsPlayerControllerEvent
{
public:
	int ShouldMoveTo( IPhysicsObject *pObject, const Vector &position )
	{
		CBaseTFPlayer *pPlayer = ( CBaseTFPlayer* )pObject->GetGameData();
		if ( pPlayer->TouchedPhysics() )
		{
			return 0;
		}
		return 1;
	}
};

static CPhysicsTFPlayerCallback TFPlayerCallback;

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTFPlayer::InitVCollision( void )
{
	if ( GetPlayerClass() )
	{
		GetPlayerClass()->InitVCollision();
	}

	// Setup the HL2 specific callback.
	if ( GetPhysicsController() )
	{
		GetPhysicsController()->SetEventHandler( &TFPlayerCallback );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Return the entity that should receive the score
//-----------------------------------------------------------------------------
CBasePlayer *CBaseTFPlayer::GetScorer( void )
{
	return this;
}

//-----------------------------------------------------------------------------
// Purpose: Return the entity that should get assistance credit
//-----------------------------------------------------------------------------
CBasePlayer *CBaseTFPlayer::GetAssistant( void )
{
	// If I'm in a vehicle, the builder gets credit
	if ( IsInAVehicle() )
	{
		CBaseObject *pObject = dynamic_cast<CBaseObject*>( GetVehicle() );
		if ( pObject )
		{
			CBasePlayer *pBuilder = pObject->GetBuilder();
			if ( pBuilder && pBuilder != this )
				return pBuilder;
		}
	}

	// If I'm boosted, someone's getting the assist
	if ( HasPowerup( POWERUP_BOOST ) && m_hLastBoostEntity.Get() )
	{
		// I may have boosted myself
		if ( m_hLastBoostEntity.Get() != this )
		{
			if ( m_hLastBoostEntity->IsPlayer() )
				return (CBasePlayer*)m_hLastBoostEntity.Get();

			// If it's an object, give the builder the assist (i.e. buff station)
			CBaseObject *pObject = dynamic_cast<CBaseObject*>( m_hLastBoostEntity.Get() );
			if ( pObject )
			{
				CBasePlayer *pBuilder = pObject->GetBuilder();
				if ( pBuilder && pBuilder != this )
					return pBuilder;
			}
		}
	}

	return NULL;
}