//========= Copyright Valve Corporation, All rights reserved. ============//
//
// TF Flame Thrower
//
//=============================================================================
#include "cbase.h"
#include "tf_weapon_flamethrower.h"
#include "tf_fx_shared.h"
#include "in_buttons.h"
#include "ammodef.h"
#include "tf_gamerules.h"

#if defined( CLIENT_DLL )

	#include "c_tf_player.h"
	#include "vstdlib/random.h"
	#include "engine/IEngineSound.h"
	#include "soundenvelope.h"
	#include "prediction.h"
	#include "haptics/ihaptics.h"
	#include "c_tf_gamestats.h"
#else

	#include "explode.h"
	#include "tf_player.h"
	#include "tf_gamestats.h"
	#include "ilagcompensationmanager.h"
	#include "collisionutils.h"
	#include "tf_team.h"
	#include "tf_obj.h"
	#include "tf_weapon_grenade_pipebomb.h"
	#include "particle_parse.h"
	#include "tf_weaponbase_grenadeproj.h"
	#include "tf_weapon_compound_bow.h"
	#include "tf_projectile_arrow.h"
	#include "tf_gamestats.h"
	#include "NextBot/NextBotManager.h"
	#include "halloween/merasmus/merasmus_trick_or_treat_prop.h"
	#include "tf_logic_robot_destruction.h"
#ifdef STAGING_ONLY
	#include "tf_fx.h"
#endif // STAGING_ONLY
	#include "tf_passtime_logic.h"

	ConVar	tf_debug_flamethrower("tf_debug_flamethrower", "0", FCVAR_CHEAT , "Visualize the flamethrower damage." );
	ConVar  tf_flamethrower_velocity( "tf_flamethrower_velocity", "2300.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Initial velocity of flame damage entities." );
	ConVar	tf_flamethrower_drag("tf_flamethrower_drag", "0.87", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Air drag of flame damage entities." );
	ConVar	tf_flamethrower_float("tf_flamethrower_float", "50.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Upward float velocity of flame damage entities." );
	ConVar  tf_flamethrower_vecrand("tf_flamethrower_vecrand", "0.05", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Random vector added to initial velocity of flame damage entities." );
	ConVar  tf_flamethrower_boxsize("tf_flamethrower_boxsize", "12.0", FCVAR_CHEAT , "Size of flame damage entities." );
	ConVar  tf_flamethrower_maxdamagedist("tf_flamethrower_maxdamagedist", "350.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Maximum damage distance for flamethrower." );
	ConVar  tf_flamethrower_shortrangedamagemultiplier("tf_flamethrower_shortrangedamagemultiplier", "1.2", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Damage multiplier for close-in flamethrower damage." );
	ConVar  tf_flamethrower_velocityfadestart("tf_flamethrower_velocityfadestart", ".3", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Time at which attacker's velocity contribution starts to fade." );
	ConVar  tf_flamethrower_velocityfadeend("tf_flamethrower_velocityfadeend", ".5", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Time at which attacker's velocity contribution finishes fading." );
	ConVar	tf_flamethrower_burst_zvelocity( "tf_flamethrower_burst_zvelocity", "350", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );

	static const char *s_pszFlameThrowerHitTargetThink = "FlameThrowerHitTargetThink";

	extern ConVar tf_player_movement_stun_time;

#endif

#include "tf_pumpkin_bomb.h"

ConVar  tf_flamethrower_burstammo("tf_flamethrower_burstammo", "20", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "How much ammo does the air burst uses per shot." );
ConVar  tf_flamethrower_flametime("tf_flamethrower_flametime", "0.5", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "Time to live of flame damage entities." );

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

// position of end of muzzle relative to shoot position
#define TF_FLAMETHROWER_MUZZLEPOS_FORWARD		70.0f
#define TF_FLAMETHROWER_MUZZLEPOS_RIGHT			12.0f
#define TF_FLAMETHROWER_MUZZLEPOS_UP			-12.0f

#define TF_FLAMETHROWER_AMMO_PER_SECOND_PRIMARY_ATTACK		14.0f

#define TF_FLAMETHROWER_HITACCURACY_MED			40.0f
#define TF_FLAMETHROWER_HITACCURACY_HIGH		60.0f

//-----------------------------------------------------------------------------

#define TF_WEAPON_BUBBLE_WAND_MODEL		"models/player/items/pyro/mtp_bubble_wand.mdl"

//-----------------------------------------------------------------------------

#ifndef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose: Only send to local player
//-----------------------------------------------------------------------------
void* SendProxy_SendLocalFlameThrowerDataTable( const SendProp *pProp, const void *pStruct, const void *pVarData, CSendProxyRecipients *pRecipients, int objectID )
{
	// Get the weapon entity
	CBaseCombatWeapon *pWeapon = (CBaseCombatWeapon*)pVarData;
	if ( pWeapon )
	{
		// Only send this chunk of data to the player carrying this weapon
		CBasePlayer *pPlayer = ToBasePlayer( pWeapon->GetOwner() );
		if ( pPlayer )
		{
			pRecipients->SetOnly( pPlayer->GetClientIndex() );
			return (void*)pVarData;
		}
	}

	return NULL;
}
REGISTER_SEND_PROXY_NON_MODIFIED_POINTER( SendProxy_SendLocalFlameThrowerDataTable );
#endif	// CLIENT_DLL

IMPLEMENT_NETWORKCLASS_ALIASED( TFFlameThrower, DT_WeaponFlameThrower )

//-----------------------------------------------------------------------------
// Purpose: Only sent to the local player
//-----------------------------------------------------------------------------
BEGIN_NETWORK_TABLE_NOBASE( CTFFlameThrower, DT_LocalFlameThrower )
	#if defined( CLIENT_DLL )
		RecvPropInt( RECVINFO( m_iActiveFlames ) ),
		RecvPropInt( RECVINFO( m_iDamagingFlames ) ),
		RecvPropBool( RECVINFO( m_bHasHalloweenSpell ) ),
	#else
		SendPropInt( SENDINFO( m_iActiveFlames ), 5, SPROP_UNSIGNED | SPROP_CHANGES_OFTEN ),
		SendPropInt( SENDINFO( m_iDamagingFlames ), 10, SPROP_UNSIGNED | SPROP_CHANGES_OFTEN ),
		SendPropBool( SENDINFO( m_bHasHalloweenSpell ) ),
	#endif
END_NETWORK_TABLE()

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
BEGIN_NETWORK_TABLE( CTFFlameThrower, DT_WeaponFlameThrower )
	#if defined( CLIENT_DLL )
		RecvPropInt( RECVINFO( m_iWeaponState ) ),
		RecvPropBool( RECVINFO( m_bCritFire ) ),
		RecvPropBool( RECVINFO( m_bHitTarget ) ),
		RecvPropFloat( RECVINFO( m_flChargeBeginTime ) ),
		RecvPropDataTable("LocalFlameThrowerData", 0, 0, &REFERENCE_RECV_TABLE( DT_LocalFlameThrower ) ),
	#else
		SendPropInt( SENDINFO( m_iWeaponState ), 4, SPROP_UNSIGNED | SPROP_CHANGES_OFTEN ),
		SendPropBool( SENDINFO( m_bCritFire ) ),
		SendPropBool( SENDINFO( m_bHitTarget ) ),
		SendPropFloat( SENDINFO( m_flChargeBeginTime ) ),
		SendPropDataTable("LocalFlameThrowerData", 0, &REFERENCE_SEND_TABLE( DT_LocalFlameThrower ), SendProxy_SendLocalFlameThrowerDataTable ),
	#endif
END_NETWORK_TABLE()

#if defined( CLIENT_DLL )
BEGIN_PREDICTION_DATA( CTFFlameThrower )
	DEFINE_PRED_FIELD( m_iWeaponState, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
	DEFINE_PRED_FIELD( m_bCritFire, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
	DEFINE_FIELD(  m_flChargeBeginTime, FIELD_FLOAT ),
END_PREDICTION_DATA()
#endif

LINK_ENTITY_TO_CLASS( tf_weapon_flamethrower, CTFFlameThrower );
PRECACHE_WEAPON_REGISTER( tf_weapon_flamethrower );

BEGIN_DATADESC( CTFFlameThrower )
END_DATADESC()

// ------------------------------------------------------------------------------------------------ //
// CTFFlameThrower implementation.
// ------------------------------------------------------------------------------------------------ //
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CTFFlameThrower::CTFFlameThrower()
#if defined( CLIENT_DLL )
: 	m_FlameEffects( this )
,	m_MmmmphEffect( this )
#endif
{
	WeaponReset();

#if defined( CLIENT_DLL )
	m_pFiringStartSound = NULL;
	m_pFiringLoop = NULL;
	m_pFiringAccuracyLoop = NULL;
	m_pFiringHitLoop = NULL;
	m_bFiringLoopCritical = false;
	m_pPilotLightSound = NULL;
	m_pSpinUpSound = NULL;
	m_szAccuracySound = NULL;
	m_bEffectsThinking = false;
	m_bFullRageEffect = false;
#else
	m_flTimeToStopHitSound = 0;
#endif
	m_bHasHalloweenSpell.Set( false );

	ListenForGameEvent( "recalculate_holidays" );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CTFFlameThrower::~CTFFlameThrower()
{
	DestroySounds();
#if defined( CLIENT_DLL )
	StopFullCritEffect();
#endif
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFFlameThrower::Precache( void )
{
	BaseClass::Precache();

	int iModelIndex = PrecacheModel( TF_WEAPON_BUBBLE_WAND_MODEL );
	PrecacheGibsForModel( iModelIndex );

	PrecacheParticleSystem( "pyro_blast" );
	PrecacheScriptSound( "Weapon_FlameThrower.AirBurstAttack" );
	PrecacheScriptSound( "TFPlayer.AirBlastImpact" );
	PrecacheScriptSound( "Weapon_FlameThrower.AirBurstAttackDeflect" );
	PrecacheParticleSystem( "deflect_fx" );
	PrecacheParticleSystem( "drg_bison_idle" );
	PrecacheParticleSystem( "medicgun_invulnstatus_fullcharge_blue" );
	PrecacheParticleSystem( "medicgun_invulnstatus_fullcharge_red" );
	PrecacheParticleSystem( "halloween_burningplayer_flyingbits");

#ifdef STAGING_ONLY
	PrecacheScriptSound( "Equipment.RocketPack_Activate" );
	PrecacheParticleSystem( "muzzle_bignasty" );
#endif // STAGING_ONLY
}

bool CTFFlameThrower::CanAirBlast() const
{
	int iAirblastDisabled = 0;
	CALL_ATTRIB_HOOK_INT( iAirblastDisabled, airblast_disabled );

	bool bAllowed = ( iAirblastDisabled == 0 );

#ifdef STAGING_ONLY
	int nRocketPack = 0;
	CALL_ATTRIB_HOOK_INT_ON_OTHER( GetOwner(), nRocketPack, rocket_pack );
	if ( nRocketPack )
	{
		bAllowed = false;
	}
#endif // STAGING_ONLY

	return bAllowed;
}

void CTFFlameThrower::DestroySounds( void )
{
#if defined( CLIENT_DLL )
	CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
	if ( m_pFiringStartSound )
	{
		controller.SoundDestroy( m_pFiringStartSound );
		m_pFiringStartSound = NULL;
	}
	if ( m_pFiringLoop )
	{
		controller.SoundDestroy( m_pFiringLoop );
		m_pFiringLoop = NULL;
	}
	if ( m_pPilotLightSound )
	{
		controller.SoundDestroy( m_pPilotLightSound );
		m_pPilotLightSound = NULL;
	}
	if ( m_pSpinUpSound )
	{
		controller.SoundDestroy( m_pSpinUpSound );
		m_pSpinUpSound = NULL;
	}
	if ( m_pFiringAccuracyLoop )
	{
		controller.SoundDestroy( m_pFiringAccuracyLoop );
		m_pFiringAccuracyLoop = NULL;
	}

	StopHitSound();
#endif

}
void CTFFlameThrower::WeaponReset( void )
{
	BaseClass::WeaponReset();

	SetWeaponState( FT_STATE_IDLE );
	m_bCritFire = false;
	m_bHitTarget = false;
	m_flStartFiringTime = 0;
	m_flAmmoUseRemainder = 0;
	m_flChargeBeginTime = 0;
	m_flSpinupBeginTime = 0;
	ResetFlameHitCount();

	DestroySounds();

#if defined( CLIENT_DLL )
	StopFullCritEffect();
#endif
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFFlameThrower::Spawn( void )
{
	m_iAltFireHint = HINT_ALTFIRE_FLAMETHROWER;
	BaseClass::Spawn();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTFFlameThrower::Holster( CBaseCombatWeapon *pSwitchingTo )
{
	SetWeaponState( FT_STATE_IDLE );
	m_bCritFire = false;
	m_bHitTarget = false;
	m_flChargeBeginTime = 0;

#if defined ( CLIENT_DLL )
	StopFlame();
	StopPilotLight();
	StopFullCritEffect();

	m_bEffectsThinking = false;
#endif

	return BaseClass::Holster( pSwitchingTo );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFFlameThrower::ItemPostFrame()
{
	if ( m_bLowered )
		return;

	// Get the player owning the weapon.
	CTFPlayer *pOwner = GetTFPlayerOwner();
	if ( !pOwner )
		return;

#ifdef CLIENT_DLL
	if ( !m_bEffectsThinking )
	{
		m_bEffectsThinking = true;
		SetContextThink( &CTFFlameThrower::ClientEffectsThink, gpGlobals->curtime, "EFFECTS_THINK" );
	}
#endif

	int iAmmo = pOwner->GetAmmoCount( m_iPrimaryAmmoType );

	m_bFiredSecondary = false;
	if ( pOwner->IsAlive() && ( pOwner->m_nButtons & IN_ATTACK2 ) )
	{
		SecondaryAttack();
	}

	// Fixes an exploit where the airblast effect repeats while +attack is active
	if ( m_bFiredBothAttacks )
	{
		if ( pOwner->m_nButtons & IN_ATTACK && !( pOwner->m_nButtons & IN_ATTACK2 ) )
		{
			pOwner->m_nButtons &= ~IN_ATTACK;
		}
		m_bFiredBothAttacks = false;
	}

	if ( pOwner->m_nButtons & IN_ATTACK && pOwner->m_nButtons & IN_ATTACK2 )
	{
		m_bFiredBothAttacks = true;
	}

	if ( !m_bFiredSecondary )
	{
		bool bSpinDown = m_flSpinupBeginTime > 0.0f;

		if ( pOwner->IsAlive() && ( pOwner->m_nButtons & IN_ATTACK ) && iAmmo > 0 )
		{
			PrimaryAttack();
			bSpinDown = false;
		}
		else if ( m_iWeaponState > FT_STATE_IDLE )
		{
			SendWeaponAnim( ACT_MP_ATTACK_STAND_POSTFIRE );
			pOwner->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_POST );
			SetWeaponState( FT_STATE_IDLE );
			m_bCritFire = false;
			m_bHitTarget = false;
		}

		if ( bSpinDown )
		{
			m_flSpinupBeginTime = 0.0f;

#if defined( CLIENT_DLL )
			if ( m_pSpinUpSound )
			{
				float flSpinUpTime = GetSpinUpTime();

				CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
				controller.SoundChangePitch( m_pSpinUpSound, 40, flSpinUpTime * 0.5f );
				controller.SoundChangeVolume( m_pSpinUpSound, 0.0f, flSpinUpTime * 2.0f );
			}
#endif
		}
	}

	if (!((pOwner->m_nButtons & IN_ATTACK) || (pOwner->m_nButtons & IN_RELOAD)) || (!(pOwner->m_nButtons & IN_ATTACK2) || !m_bFiredSecondary))
	{
		// no fire buttons down or reloading
		if ( !ReloadOrSwitchWeapons() && ( m_bInReload == false ) )
		{
			WeaponIdle();
		}
	}

	// charged airblast
	int iChargedAirblast = 0;
	CALL_ATTRIB_HOOK_INT( iChargedAirblast, set_charged_airblast );
	if ( iChargedAirblast != 0 )
	{
		if ( m_flChargeBeginTime > 0 )
		{
			CTFPlayer *pPlayer = GetTFPlayerOwner();
			if ( !pPlayer )
				return;

			// If we're not holding down the attack button, launch the flame rocket
			if ( !(pPlayer->m_nButtons & IN_ATTACK2) )
			{
				//FireProjectile( pOwner );
				float flMultAmmoPerShot = 1.0f;
				CALL_ATTRIB_HOOK_FLOAT( flMultAmmoPerShot, mult_airblast_cost );
				int iAmmoPerShot = tf_flamethrower_burstammo.GetInt() * flMultAmmoPerShot;
				FireAirBlast( iAmmoPerShot );
			}
		}
	}
}

class CTraceFilterIgnoreObjects : public CTraceFilterSimple
{
public:
	// It does have a base, but we'll never network anything below here..
	DECLARE_CLASS( CTraceFilterIgnoreObjects, CTraceFilterSimple );

	CTraceFilterIgnoreObjects( const IHandleEntity *passentity, int collisionGroup )
		: CTraceFilterSimple( passentity, collisionGroup )
	{
	}

	virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
	{
		CBaseEntity *pEntity = EntityFromEntityHandle( pServerEntity );

		if ( pEntity && pEntity->IsBaseObject() )
			return false;

		return BaseClass::ShouldHitEntity( pServerEntity, contentsMask );
	}
};

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFFlameThrower::PrimaryAttack()
{
	float flSpinUpTime = GetSpinUpTime();

	if ( flSpinUpTime > 0.0f )
	{
		if ( m_flSpinupBeginTime > 0.0f )
		{
			if ( gpGlobals->curtime - m_flSpinupBeginTime < flSpinUpTime )
			{
				return;
			}
		}
		else
		{
			m_flSpinupBeginTime = gpGlobals->curtime;

#if defined( CLIENT_DLL )
			CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
			if ( !m_pSpinUpSound )
			{
				// Create the looping pilot light sound
				const char *pchSpinUpSound = GetShootSound( RELOAD );
				CLocalPlayerFilter filter;
				m_pSpinUpSound = controller.SoundCreate( filter, entindex(), pchSpinUpSound );

				controller.Play( m_pSpinUpSound, 0.0f, 40 );
			}

			if ( m_pSpinUpSound )
			{
				controller.SoundChangePitch( m_pSpinUpSound, 100, flSpinUpTime );
				controller.SoundChangeVolume( m_pSpinUpSound, 1.0f, flSpinUpTime * 0.1f );
			}
#endif
			return;
		}
	}

	// Are we capable of firing again?
	if ( m_flNextPrimaryAttack > gpGlobals->curtime )
		return;

	// Get the player owning the weapon.
	CTFPlayer *pOwner = GetTFPlayerOwner();
	if ( !pOwner )
		return;

	if ( !CanAttack() )
	{
#if defined ( CLIENT_DLL )
		StopFlame();
#endif
		SetWeaponState( FT_STATE_IDLE );
		return;
	}

	m_iWeaponMode = TF_WEAPON_PRIMARY_MODE;

	CalcIsAttackCritical();

	// Because the muzzle is so long, it can stick through a wall if the player is right up against it.
	// Make sure the weapon can't fire in this condition by tracing a line between the eye point and the end of the muzzle.
	trace_t trace;	
	Vector vecEye = pOwner->EyePosition();
	Vector vecMuzzlePos = GetVisualMuzzlePos();
	CTraceFilterIgnoreObjects traceFilter( this, COLLISION_GROUP_NONE );
	UTIL_TraceLine( vecEye, vecMuzzlePos, MASK_SOLID, &traceFilter, &trace );
	if ( trace.fraction < 1.0 && ( !trace.m_pEnt || trace.m_pEnt->m_takedamage == DAMAGE_NO ) )
	{
		// there is something between the eye and the end of the muzzle, most likely a wall, don't fire, and stop firing if we already are
		if ( m_iWeaponState > FT_STATE_IDLE )
		{
#if defined ( CLIENT_DLL )
			StopFlame();
#endif
			SetWeaponState( FT_STATE_IDLE );
		}
		return;
	}

	switch ( m_iWeaponState )
	{
	case FT_STATE_IDLE:
		{
			// Just started, play PRE and start looping view model anim

			pOwner->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRE );

			SendWeaponAnim( ACT_VM_PRIMARYATTACK );

			m_flStartFiringTime = gpGlobals->curtime + 0.16;	// 5 frames at 30 fps

			SetWeaponState( FT_STATE_STARTFIRING );
		}
		break;
	case FT_STATE_STARTFIRING:
		{
			// if some time has elapsed, start playing the looping third person anim
			if ( gpGlobals->curtime > m_flStartFiringTime )
			{
				SetWeaponState( FT_STATE_FIRING );
				m_flNextPrimaryAttackAnim = gpGlobals->curtime;
			}
		}
		break;
	case FT_STATE_FIRING:
		{
			if ( gpGlobals->curtime >= m_flNextPrimaryAttackAnim )
			{
				pOwner->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY );
				m_flNextPrimaryAttackAnim = gpGlobals->curtime + 1.4;		// fewer than 45 frames!
			}
		}
		break;

	default:
		break;
	}

#ifdef CLIENT_DLL
	// Restart our particle effect if we've transitioned across water boundaries
	if ( m_iParticleWaterLevel != -1 && pOwner->GetWaterLevel() != m_iParticleWaterLevel )
	{
		if ( m_iParticleWaterLevel == WL_Eyes || pOwner->GetWaterLevel() == WL_Eyes )
		{
			RestartParticleEffect();
		}
	}
#endif

#if !defined (CLIENT_DLL)
	// Let the player remember the usercmd he fired a weapon on. Assists in making decisions about lag compensation.
	pOwner->NoteWeaponFired();

	pOwner->SpeakWeaponFire();
	CTF_GameStats.Event_PlayerFiredWeapon( pOwner, m_bCritFire );

	// Move other players back to history positions based on local player's lag
	lagcompensation->StartLagCompensation( pOwner, pOwner->GetCurrentCommand() );

	// PASSTIME custom lag compensation for the ball; see also tf_fx_shared.cpp
	// it would be better if all entities could opt-in to this, or a way for lagcompensation to handle non-players automatically
	if ( g_pPasstimeLogic && g_pPasstimeLogic->GetBall() )
	{
		g_pPasstimeLogic->GetBall()->StartLagCompensation( pOwner, pOwner->GetCurrentCommand() );
	}

#endif
#ifdef CLIENT_DLL
	C_CTF_GameStats.Event_PlayerFiredWeapon( pOwner, IsCurrentAttackACrit() );
#endif

	float flFiringInterval = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeFireDelay;

#ifdef STAGING_ONLY
	if ( ShootsNapalm() )
	{
		flFiringInterval *= 4.f;
	}
#endif // STAGING_ONLY

	// Don't attack if we're underwater
	if ( pOwner->GetWaterLevel() != WL_Eyes )
	{
		// Find eligible entities in a cone in front of us.
		// Vector vOrigin = pOwner->Weapon_ShootPosition();
		Vector vForward, vRight, vUp;
		QAngle vAngles = pOwner->EyeAngles() + pOwner->GetPunchAngle();
		AngleVectors( vAngles, &vForward, &vRight, &vUp );

		#define NUM_TEST_VECTORS	30

#ifdef CLIENT_DLL
		bool bWasCritical = m_bCritFire;
#endif

		// Burn & Ignite 'em
		int iDmgType = g_aWeaponDamageTypes[ GetWeaponID() ];
		m_bCritFire = IsCurrentAttackACrit();
		if ( m_bCritFire )
		{
			iDmgType |= DMG_CRITICAL;
		}

#ifdef CLIENT_DLL
		if ( bWasCritical != m_bCritFire )
		{
			RestartParticleEffect();
		}
#endif


#ifdef GAME_DLL
		// create the flame entity
		int iDamagePerSec = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_nDamage;
		float flDamage = (float)iDamagePerSec * flFiringInterval;
		CALL_ATTRIB_HOOK_FLOAT( flDamage, mult_dmg );

		int iCritFromBehind = 0;
		CALL_ATTRIB_HOOK_INT( iCritFromBehind, set_flamethrower_back_crit );

#ifdef STAGING_ONLY
		if ( ShootsNapalm() )
		{
			CTFProjectile_Napalm::Create( pOwner, this );
		}
		else
#endif // STAGING_ONLY
		{
			CTFFlameEntity::Create( GetFlameOriginPos(), pOwner->EyeAngles(), this, tf_flamethrower_velocity.GetFloat(), iDmgType, flDamage, iCritFromBehind == 1 );
		}

		// Pyros can become invis in some game modes.  Hitting fire normally handles this,
		// but in the case of flamethrowers it's likely that stealth will be applied while
		// the fire button is down, so we have to call into RemoveInvisibility here, too.
		if ( pOwner->m_Shared.IsStealthed() )
		{
			pOwner->RemoveInvisibility();
		}
#endif
	}

#ifdef GAME_DLL
	// Figure how much ammo we're using per shot and add it to our remainder to subtract.  (We may be using less than 1.0 ammo units
	// per frame, depending on how constants are tuned, so keep an accumulator so we can expend fractional amounts of ammo per shot.)
	// Note we do this only on server and network it to client.  If we predict it on client, it can get slightly out of sync w/server
	// and cause ammo pickup indicators to appear
	float flAmmoPerSecond = TF_FLAMETHROWER_AMMO_PER_SECOND_PRIMARY_ATTACK;
	CALL_ATTRIB_HOOK_FLOAT( flAmmoPerSecond, mult_flame_ammopersec );
	m_flAmmoUseRemainder += flAmmoPerSecond * flFiringInterval;
	// take the integer portion of the ammo use accumulator and subtract it from player's ammo count; any fractional amount of ammo use
	// remains and will get used in the next shot
	int iAmmoToSubtract = (int) m_flAmmoUseRemainder;
	if ( iAmmoToSubtract > 0 )
	{
		pOwner->RemoveAmmo( iAmmoToSubtract, m_iPrimaryAmmoType );
		m_flAmmoUseRemainder -= iAmmoToSubtract;
		// round to 2 digits of precision
		m_flAmmoUseRemainder = (float) ( (int) (m_flAmmoUseRemainder * 100) ) / 100.0f;
	}
#endif

	m_flNextPrimaryAttack = gpGlobals->curtime + flFiringInterval;
	m_flTimeWeaponIdle = gpGlobals->curtime + flFiringInterval;

#if !defined (CLIENT_DLL)
	lagcompensation->FinishLagCompensation( pOwner );

	// PASSTIME custom lag compensation for the ball; see also tf_fx_shared.cpp
	// it would be better if all entities could opt-in to this, or a way for lagcompensation to handle non-players automatically
	if ( g_pPasstimeLogic && g_pPasstimeLogic->GetBall() )
	{
		g_pPasstimeLogic->GetBall()->FinishLagCompensation( pOwner );
	}
#endif
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
float AirBurstDamageForce( const Vector &size, float damage, float scale )
{ 
	float force = damage * ((48 * 48 * 82.0) / (size.x * size.y * size.z)) * scale;

	if ( force > 1000.0) 
	{
		force = 1000.0;
	}

	return force;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTFFlameThrower::SupportsAirBlastFunction( EFlameThrowerAirblastFunction eFunction ) const
{
	int iSupportedAirBlastFunctions = 0;
	CALL_ATTRIB_HOOK_INT( iSupportedAirBlastFunctions, airblast_functionality_flags );

	// If we don't have this attribute specified, or it is set to the value 0, we interpret
	// that as "I can do everything!".
	if ( iSupportedAirBlastFunctions == 0 )
	{
		// They can do everything unless airblast is disabled, in which case they can do nothing
		return CanAirBlast();
	}

	return (iSupportedAirBlastFunctions & eFunction) != 0;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFFlameThrower::FireAirBlast( int iAmmoPerShot )
{
	CTFPlayer *pOwner = GetTFPlayerOwner();
	if ( !pOwner )
		return;

	m_bFiredSecondary = true;

#ifdef CLIENT_DLL
	// Stop the flame if we're currently firing
	StopFlame( false );
#endif

	SetWeaponState( FT_STATE_SECONDARY );

#ifdef GAME_DLL
	SendWeaponAnim( ACT_VM_SECONDARYATTACK );
	pOwner->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_SECONDARY );

	int nDash = 0;
	CALL_ATTRIB_HOOK_INT( nDash, airblast_dashes );

	if ( !nDash )
	{
		DeflectProjectiles();
	}
	else
	{
#ifdef STAGING_ONLY
		Vector vDashDir;
		AngleVectors( pOwner->EyeAngles() + QAngle( 0.0f, 180.0f, 0.0f ), &vDashDir );
#else
		Vector vDashDir = pOwner->GetAbsVelocity();
		if ( !pOwner->GetGroundEntity() || vDashDir.Length() == 0.0f )
		{
			AngleVectors( pOwner->EyeAngles(), &vDashDir );
		}
#endif
		vDashDir.z = 0.0f;
		VectorNormalize( vDashDir );

		Vector vCenter = pOwner->WorldSpaceCenter();
		Vector vSize = GetDeflectionSize();
		DeflectPlayer( pOwner, pOwner, vDashDir, vCenter, vSize );
	}

	// for charged airblast
	int iChargedAirblast = 0;
	CALL_ATTRIB_HOOK_INT( iChargedAirblast, set_charged_airblast );
	if ( iChargedAirblast != 0 )
	{
		m_flChargeBeginTime = 0;
	}

	// compression blast doesn't go through the normal "weapon fired" code path
	TheNextBots().OnWeaponFired( pOwner, this );
#endif

#ifdef CLIENT_DLL
	if ( prediction->IsFirstTimePredicted() == true )
	{
		StartFlame();
	}
#endif

	float fAirblastRefireTimeScale = 1.0f;
	CALL_ATTRIB_HOOK_FLOAT( fAirblastRefireTimeScale, mult_airblast_refire_time );
	if ( fAirblastRefireTimeScale <= 0.0f  )
	{
		fAirblastRefireTimeScale = 1.0f;
	}

	float fAirblastPrimaryRefireTimeScale = 1.0f;
	CALL_ATTRIB_HOOK_FLOAT( fAirblastPrimaryRefireTimeScale, mult_airblast_primary_refire_time );
	if ( fAirblastPrimaryRefireTimeScale <= 0.0f )
	{
		fAirblastPrimaryRefireTimeScale = 1.0f;
	}

	// Haste Powerup Rune adds multiplier to fire delay time
	if ( pOwner->m_Shared.GetCarryingRuneType() == RUNE_HASTE )
	{
		fAirblastRefireTimeScale *= 0.5f;
	}

	m_flNextSecondaryAttack = gpGlobals->curtime + (0.75f * fAirblastRefireTimeScale);	
	m_flNextPrimaryAttack = gpGlobals->curtime + (1.0f * fAirblastRefireTimeScale * fAirblastPrimaryRefireTimeScale);
	m_flResetBurstEffect = gpGlobals->curtime + 0.05f;

	pOwner->RemoveAmmo( iAmmoPerShot, m_iPrimaryAmmoType );
}

float CTFFlameThrower::GetSpinUpTime( void ) const
{
	float flSpinUpTime = 0.0f;
	CALL_ATTRIB_HOOK_FLOAT( flSpinUpTime, mod_flamethrower_spinup_time );

	return flSpinUpTime;
}

void CTFFlameThrower::SetWeaponState( int nWeaponState )
{
	if ( m_iWeaponState == nWeaponState )
		return;

	CTFPlayer *pOwner = GetTFPlayerOwner();

	switch ( nWeaponState )
	{
	case FT_STATE_IDLE:
		if ( pOwner )
		{
			float flFiringForwardPull = 0.0f;
			CALL_ATTRIB_HOOK_FLOAT( flFiringForwardPull, firing_forward_pull );
			if ( flFiringForwardPull )
			{
				pOwner->m_Shared.RemoveCond( TF_COND_SPEED_BOOST );
			}
		}
		break;

	case FT_STATE_STARTFIRING:
		if ( pOwner )
		{
			float flFiringForwardPull = 0.0f;
			CALL_ATTRIB_HOOK_FLOAT( flFiringForwardPull, firing_forward_pull );
			if ( flFiringForwardPull )
			{
				pOwner->m_Shared.AddCond( TF_COND_SPEED_BOOST );
			}
		}
		break;
	}

	m_iWeaponState = nWeaponState;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFFlameThrower::UseRage( void )
{
	if ( !IsRageFull() )
		return;

	CTFPlayer *pPlayer = GetTFPlayerOwner();
	if ( !pPlayer )
		return;

	if ( !pPlayer->IsAllowedToTaunt() )
		return;

	float flNextAttack = m_flNextSecondaryAttack;

#if GAME_DLL
	// Do a taunt so everyone has a chance to run
	pPlayer->Taunt( TAUNT_BASE_WEAPON );
	if ( pPlayer->m_Shared.IsRageDraining() )
	{
		// taunt succeeded
		flNextAttack = gpGlobals->curtime + 1.0f;
	}
#else
	flNextAttack = gpGlobals->curtime + 1.0f;
#endif

	m_flNextSecondaryAttack = flNextAttack;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFFlameThrower::SecondaryAttack()
{
	if ( m_flChargeBeginTime > 0 )
	{
		m_bFiredSecondary = true;
		return;
	}

	if ( m_flNextSecondaryAttack > gpGlobals->curtime )
	{
#ifndef CLIENT_DLL
		if ( m_flResetBurstEffect <= gpGlobals->curtime )
		{
			SetWeaponState( FT_STATE_IDLE );
		}
#endif
		return;
	}

	CTFPlayer *pOwner = GetTFPlayerOwner();
	if ( !pOwner )
		return;

	if ( pOwner->GetWaterLevel() == WL_Eyes )
		return;

	if ( !CanAttack() )
	{
		SetWeaponState( FT_STATE_IDLE );
		return;
	}

	int iAmmo = pOwner->GetAmmoCount( m_iPrimaryAmmoType );

	// charged airblast
	int iChargedAirblast = 0;
	CALL_ATTRIB_HOOK_INT( iChargedAirblast, set_charged_airblast );
	int iBuffType = 0;
	CALL_ATTRIB_HOOK_INT( iBuffType, set_buff_type );
	float flMultAmmoPerShot = 1.0f;
	CALL_ATTRIB_HOOK_FLOAT( flMultAmmoPerShot, mult_airblast_cost );
	int iAmmoPerShot = tf_flamethrower_burstammo.GetInt() * flMultAmmoPerShot;

	if ( iBuffType != 0 )
	{
		UseRage();
		return;
	}

	if ( iAmmo < iAmmoPerShot )
		return;

	// normal air blast?
	if ( iChargedAirblast == 0 && CanAirBlast() )
	{
		FireAirBlast( iAmmoPerShot );
		return;
	}

#ifdef CLIENT_DLL
	// Stop the flame if we're currently firing
	StopFlame( false );
#endif

	SetWeaponState( FT_STATE_SECONDARY );

#ifdef STAGING_ONLY
	if ( RocketPackCanActivate( iAmmoPerShot ) )
	{
		RocketPackLaunch( iAmmoPerShot );
		return;
	}
#endif // STAGING_ONLY

#ifdef GAME_DLL
	m_iWeaponMode = TF_WEAPON_SECONDARY_MODE;
	m_flChargeBeginTime = gpGlobals->curtime;
	SendWeaponAnim( ACT_VM_PULLBACK );
	// @todo replace with the correct one
	WeaponSound( SINGLE );
#endif
}

#ifdef GAME_DLL

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
Vector CTFFlameThrower::GetDeflectionSize()
{ 
	const Vector vecBaseDeflectionSize = BaseClass::GetDeflectionSize();
	float fMultiplier = 1.0f;

	// int iChargedAirblast = 0;
	// CALL_ATTRIB_HOOK_INT( iChargedAirblast, set_charged_airblast );
	// if ( iChargedAirblast != 0 )
	// {
	//	 fMultiplier *= RemapValClamped( ( gpGlobals->curtime - m_flChargeBeginTime ),
	// 										  0.0f,
	// 										  GetChargeMaxTime(),
	// 										  AIRBLAST_CHARGE_MULT_MIN,
	// 										  AIRBLAST_CHARGE_MULT_MAX );
	// }

	// Allow custom attributes to scale the deflection size.
	CALL_ATTRIB_HOOK_FLOAT( fMultiplier, deflection_size_multiplier );

	return vecBaseDeflectionSize * fMultiplier;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
#ifdef _DEBUG
ConVar tf_pushbackscalescale( "tf_pushbackscalescale", "1.0" );
ConVar tf_pushbackscalescale_vertical( "tf_pushbackscalescale_vertical", "1.0" );
#endif

void ExtinguishPlayer( CEconEntity *pExtinguisher, CTFPlayer *pOwner, CTFPlayer *pTarget, const char *pExtinguisherName )
{
	pTarget->EmitSound( "TFPlayer.FlameOut" );

	pTarget->m_Shared.RemoveCond( TF_COND_BURNING );

	// we're going to limit the number of times you can be awarded bonus points to prevent exploits
	if ( pOwner->ShouldGetBonusPointsForExtinguishEvent( pTarget->GetUserID() ) )
	{
		CTF_GameStats.Event_PlayerAwardBonusPoints( pOwner, pTarget, 10 );
	}

	CRecipientFilter involved_filter;
	involved_filter.AddRecipient( pOwner );
	involved_filter.AddRecipient( pTarget );

	UserMessageBegin( involved_filter, "PlayerExtinguished" );
	WRITE_BYTE( pOwner->entindex() );
	WRITE_BYTE( pTarget->entindex() );
	MessageEnd();

	IGameEvent *event = gameeventmanager->CreateEvent( "player_extinguished" );
	if ( event )
	{
		event->SetInt( "victim", pTarget->entindex() );
		event->SetInt( "healer", pOwner->entindex() );

		gameeventmanager->FireEvent( event, true );
	}

	// stats
	EconEntity_OnOwnerKillEaterEvent( pExtinguisher, pOwner, pTarget, kKillEaterEvent_BurningAllyExtinguished );

	UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"player_extinguished\" against \"%s<%i><%s><%s>\" with \"%s\" (attacker_position \"%d %d %d\") (victim_position \"%d %d %d\")\n",    
				pOwner->GetPlayerName(), pOwner->GetUserID(), pOwner->GetNetworkIDString(), pOwner->GetTeam()->GetName(),
				pTarget->GetPlayerName(), pTarget->GetUserID(), pTarget->GetNetworkIDString(), pTarget->GetTeam()->GetName(),
				pExtinguisherName, (int)pOwner->GetAbsOrigin().x, (int)pOwner->GetAbsOrigin().y, (int)pOwner->GetAbsOrigin().z,
				(int)pTarget->GetAbsOrigin().x, (int)pTarget->GetAbsOrigin().y, (int)pTarget->GetAbsOrigin().z );
}

bool CTFFlameThrower::DeflectPlayer( CTFPlayer *pTarget, CTFPlayer *pOwner, Vector &vecForward, Vector &vecCenter, Vector &vecSize )
{
	if ( pTarget->GetTeamNumber() == pOwner->GetTeamNumber() && pTarget != pOwner )
	{
		if ( pTarget->m_Shared.InCond( TF_COND_BURNING ) && SupportsAirBlastFunction( TF_FUNCTION_AIRBLAST_PUT_OUT_TEAMMATES ) )
		{
			ExtinguishPlayer( this, pOwner, pTarget, "tf_weapon_flamethrower" );

			// Return health to the Pyro. 
			// We may want to cap the amount of health per extinguish but for now lets test this
			int iRestoreHealthOnExtinguish = 0;
			CALL_ATTRIB_HOOK_INT( iRestoreHealthOnExtinguish, extinguish_restores_health );
			if ( iRestoreHealthOnExtinguish > 0 )
			{
				pOwner->TakeHealth( iRestoreHealthOnExtinguish, DMG_GENERIC );
				IGameEvent *healevent = gameeventmanager->CreateEvent( "player_healonhit" );
				if ( healevent )
				{
					healevent->SetInt( "amount", iRestoreHealthOnExtinguish );
					healevent->SetInt( "entindex", pOwner->entindex() );
					item_definition_index_t healingItemDef = INVALID_ITEM_DEF_INDEX;
					if ( GetAttributeContainer() && GetAttributeContainer()->GetItem() )
					{
						healingItemDef = GetAttributeContainer()->GetItem()->GetItemDefIndex();
					}
					healevent->SetInt( "weapon_def_index", healingItemDef );

					gameeventmanager->FireEvent( healevent ); 
				}
			}
		}

		return false;
	}
	
	if ( SupportsAirBlastFunction( TF_FUNCTION_AIRBLAST_PUSHBACK ) )
	{
		int iReverseBlast = 0;
		CALL_ATTRIB_HOOK_INT( iReverseBlast, reverse_airblast );

		// Against players, let's force the pyro to be actually looking at them.
		// We'll be a bit more laxed when it comes to aiming at rockets and grenades.
		Vector vecToTarget;

		if ( pTarget == pOwner )
		{
			vecToTarget = vecForward;
		}
		else
		{
			vecToTarget = pTarget->WorldSpaceCenter() - pOwner->WorldSpaceCenter();
			VectorNormalize( vecToTarget );
		}

		// Quick Fix Uber is immune
		if ( pTarget->m_Shared.InCond( TF_COND_MEGAHEAL )) 
			return false;


		// Require our target be in a cone in front of us. Default threshold is the dot-product needs to be at least 0.8 = 1 - 0.2. 
		float flDot = DotProduct( vecForward, vecToTarget );
		float flAirblastConeScale = 0.2f;
		CALL_ATTRIB_HOOK_FLOAT( flAirblastConeScale, mult_airblast_cone_scale );
		float flAirblastConeThreshold = Clamp(1.0f - flAirblastConeScale, 0.0f, 1.0f);
		if (flDot < flAirblastConeThreshold)
		{
			return false;
		}

		if ( pTarget != pOwner )
		{
			pTarget->SetAbsVelocity( vec3_origin );

			if ( SupportsAirBlastFunction( TF_FUNCTION_AIRBLAST_PUSHBACK__STUN ) )
			{
				if ( !pTarget->m_Shared.InCond( TF_COND_KNOCKED_INTO_AIR ) )
				{
					pTarget->m_Shared.StunPlayer( tf_player_movement_stun_time.GetFloat(), 1.f, TF_STUN_MOVEMENT, pOwner );
				}
			}

			if ( SupportsAirBlastFunction( TF_FUNCTION_AIRBLAST_PUSHBACK__VIEW_PUNCH ) )
			{
				pTarget->ApplyPunchImpulseX( RandomInt( 10, 15 ) );
			}
		}

		pTarget->SpeakConceptIfAllowed( MP_CONCEPT_DEFLECTED, "projectile:0,victim:1" );

		float flForce = AirBurstDamageForce( pTarget->WorldAlignSize(), 60, 6.f );

		CALL_ATTRIB_HOOK_FLOAT( flForce, airblast_pushback_scale );

#ifdef _DEBUG
		Vector vecForce = vecToTarget * flForce * tf_pushbackscalescale.GetFloat();
#else
		Vector vecForce = vecToTarget * flForce;	
#endif

		if ( iReverseBlast )
		{
			vecForce = -vecForce;
		}

		float flVerticalPushbackScale = tf_flamethrower_burst_zvelocity.GetFloat();
		if ( iReverseBlast )
		{
			// Don't give quite so big a vertical kick if we're sucking rather than blowing...
			flVerticalPushbackScale *= 0.75f;
		}

#ifdef STAGING_ONLY
		if ( !( pTarget == pOwner && pOwner->GetGroundEntity() ) )
#endif		
		{
			CALL_ATTRIB_HOOK_FLOAT( flVerticalPushbackScale, airblast_vertical_pushback_scale );
		}

#ifdef _DEBUG
		vecForce.z += flVerticalPushbackScale * tf_pushbackscalescale_vertical.GetFloat();

		/*
		// Kyle says: this will force players off the ground for at least one frame.
		//			  This is disabled on purpose right now to match previous flamethrower functionality.
		if ( pTarget->GetFlags() & FL_ONGROUND )
		{
			vecForce.z += 268.3281572999747f;
		}
		*/
#else
		vecForce.z += flVerticalPushbackScale;
#endif

		// Apply AirBlastImpulse
		pTarget->ApplyAirBlastImpulse( vecForce );
		
		// Make sure we get credit for the airblast if the target falls to its death
		pTarget->m_AchievementData.AddDamagerToHistory( pOwner );

		SendObjectDeflectedEvent( pOwner, pTarget, TF_WEAPON_NONE, pTarget ); // TF_WEAPON_NONE means the player got pushed

		// If the target is charging, stop the charge and keep the charge meter where it is.
		pTarget->m_Shared.InterruptCharge();

		// Track for achievements
		pTarget->m_AchievementData.AddPusherToHistory( pOwner );

		// Give bonus points whenever a pyro pushes high-value targets back
		if ( TFGameRules() && ( pTarget->IsMiniBoss() || pTarget->m_Shared.IsInvulnerable() ) )
		{
			int nAmount = pTarget->IsMiniBoss() ? 10 : 5;
			CTF_GameStats.Event_PlayerAwardBonusPoints( pOwner, pTarget, nAmount );
		}

		return true;
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFFlameThrower::PlayDeflectionSound( bool bPlayer )
{
	if ( bPlayer )
	{
		EmitSound( "TFPlayer.AirBlastImpact" );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTFFlameThrower::DeflectEntity( CBaseEntity *pTarget, CTFPlayer *pOwner, Vector &vecForward, Vector &vecCenter, Vector &vecSize )
{
	Assert( pTarget );
	Assert( pOwner );

	if ( !SupportsAirBlastFunction( TF_FUNCTION_AIRBLAST_REFLECT_PROJECTILES ) )
		return false;

	// can't deflect things on our own team
	// except the passtime ball when in passtime mode
	if ( (pTarget->GetTeamNumber() == pOwner->GetTeamNumber()) 
		&& !(g_pPasstimeLogic && (g_pPasstimeLogic->GetBall() == pTarget)) )
	{
		return false;
	}

	// Grab the owner of the projectile *before* we reflect it.
	CTFPlayer *pTFPlayerVictim = dynamic_cast<CTFPlayer *>( pTarget );
	if ( !pTFPlayerVictim )
	{
		pTFPlayerVictim = dynamic_cast<CTFPlayer *>( pTarget->GetOwnerEntity() );
	}

	if ( !pTFPlayerVictim )
	{
		// We can't use OwnerEntity for grenades, because then the owner can't shoot them with his hitscan weapons (due to collide rules)
		// Thrower is used to store the person who threw the grenade, for damage purposes.
		CBaseGrenade *pBaseGrenade = dynamic_cast< CBaseGrenade*>( pTarget );
		if ( pBaseGrenade )
		{
			pTFPlayerVictim = dynamic_cast<CTFPlayer *>( pBaseGrenade->GetThrower() );
		}
	}

	if ( !pTFPlayerVictim )
	{
		// Is the OwnerEntity() a base object, like a sentry gun shooting rockets at us?
		if ( pTarget->GetOwnerEntity() && pTarget->GetOwnerEntity()->IsBaseObject() )
		{
			CBaseObject *pObj = dynamic_cast<CBaseObject *>( pTarget->GetOwnerEntity() );
			if ( pObj )
			{
				pTFPlayerVictim = dynamic_cast<CTFPlayer *>( pObj->GetOwner() );
			}
		}
	}

	bool bDeflected = BaseClass::DeflectEntity( pTarget, pOwner, vecForward, vecCenter, vecSize );
	if ( bDeflected )
	{
		pTarget->EmitSound( "Weapon_FlameThrower.AirBurstAttackDeflect" );

		EconEntity_OnOwnerKillEaterEvent( this, pOwner, pTFPlayerVictim, kKillEaterEvent_ProjectileReflect );
	}
	return bDeflected;
}
#endif

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTFFlameThrower::Lower( void )
{
	if ( BaseClass::Lower() )
	{
		// If we were firing, stop
		if ( m_iWeaponState > FT_STATE_IDLE )
		{
			SendWeaponAnim( ACT_MP_ATTACK_STAND_POSTFIRE );
			SetWeaponState( FT_STATE_IDLE );
		}

		return true;
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Returns the position of the tip of the muzzle at it appears visually
//-----------------------------------------------------------------------------
Vector CTFFlameThrower::GetVisualMuzzlePos()
{
	return GetMuzzlePosHelper( true );
}

//-----------------------------------------------------------------------------
// Purpose: Returns the position at which to spawn flame damage entities
//-----------------------------------------------------------------------------
Vector CTFFlameThrower::GetFlameOriginPos()
{
	return GetMuzzlePosHelper( false );
}

#ifdef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
float CTFFlameThrower::GetFlameHitRatio( void )
{
	// Safety net to avoid divide by zero
	if ( m_iActiveFlames == 0 )
		return 0.1f;

	float flRatio = ( ( (float)m_iDamagingFlames ) / ( (float)m_iActiveFlames ) );
	//Msg( "Act:  %d  Dmg:  %d\n", m_iActiveFlames, m_iDamagingFlames );

	return flRatio;
}
#endif

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFFlameThrower::IncrementFlameDamageCount( void )
{
	m_iDamagingFlames++;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFFlameThrower::DecrementFlameDamageCount( void )
{
	if ( m_iDamagingFlames <= 0 )
		return;

	m_iDamagingFlames--;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFFlameThrower::IncrementActiveFlameCount( void )
{
	m_iActiveFlames++;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFFlameThrower::DecrementActiveFlameCount( void )
{
	if ( m_iActiveFlames <= 0 )
		return;

	m_iActiveFlames--;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFFlameThrower::ResetFlameHitCount( void )
{
	m_iDamagingFlames = 0;
	m_iActiveFlames = 0;
}


//-----------------------------------------------------------------------------
// Purpose: UI Progress
//-----------------------------------------------------------------------------
float CTFFlameThrower::GetProgress( void )
{
	CTFPlayer *pPlayer = GetTFPlayerOwner();
	if ( !pPlayer )
		return 0.f;

	return pPlayer->m_Shared.GetRageMeter() / 100.0f;
}


//-----------------------------------------------------------------------------
// Purpose: UI Progress (same as GetProgress() without the division by 100.0f)
//-----------------------------------------------------------------------------
bool CTFFlameThrower::IsRageFull( void )
{
	CTFPlayer *pPlayer = GetTFPlayerOwner();
	if ( !pPlayer )
		return false;

	return ( pPlayer->m_Shared.GetRageMeter() >= 100.0f );
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFFlameThrower::EffectMeterShouldFlash( void )
{
	CTFPlayer *pPlayer = GetTFPlayerOwner();
	if ( !pPlayer )
		return false;

	if ( pPlayer && (IsRageFull() || pPlayer->m_Shared.IsRageDraining()) )
		return true;
	else
		return false;
}


//-----------------------------------------------------------------------------
// Purpose: Returns the position of the tip of the muzzle
//-----------------------------------------------------------------------------
Vector CTFFlameThrower::GetMuzzlePosHelper( bool bVisualPos )
{
	Vector vecMuzzlePos;
	CTFPlayer *pOwner = GetTFPlayerOwner();
	if ( pOwner ) 
	{
		Vector vecForward, vecRight, vecUp;
		AngleVectors( pOwner->GetAbsAngles(), &vecForward, &vecRight, &vecUp );
		vecMuzzlePos = pOwner->Weapon_ShootPosition();
		vecMuzzlePos +=  vecRight * TF_FLAMETHROWER_MUZZLEPOS_RIGHT;
		// if asking for visual position of muzzle, include the forward component
		if ( bVisualPos )
		{
			vecMuzzlePos +=  vecForward * TF_FLAMETHROWER_MUZZLEPOS_FORWARD;
		}
	}
	return vecMuzzlePos;
}

void CTFFlameThrower::CalculateHalloweenSpell( void )
{
	m_bHasHalloweenSpell.Set( false );
	if ( TF_IsHolidayActive( kHoliday_HalloweenOrFullMoon ) )
	{
		int iHalloweenSpell = 0;
		CALL_ATTRIB_HOOK_INT_ON_OTHER( this, iHalloweenSpell, halloween_green_flames );
		m_bHasHalloweenSpell.Set( iHalloweenSpell > 0 );
	}
}

bool CTFFlameThrower::Deploy( void )
{
#if defined( CLIENT_DLL )
	StartPilotLight();
	m_flFlameHitRatio = 0;
	m_flPrevFlameHitRatio = -1;
	m_flChargeBeginTime = 0;

	m_bEffectsThinking = true;
	SetContextThink( &CTFFlameThrower::ClientEffectsThink, gpGlobals->curtime, "EFFECTS_THINK" );

	StopFullCritEffect();
#endif // CLIENT_DLL

	CalculateHalloweenSpell();

	return BaseClass::Deploy();
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFFlameThrower::FireGameEvent( IGameEvent *event )
{
	if ( FStrEq( event->GetName(), "recalculate_holidays" ) )
	{
		CalculateHalloweenSpell();
	}
}

#if defined( CLIENT_DLL )

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFFlameThrower::OnDataChanged(DataUpdateType_t updateType)
{
	BaseClass::OnDataChanged(updateType);

	C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
	C_TFPlayer *pPlayerOwner = GetTFPlayerOwner();

	//
	bool bLocalPlayerAmmo = true;

	if ( pPlayerOwner == pLocalPlayer )
	{
		bLocalPlayerAmmo = GetPlayerOwner()->GetAmmoCount( m_iPrimaryAmmoType ) > 0;
	}

	if ( IsCarrierAlive() && ( WeaponState() == WEAPON_IS_ACTIVE ) && bLocalPlayerAmmo == true )
	{
		if ( m_iWeaponState > FT_STATE_IDLE )
		{
			if ( ( m_iWeaponState == FT_STATE_SECONDARY && GetPlayerOwner() != C_BasePlayer::GetLocalPlayer() ) || m_iWeaponState != FT_STATE_SECONDARY )
			{
				StartFlame();

#ifdef STAGING_ONLY
				if ( ShootsNapalm() )
				{
					RestartParticleEffect();
				}
#endif // STAGING_ONLY
			}
		}
		else
		{
			StartPilotLight();
		}
	}
	else 
	{
		StopFlame();
		StopPilotLight();
		StopFullCritEffect();
		m_bEffectsThinking = false;
	}
	
	if ( pPlayerOwner == pLocalPlayer )
	{
		if ( m_pFiringLoop )
		{
 			m_flFlameHitRatio = GetFlameHitRatio();
 			m_flFlameHitRatio = RemapValClamped( m_flFlameHitRatio, 0.0f, 1.0f, 1.0f, 100.f );

			//Msg ( "%f\n", m_flFlameHitRatio );

			if ( m_flFlameHitRatio != m_flPrevFlameHitRatio )
			{
				m_flPrevFlameHitRatio = m_flFlameHitRatio;

				CLocalPlayerFilter filter;
				CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();

				// We play accent sounds based on accuracy
				if ( m_flFlameHitRatio >= TF_FLAMETHROWER_HITACCURACY_HIGH )
				{
					controller.SoundChangePitch( m_pFiringLoop, 140, 0.1 );	
					m_szAccuracySound = "Weapon_FlameThrower.FireHitHard";
				}
				else
				{
					controller.SoundChangePitch( m_pFiringLoop, 100, 0.1 );	

					// If our accuracy is too low
					if ( m_pFiringAccuracyLoop )
					{
						controller.SoundDestroy( m_pFiringAccuracyLoop );
						m_pFiringAccuracyLoop = NULL;					
					}

					return;
				}

				// Only start a new sound if there's been a change
				if ( !m_pFiringAccuracyLoop )
				{
					m_pFiringAccuracyLoop = controller.SoundCreate( filter, entindex(), m_szAccuracySound );
					controller.Play( m_pFiringAccuracyLoop, 1.0, 100 );
				}

			}
		}
		else if ( m_pFiringAccuracyLoop )
		{
			CSoundEnvelopeController::GetController().SoundDestroy( m_pFiringAccuracyLoop );
			m_pFiringAccuracyLoop = NULL;
		}

		if ( GetBuffType() > 0 )
		{
			if ( !m_bFullRageEffect && pPlayerOwner && pPlayerOwner->m_Shared.GetRageMeter() >= 100.0f )
			{
				m_bFullRageEffect = true;
				m_MmmmphEffect.StartEffects( FullCritChargedEffectName() );
			}
			else if ( m_bFullRageEffect && pPlayerOwner && pPlayerOwner->m_Shared.GetRageMeter() < 100.0f )
			{
				StopFullCritEffect();
				m_MmmmphEffect.StopEffects();
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFFlameThrower::UpdateOnRemove( void )
{
	m_FlameEffects.StopEffects();
	m_MmmmphEffect.StopEffects();
	StopPilotLight();
	StopFullCritEffect();
	m_bEffectsThinking = false;

	BaseClass::UpdateOnRemove();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFFlameThrower::SetDormant( bool bDormant )
{
	// If I'm going from active to dormant and I'm carried by another player, stop our firing sound.
	if ( !IsCarriedByLocalPlayer() )
	{
		if ( !IsDormant() && bDormant )
		{
			StopFlame();
			StopPilotLight();
			StopFullCritEffect();
			m_bEffectsThinking = false;
		}
	}

	// Deliberately skip base combat weapon to avoid being holstered
	C_BaseEntity::SetDormant( bDormant );
}

int CTFFlameThrower::GetWorldModelIndex( void )
{
	int iParticleEffectIndex = 0;
	CALL_ATTRIB_HOOK_INT( iParticleEffectIndex, set_weapon_mode );

	// Pyro bubble wand support.
	if ( iParticleEffectIndex == 3 )
	{
		CTFPlayer *pPlayer = ToTFPlayer( GetOwner() );
		if ( pPlayer && pPlayer->m_Shared.InCond( TF_COND_TAUNTING ) && pPlayer->m_Shared.GetTauntIndex() == TAUNT_BASE_WEAPON )
		{
			// While we are taunting, replace our normal world model with the bubble wand.
			m_iWorldModelIndex = modelinfo->GetModelIndex( TF_WEAPON_BUBBLE_WAND_MODEL );
			return m_iWorldModelIndex;
		}
	}

	return BaseClass::GetWorldModelIndex();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFFlameThrower::StartFlame()
{
	if ( m_iWeaponState == FT_STATE_SECONDARY )
	{
		GetAppropriateWorldOrViewModel()->ParticleProp()->Create( "pyro_blast", PATTACH_POINT_FOLLOW, "muzzle" );
		CLocalPlayerFilter filter;
		const char *shootsound = GetShootSound( WPN_DOUBLE );
		EmitSound( filter, entindex(), shootsound );

		return;
	}

	CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();

	// normally, crossfade between start sound & firing loop in 3.5 sec
	float flCrossfadeTime = 3.5;

	if ( m_pFiringLoop && ( m_bCritFire != m_bFiringLoopCritical ) )
	{
		// If we're firing and changing between critical & noncritical, just need to change the firing loop.
		// Set crossfade time to zero so we skip the start sound and go to the loop immediately.

		flCrossfadeTime = 0;
		StopFlame( true );
	}

	StopPilotLight();

	if ( !m_pFiringStartSound && !m_pFiringLoop )
	{
		// NVNT if the local player is owning this weapon, process the start event
		if ( C_BasePlayer::GetLocalPlayer() == GetOwner() && haptics )
			haptics->ProcessHapticEvent(2,"Weapons","flamer_start");

		RestartParticleEffect();
		CLocalPlayerFilter filter;

		// Play the fire start sound
		const char *shootsound = GetShootSound( SINGLE );
		if ( flCrossfadeTime > 0.0 )
		{
			// play the firing start sound and fade it out
			m_pFiringStartSound = controller.SoundCreate( filter, entindex(), shootsound );		
			controller.Play( m_pFiringStartSound, 1.0, 100 );
			controller.SoundChangeVolume( m_pFiringStartSound, 0.0, flCrossfadeTime );
		}

		// Start the fire sound loop and fade it in
		if ( m_bCritFire )
		{
			shootsound = GetShootSound( BURST );
		}
		else
		{
			shootsound = GetShootSound( SPECIAL1 );
		}
		m_pFiringLoop = controller.SoundCreate( filter, entindex(), shootsound );
		m_bFiringLoopCritical = m_bCritFire;

		// play the firing loop sound and fade it in
		if ( flCrossfadeTime > 0.0 )
		{
			controller.Play( m_pFiringLoop, 0.0, 100 );
			controller.SoundChangeVolume( m_pFiringLoop, 1.0, flCrossfadeTime );
		}
		else
		{
			controller.Play( m_pFiringLoop, 1.0, 100 );
		}
	}

	// check our "hit" sound
	if ( m_bHitTarget != m_bFiringHitTarget )
	{
		if ( m_bHitTarget == false )
		{
			StopHitSound();
		}
		else
		{
			char *pchFireHitSound = "Weapon_FlameThrower.FireHit";

			int iParticleEffectIndex = 0;
			CALL_ATTRIB_HOOK_INT( iParticleEffectIndex, set_weapon_mode );

			if ( iParticleEffectIndex == 3 )
			{
				pchFireHitSound = "Weapon_Rainblower.FireHit";
			}

			CLocalPlayerFilter filter;
			m_pFiringHitLoop = controller.SoundCreate( filter, entindex(), pchFireHitSound );	
			controller.Play( m_pFiringHitLoop, 1.0, 100 );
		}

		m_bFiringHitTarget = m_bHitTarget;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFFlameThrower::StopHitSound()
{
	if ( m_pFiringHitLoop )
	{
		CSoundEnvelopeController::GetController().SoundDestroy( m_pFiringHitLoop );
		m_pFiringHitLoop = NULL;
	}

	m_bHitTarget = m_bFiringHitTarget = false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFFlameThrower::StopFlame( bool bAbrupt /* = false */ )
{
	if ( ( m_pFiringLoop || m_pFiringStartSound ) && !bAbrupt )
	{
		// play a quick wind-down poof when the flame stops
		CLocalPlayerFilter filter;
		const char *shootsound = GetShootSound( SPECIAL3 );
		EmitSound( filter, entindex(), shootsound );
	}

	if ( m_pFiringLoop )
	{
		CSoundEnvelopeController::GetController().SoundDestroy( m_pFiringLoop );
		m_pFiringLoop = NULL;
	}

	if ( m_pFiringStartSound )
	{
		CSoundEnvelopeController::GetController().SoundDestroy( m_pFiringStartSound );
		m_pFiringStartSound = NULL;
	}

	if ( m_FlameEffects.StopEffects() )
	{
		C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
		if ( pLocalPlayer && pLocalPlayer == GetOwner() )
		{
			// NVNT local player is finished firing. send the stop event.
			if ( haptics )
				haptics->ProcessHapticEvent(2,"Weapons","flamer_stop");
		}
	}

	if ( !bAbrupt )
	{
		StopHitSound();
	}

	m_iParticleWaterLevel = -1;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFFlameThrower::StartPilotLight()
{
	if ( !m_pPilotLightSound )
	{
		StopFlame();

		// Create the looping pilot light sound
		const char *pilotlightsound = GetShootSound( SPECIAL2 );
		CLocalPlayerFilter filter;

		CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
		m_pPilotLightSound = controller.SoundCreate( filter, entindex(), pilotlightsound );

		controller.Play( m_pPilotLightSound, 1.0, 100 );
	}	
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFFlameThrower::StopPilotLight()
{
	if ( m_pPilotLightSound )
	{
		CSoundEnvelopeController::GetController().SoundDestroy( m_pPilotLightSound );
		m_pPilotLightSound = NULL;
	}
}

void CTFFlameThrower::StopFullCritEffect()
{
	m_bFullRageEffect = false;

	m_MmmmphEffect.StopEffects();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFFlameThrower::RestartParticleEffect( void )
{
	CTFPlayer *pOwner = GetTFPlayerOwner();
	if ( !pOwner )
		return;

	if ( m_iWeaponState != FT_STATE_FIRING && m_iWeaponState != FT_STATE_STARTFIRING )
	{
		return;
	}

	m_iParticleWaterLevel = pOwner->GetWaterLevel();

	bool bIsFirstPersonView = IsFirstPersonView();

	// Start the appropriate particle effect
	const char *pszParticleEffect;
	if ( pOwner->GetWaterLevel() == WL_Eyes )
	{
		pszParticleEffect = "flamethrower_underwater";
	}
	else
	{
		if ( m_bCritFire )
		{
			pszParticleEffect = FlameCritEffectName( bIsFirstPersonView );
		}
		else 
		{
			pszParticleEffect = FlameEffectName( bIsFirstPersonView );
		}
	}

	m_FlameEffects.StartEffects( pszParticleEffect );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
const char* CTFFlameThrower::FlameEffectName( bool bIsFirstPersonView )
{
	CTFPlayer *pOwner = GetTFPlayerOwner();
	if ( !pOwner )
		return NULL;

#ifdef STAGING_ONLY
	if ( ShootsNapalm() )
	{
		return "muzzle_bignasty";
	}
#endif // STAGING_ONLY

	// Halloween Spell
	if ( m_bHasHalloweenSpell )
	{
		return "flamethrower_halloween";
	}

	int iParticleEffectIndex = 0;
	CALL_ATTRIB_HOOK_INT( iParticleEffectIndex, set_weapon_mode );

	switch ( iParticleEffectIndex )
	{
	case 1:		return "drg_phlo_stream";
	case 2:		return "flamethrower_giant_mvm";
	case 3:		return ( bIsFirstPersonView ? "flamethrower_rainbow_FP" : "flamethrower_rainbow" );
	default:	return ( pOwner->GetTeamNumber() == TF_TEAM_BLUE ? "flamethrower_blue" : "flamethrower" );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
const char* CTFFlameThrower::FlameCritEffectName( bool bIsFirstPersonView )
{
	CTFPlayer *pOwner = GetTFPlayerOwner();
	if ( !pOwner )
		return NULL;

#ifdef STAGING_ONLY
	if ( ShootsNapalm() )
	{
		return "muzzle_bignasty";
	}
#endif // STAGING_ONLY

	// Halloween Spell
	if ( m_bHasHalloweenSpell )
	{
		return ( pOwner->GetTeamNumber() == TF_TEAM_BLUE ? "flamethrower_halloween_crit_blue" : "flamethrower_halloween_crit_red" );
	}

	int iParticleEffectIndex = 0;
	CALL_ATTRIB_HOOK_INT( iParticleEffectIndex, set_weapon_mode );

	switch ( iParticleEffectIndex )
	{
	case 1:		return "drg_phlo_stream_crit";
	case 2:		return "flamethrower_crit_giant_mvm";
	case 3:		return ( bIsFirstPersonView ? "flamethrower_rainbow_FP" : "flamethrower_rainbow" );
	default:	return ( pOwner->GetTeamNumber() == TF_TEAM_BLUE ? "flamethrower_crit_blue" : "flamethrower_crit_red" );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
const char* CTFFlameThrower::FullCritChargedEffectName( void )
{
	switch( GetTeamNumber() )
	{
	case TF_TEAM_BLUE:	return "medicgun_invulnstatus_fullcharge_blue";
	case TF_TEAM_RED:	return "medicgun_invulnstatus_fullcharge_red";
	default:			return "";
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFFlameThrower::ClientEffectsThink( void )
{
	CTFPlayer *pPlayer = GetTFPlayerOwner();
	if ( !pPlayer )
		return;

	if ( !pPlayer->IsLocalPlayer() )
		return;

	if ( !pPlayer->GetViewModel() )
		return;

	if ( !m_bEffectsThinking )
		return;

	float flRageInverse = 1.f;

	if ( GetBuffType() > 0 )
	{
		flRageInverse = 1.0f - ( pPlayer->m_Shared.GetRageMeter() / 100.0f );
		if ( flRageInverse < 1.0f )
		{
			// We have some rage, let's spark!
			ParticleProp()->Init( this );
			CNewParticleEffect* pEffect = ParticleProp()->Create( "drg_bison_idle", PATTACH_POINT_FOLLOW, "muzzle" );
			if ( pEffect )
			{
				pEffect->SetControlPoint( CUSTOM_COLOR_CP1, GetParticleColor( 1 ) );
				pEffect->SetControlPoint( CUSTOM_COLOR_CP2, GetParticleColor( 2 ) );
			}
		}
	}

	SetContextThink( &CTFFlameThrower::ClientEffectsThink, gpGlobals->curtime + 0.1f + RandomFloat( 1.0f, 5.0f ) * flRageInverse, "EFFECTS_THINK" );
}

void CTFFlameThrower::FlameEffect_t::StartEffects( const char* pszEffectName )
{
	// Stop any old flame effects
	StopEffects();

	// Figure out which weapon this flame effect is to be attached to.  Store this for
	// later so we know which weapon to deactivate the effect on
	m_hEffectWeapon = m_pOwner->GetWeaponForEffect();

	if( m_hEffectWeapon )
	{
		CParticleProperty* pParticleProp = m_hEffectWeapon->ParticleProp();
		if( pParticleProp )
		{
			// Flame on
			m_pFlameEffect = pParticleProp->Create( pszEffectName, PATTACH_POINT_FOLLOW, "muzzle" );
		}
	}
}

bool CTFFlameThrower::FlameEffect_t::StopEffects()
{
	bool bStopped = false;
	// Stop any old flame effects
	if ( m_pFlameEffect && m_hEffectWeapon )
	{
		m_hEffectWeapon->ParticleProp()->StopEmission( m_pFlameEffect );
		bStopped = true;
	}

	m_pFlameEffect = NULL;
	m_hEffectWeapon = NULL;

	return bStopped;
}

#else

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFFlameThrower::HitTargetThink( void )
{
	if ( ( m_flTimeToStopHitSound > 0 ) && ( m_flTimeToStopHitSound < gpGlobals->curtime ) )
	{
		m_bHitTarget = false;
		m_flTimeToStopHitSound = 0;
		SetContextThink( NULL, 0, s_pszFlameThrowerHitTargetThink );
		return;
	}

	SetNextThink( gpGlobals->curtime + 0.1f, s_pszFlameThrowerHitTargetThink );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFFlameThrower::SetHitTarget( void )
{ 
	if ( m_iWeaponState > FT_STATE_IDLE )
	{
		m_bHitTarget = true;
		m_flTimeToStopHitSound = gpGlobals->curtime + 0.2;

		// Start the hit target thinking
		SetContextThink( &CTFFlameThrower::HitTargetThink, gpGlobals->curtime + 0.1f, s_pszFlameThrowerHitTargetThink );
	}
}

#endif

#ifdef STAGING_ONLY
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTFFlameThrower::RocketPackCanActivate( int nAmmoCost )
{
	CTFPlayer *pOwner = GetTFPlayerOwner();
	if ( !pOwner )
		return false;

	int nRocketPack = 0;
	CALL_ATTRIB_HOOK_INT_ON_OTHER( pOwner, nRocketPack, rocket_pack );
	if ( !nRocketPack )
		return false;

	if ( pOwner->m_Shared.IsLoser() )
		return false;

	if ( pOwner->m_Shared.InCond( TF_COND_STUNNED ) )
		return false;

	if ( pOwner->IsTaunting() )
		return false;

// 	if ( pOwner->m_Shared.GetChargeMeter() < 100.f )
// 		return false;

	if ( pOwner->GetAmmoCount( TF_AMMO_PRIMARY ) < nAmmoCost )
		return false;

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTFFlameThrower::RocketPackLaunch( int nAmmoCost )
{
	CTFPlayer *pOwner = GetTFPlayerOwner();
	if ( !pOwner )
		return false;

#ifdef CLIENT_DLL
	StopFlame( false );
#endif // CLIENT_DLL

#ifdef GAME_DLL
	// Launch
	if ( !pOwner->m_Shared.InCond( TF_COND_ROCKETPACK ) )
	{
		pOwner->m_Shared.AddCond( TF_COND_ROCKETPACK );
		pOwner->m_Shared.StunPlayer( 0.5f, 1.0f, TF_STUN_MOVEMENT );
	}

	Vector vecDir;
	pOwner->EyeVectors( &vecDir );
	pOwner->SetAbsVelocity( vec3_origin );
	Vector vecFlightDir = -vecDir;
	VectorNormalize( vecFlightDir );
	float flForce = 450.f;

	const float flPushScale = ( pOwner->GetFlags() & FL_ONGROUND ) ? 1.2f : 1.8f;		// Greater force while airborne
	const float flVertPushScale = ( pOwner->GetFlags() & FL_ONGROUND ) ? 1.2f : 0.25f;	// Less vertical force while airborne
	Vector vecForce = vecFlightDir * -flForce * flPushScale;
	vecForce.z += 1.f * flForce * flVertPushScale;
	pOwner->RemoveFlag( FL_ONGROUND );
	pOwner->ApplyAbsVelocityImpulse( vecForce );

	m_flNextSecondaryAttack = gpGlobals->curtime + 0.75f;
	m_flNextPrimaryAttack = gpGlobals->curtime + 1.f;
	m_flResetBurstEffect = gpGlobals->curtime + 0.05f;
	m_bFiredSecondary = true;
	m_flChargeBeginTime = 0;

	pOwner->RemoveAmmo( nAmmoCost, m_iPrimaryAmmoType );
	pOwner->EmitSound( "Equipment.RocketPack_Activate" );
#endif // GAME_DLL

#ifdef CLIENT_DLL
	if ( prediction->IsFirstTimePredicted() == true )
	{
		StartFlame();
	}
#endif // CLIENT_DLL

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTFFlameThrower::ShootsNapalm( void )
{
	int iNapalm = 0;
	CALL_ATTRIB_HOOK_INT_ON_OTHER( GetOwnerEntity(), iNapalm, mod_flamethrower_napalm );
	return ( iNapalm > 0 );
}
#endif // STAGING_ONLY

IMPLEMENT_NETWORKCLASS_ALIASED( TFFlameRocket, DT_TFFlameRocket )
BEGIN_NETWORK_TABLE( CTFFlameRocket, DT_TFFlameRocket )
END_NETWORK_TABLE()

#ifdef GAME_DLL
LINK_ENTITY_TO_CLASS( tf_flame, CTFFlameEntity );
IMPLEMENT_AUTO_LIST( ITFFlameEntityAutoList );

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CTFFlameEntity::CTFFlameEntity()
{}

//-----------------------------------------------------------------------------
// Purpose: Spawns this entity
//-----------------------------------------------------------------------------
void CTFFlameEntity::Spawn( void )
{
	BaseClass::Spawn();

	// don't collide with anything, we do our own collision detection in our think method
	SetSolid( SOLID_NONE );
	SetSolidFlags( FSOLID_NOT_SOLID );
	SetCollisionGroup( COLLISION_GROUP_NONE );
	// move noclip: update position from velocity, that's it
	SetMoveType( MOVETYPE_NOCLIP, MOVECOLLIDE_DEFAULT );
	AddEFlags( EFL_NO_WATER_VELOCITY_CHANGE );

	float iBoxSize = tf_flamethrower_boxsize.GetFloat();
	CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetOwnerEntity(), iBoxSize, mult_flame_size );
	UTIL_SetSize( this, -Vector( iBoxSize, iBoxSize, iBoxSize ), Vector( iBoxSize, iBoxSize, iBoxSize ) );

	// Setup attributes.
	m_takedamage = DAMAGE_NO;
	m_vecInitialPos = GetAbsOrigin();
	m_vecPrevPos = m_vecInitialPos;

	// Track total active flame entities
	m_hFlameThrower = dynamic_cast< CTFFlameThrower* >( GetOwnerEntity() );
	if ( m_hFlameThrower )
	{
		m_hFlameThrower->IncrementActiveFlameCount();
		m_bBurnedEnemy = false;

		float flFlameLife = tf_flamethrower_flametime.GetFloat();
		CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetOwnerEntity(), flFlameLife, mult_flame_life );
		m_flTimeRemove = gpGlobals->curtime + ( flFlameLife * random->RandomFloat( 0.9f, 1.1f ) );
	}
	else
	{
		m_flTimeRemove = gpGlobals->curtime + 3.f;
	}

	// Setup the think function.
	SetThink( &CTFFlameEntity::FlameThink );
	SetNextThink( gpGlobals->curtime );
}

//-----------------------------------------------------------------------------
// Purpose: Creates an instance of this entity
//-----------------------------------------------------------------------------
CTFFlameEntity *CTFFlameEntity::Create( const Vector &vecOrigin, const QAngle &vecAngles, CBaseEntity *pOwner, float flSpeed, int iDmgType, float flDmgAmount, bool bAlwaysCritFromBehind, bool bRandomize )
{
	CTFFlameEntity *pFlame = static_cast<CTFFlameEntity*>( CBaseEntity::Create( "tf_flame", vecOrigin, vecAngles, pOwner ) );
	if ( !pFlame )
		return NULL;

	// Initialize the owner.
	pFlame->SetOwnerEntity( pOwner );
	if ( pOwner->GetOwnerEntity() )
		pFlame->m_hAttacker = pOwner->GetOwnerEntity();
	else
		pFlame->m_hAttacker = pOwner;
	CBaseEntity *pAttacker = (CBaseEntity *) pFlame->m_hAttacker;
	if ( pAttacker )
	{
		pFlame->m_iAttackerTeam = pAttacker->GetTeamNumber();
	}

	// Set team.
	pFlame->ChangeTeam( pOwner->GetTeamNumber() );
	pFlame->m_iDmgType = iDmgType;
	pFlame->m_flDmgAmount = flDmgAmount;

	// Setup the initial velocity.
	Vector vecForward, vecRight, vecUp;
	AngleVectors( vecAngles, &vecForward, &vecRight, &vecUp );

	float flFlameLifeMult = 1.0f;
	CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pFlame->m_hAttacker, flFlameLifeMult, mult_flame_life );
	float velocity = flFlameLifeMult * flSpeed;
	pFlame->m_vecBaseVelocity = vecForward * velocity;
	float iFlameSizeMult = 1.0f;
	CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pFlame->m_hAttacker, iFlameSizeMult, mult_flame_size );
	if ( bRandomize )
	{
		pFlame->m_vecBaseVelocity += RandomVector( -velocity * iFlameSizeMult * tf_flamethrower_vecrand.GetFloat(), velocity * iFlameSizeMult * tf_flamethrower_vecrand.GetFloat() );
	}
	if ( pOwner->GetOwnerEntity() )
	{
		pFlame->m_vecAttackerVelocity = pOwner->GetOwnerEntity()->GetAbsVelocity();
	}
	pFlame->SetAbsVelocity( pFlame->m_vecBaseVelocity );	
	// Setup the initial angles.
	pFlame->SetAbsAngles( vecAngles );
	pFlame->SetCritFromBehind( bAlwaysCritFromBehind );

	return pFlame;
}

//-----------------------------------------------------------------------------
class CFlameEntityEnum : public IEntityEnumerator
{
public:
	CFlameEntityEnum( CBaseEntity *pShooter )
	{
		m_pShooter = pShooter;
	}

	virtual bool EnumEntity( IHandleEntity *pHandleEntity )
	{
		CBaseEntity *pEnt = static_cast<CBaseEntity*>( pHandleEntity );

		// Ignore collisions with the shooter
		if ( pEnt == m_pShooter )
			return true;

		if ( pEnt->IsPlayer() && pEnt->IsAlive() )
		{
			m_Targets.AddToTail( pEnt );
		}
		else if ( pEnt->MyNextBotPointer() && pEnt->IsAlive() )
		{
			// add non-player bots
			m_Targets.AddToTail( pEnt );
		}
		else if ( pEnt->IsBaseObject() && m_pShooter->GetTeamNumber() != pEnt->GetTeamNumber() )
		{
			// only add enemy objects
			m_Targets.AddToTail( pEnt );
		}
		else if ( CTFRobotDestructionLogic::GetRobotDestructionLogic() && m_pShooter->GetTeamNumber() != pEnt->GetTeamNumber() && FClassnameIs( pEnt, "tf_robot_destruction_robot" ) )
		{
			// only add enemy robots
			m_Targets.AddToTail( pEnt );
		}
		else if ( FClassnameIs( pEnt, "func_breakable" ) || FClassnameIs( pEnt, "tf_pumpkin_bomb" ) || FClassnameIs( pEnt, "tf_merasmus_trick_or_treat_prop" ) )
		{
			m_Targets.AddToTail( pEnt );
		}

		return true;
	}

	const CUtlVector< CBaseEntity* >& GetTargets() { return m_Targets; }

public:
	Ray_t		*m_pRay;
	CBaseEntity	*m_pShooter;
	CUtlVector< CBaseEntity* > m_Targets;
};

//-----------------------------------------------------------------------------
// Purpose: Think method
//-----------------------------------------------------------------------------
void CTFFlameEntity::FlameThink( void )
{
	TM_ZONE_DEFAULT( TELEMETRY_LEVEL0 )
	// if we've expired, remove ourselves
	if ( gpGlobals->curtime >= m_flTimeRemove )
	{
		RemoveFlame();
		return;
	}
	else
	{
		// Always think, if we haven't died due to our timeout.
		SetNextThink( gpGlobals->curtime );
	}

	// Did we move? should we check collision?
	if ( GetAbsOrigin() != m_vecPrevPos )
	{
		tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s Collision", __FUNCTION__ );
		CTFPlayer *pAttacker = dynamic_cast<CTFPlayer *>( (CBaseEntity *) m_hAttacker );
		if ( !pAttacker )
			return;

		// Create a ray for flame entity to trace
		Ray_t rayWorld;
		rayWorld.Init( m_vecInitialPos, GetAbsOrigin(), WorldAlignMins(), WorldAlignMaxs() );

		// check against world first
		// if we collide with world, just destroy the flame
		trace_t trWorld;
		UTIL_TraceRay( rayWorld, MASK_SOLID, this, COLLISION_GROUP_DEBRIS, &trWorld );

		bool bHitWorld = trWorld.startsolid || trWorld.fraction < 1.f;

		// update the ray
		Ray_t rayEnt;
		rayEnt.Init( m_vecPrevPos, GetAbsOrigin(), WorldAlignMins(), WorldAlignMaxs() );

		// burn all entities that we should collide with
		CFlameEntityEnum eFlameEnum( pAttacker );
		enginetrace->EnumerateEntities( rayEnt, false, &eFlameEnum );

		bool bHitSomething = false;
		FOR_EACH_VEC( eFlameEnum.GetTargets(), i )
		{
			CBaseEntity *pEnt = eFlameEnum.GetTargets()[i];

			// skip ent that's already burnt by this flame
			int iIndex = m_hEntitiesBurnt.Find( pEnt );
			if ( iIndex != m_hEntitiesBurnt.InvalidIndex() )
				continue;

			// if we're removing the flame this frame from hitting world, check if we hit this ent before hitting the world
			if ( bHitWorld )
			{
				trace_t trEnt;
				enginetrace->ClipRayToEntity( rayWorld, MASK_SOLID | CONTENTS_HITBOX, pEnt, &trEnt );
				// hit world before this ent, skip it
				if ( trEnt.fraction >= trWorld.fraction )
					continue;
			}

			// burn them all!
			if ( pEnt->IsPlayer() && pEnt->InSameTeam( pAttacker ) )
			{
				OnCollideWithTeammate( ToTFPlayer( pEnt ) );
			}
			else
			{
				OnCollide( pEnt );
			}

			bHitSomething = true;
		}

		// now, let's see if the flame visual could have actually hit this player.  Trace backward from the
		// point of impact to where the flame was fired, see if we hit anything.
		if ( bHitSomething && tf_debug_flamethrower.GetBool() )
		{
			NDebugOverlay::SweptBox( m_vecPrevPos, GetAbsOrigin(), WorldAlignMins(), WorldAlignMaxs(), vec3_angle, 255, 255, 0, 100, 5.0 );
			NDebugOverlay::EntityBounds( this, 255, 255, 0, 100, 5.0 );
		}

		// remove the flame if it hits the world
		if ( bHitWorld )
		{
			if ( tf_debug_flamethrower.GetInt() )
			{
				NDebugOverlay::SweptBox( m_vecInitialPos, GetAbsOrigin(), WorldAlignMins(), WorldAlignMaxs(), vec3_angle, 255, 0, 0, 100, 3.0 );
			}

			RemoveFlame();
		}
	}

	// Reduce our base velocity by the air drag constant
	m_vecBaseVelocity *= GetFlameDrag();

	// Add our float upward velocity
	Vector vecVelocity = m_vecBaseVelocity + Vector( 0, 0, GetFlameFloat() ) + m_vecAttackerVelocity;

	// Update our velocity
	SetAbsVelocity( vecVelocity );
	
	// Render debug visualization if convar on
	if ( tf_debug_flamethrower.GetInt() )
	{
		if ( m_hEntitiesBurnt.Count() > 0 )
		{
			int val = ( (int) ( gpGlobals->curtime * 10 ) ) % 255;
			NDebugOverlay::EntityBounds(this, val, 255, val, 0 ,0 );
		} 
		else 
		{
			NDebugOverlay::EntityBounds(this, 0, 100, 255, 0 ,0) ;
		}
	}

	m_vecPrevPos = GetAbsOrigin();
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFFlameEntity::SetHitTarget( void )
{
	if ( !m_hFlameThrower )
		return;

	m_hFlameThrower->SetHitTarget();
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFFlameEntity::RemoveFlame()
{
	UpdateFlameThrowerHitRatio();

	UTIL_Remove( this );
}


//-----------------------------------------------------------------------------
// Purpose: Called when we've collided with another entity
//-----------------------------------------------------------------------------
void CTFFlameEntity::OnCollide( CBaseEntity *pOther )
{
	int nContents = UTIL_PointContents( GetAbsOrigin() );
	if ( (nContents & MASK_WATER) )
	{
		RemoveFlame();
		return;
	}

	// remember that we've burnt this player
	m_hEntitiesBurnt.AddToTail( pOther );
	
	float flDistance = GetAbsOrigin().DistTo( m_vecInitialPos );
	float flDamage = m_flDmgAmount * RemapValClamped( flDistance, tf_flamethrower_maxdamagedist.GetFloat()/2, tf_flamethrower_maxdamagedist.GetFloat(), 1.0f, 0.70f );

	flDamage = MAX( flDamage, 1.0 );
	if ( tf_debug_flamethrower.GetInt() )
	{
		Msg( "Flame touch dmg: %.1f\n", flDamage );
	}

	CBaseEntity *pAttacker = m_hAttacker;
	if ( !pAttacker )
		return;

	SetHitTarget();

	int iDamageType = m_iDmgType;

	if ( pOther && pOther->IsPlayer() )
	{
		CTFPlayer *pVictim = ToTFPlayer( pOther );
		if ( IsBehindTarget( pOther ) )
		{
			if ( m_bCritFromBehind == true )
			{
				iDamageType |= DMG_CRITICAL;
			}

			if ( pVictim )
			{
				pVictim->HandleAchievement_Pyro_BurnFromBehind( ToTFPlayer( pAttacker ) );
			}
		}

		// Pyro-specific
		if ( pAttacker->IsPlayer() && pVictim )
		{
			CTFPlayer *pPlayerAttacker = ToTFPlayer( pAttacker );
			if ( pPlayerAttacker && pPlayerAttacker->IsPlayerClass( TF_CLASS_PYRO ) )
			{
				// burn the victim while taunting?
				if ( pVictim->m_Shared.InCond( TF_COND_TAUNTING ) )
				{
					static CSchemaItemDefHandle flipTaunt( "Flippin' Awesome Taunt" );
					// if I'm the one being flipped, and getting lit on fire
					if ( !pVictim->IsTauntInitiator() && pVictim->GetTauntEconItemView() && pVictim->GetTauntEconItemView()->GetItemDefinition() == flipTaunt )
					{
						pPlayerAttacker->AwardAchievement( ACHIEVEMENT_TF_PYRO_IGNITE_PLAYER_BEING_FLIPPED );
					}
				}

				pVictim->m_Shared.AddCond( TF_COND_HEALING_DEBUFF, 2.f, pAttacker );
			}
		}
	}

	CTakeDamageInfo info( GetOwnerEntity(), pAttacker, GetOwnerEntity(), flDamage, iDamageType, TF_DMG_CUSTOM_BURNING );
	info.SetReportedPosition( pAttacker->GetAbsOrigin() );

	if ( info.GetDamageType() & DMG_CRITICAL )
	{
		info.SetCritType( CTakeDamageInfo::CRIT_FULL );
	}

	// terrible hack for flames hitting the Merasmus props to get the particle effect in the correct position
	if ( TFGameRules() && TFGameRules()->GetActiveBoss() && ( TFGameRules()->GetActiveBoss()->GetBossType() == HALLOWEEN_BOSS_MERASMUS ) )
	{
		info.SetDamagePosition( GetAbsOrigin() );
	}

	// Track hits for the Flamethrower, which is used to change the weapon sound based on hit ratio
	if ( m_hFlameThrower )
	{
		m_bBurnedEnemy = true;
		m_hFlameThrower->IncrementFlameDamageCount();
	}

	// We collided with pOther, so try to find a place on their surface to show blood
	trace_t pTrace;
	UTIL_TraceLine( WorldSpaceCenter(), pOther->WorldSpaceCenter(), MASK_SOLID|CONTENTS_HITBOX, this, COLLISION_GROUP_NONE, &pTrace );

	pOther->DispatchTraceAttack( info, GetAbsVelocity(), &pTrace );
	ApplyMultiDamage();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFFlameEntity::OnCollideWithTeammate( CTFPlayer *pPlayer )
{
	// Only care about Snipers
	if ( !pPlayer->IsPlayerClass(TF_CLASS_SNIPER) )
		return;

	int iIndex = m_hEntitiesBurnt.Find( pPlayer );
	if ( iIndex != m_hEntitiesBurnt.InvalidIndex() )
		return;

	m_hEntitiesBurnt.AddToTail( pPlayer );

	// Does he have the bow?
	CTFWeaponBase *pWpn = pPlayer->GetActiveTFWeapon();
	if ( pWpn && pWpn->GetWeaponID() == TF_WEAPON_COMPOUND_BOW )
	{
		CTFCompoundBow *pBow = static_cast<CTFCompoundBow*>( pWpn );
		pBow->SetArrowAlight( true );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTFFlameEntity::IsBehindTarget( CBaseEntity *pTarget )
{
	return ( DotProductToTarget( pTarget ) > 0.8 );
}

//-----------------------------------------------------------------------------
// Purpose: Utility to calculate dot product between facing angles of flame and target
//-----------------------------------------------------------------------------
float CTFFlameEntity::DotProductToTarget( CBaseEntity *pTarget )
{
	Assert( pTarget );

	// Get the forward view vector of the target, ignore Z
	Vector vecVictimForward;
	AngleVectors( pTarget->EyeAngles(), &vecVictimForward, NULL, NULL );
	vecVictimForward.z = 0.0f;
	vecVictimForward.NormalizeInPlace();

	Vector vecTraveling = m_vecBaseVelocity;
	vecTraveling.z = 0.0f;
	vecTraveling.NormalizeInPlace();

	return DotProduct( vecVictimForward, vecTraveling );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFFlameEntity::UpdateFlameThrowerHitRatio( void )
{
	if ( !m_hFlameThrower )
		return;
	
	if ( m_bBurnedEnemy )
	{
		m_hFlameThrower->DecrementFlameDamageCount();
	}

	m_hFlameThrower->DecrementActiveFlameCount();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
float CTFFlameEntity::GetFlameFloat( void )
{
	return tf_flamethrower_float.GetFloat();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
float CTFFlameEntity::GetFlameDrag( void )
{
	return tf_flamethrower_drag.GetFloat();
}

#endif // GAME_DLL

#ifdef STAGING_ONLY
//-----------------------------------------------------------------------------
// Purpose: Napalm
//-----------------------------------------------------------------------------
#ifdef GAME_DLL
#define NAPALM_THINK_CONTEXT	"CTFMedigunShield_ShieldThink"
#endif // GAME_DLL

LINK_ENTITY_TO_CLASS( tf_projectile_napalm, CTFProjectile_Napalm );

IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_Napalm, DT_TFProjectile_Napalm )

BEGIN_NETWORK_TABLE( CTFProjectile_Napalm, DT_TFProjectile_Napalm )
#ifdef GAME_DLL
	SendPropEHandle( SENDINFO( m_hFlameThrower ) ),
#else
	RecvPropEHandle( RECVINFO( m_hFlameThrower ) ),
#endif
END_NETWORK_TABLE()

BEGIN_PREDICTION_DATA( CTFProjectile_Napalm )
END_PREDICTION_DATA()

// Data
BEGIN_DATADESC( CTFProjectile_Napalm )
#ifdef GAME_DLL
DEFINE_THINKFUNC( NapalmThink ),
#endif // GAME_DLL
END_DATADESC()


//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CTFProjectile_Napalm::CTFProjectile_Napalm()
{
#ifdef GAME_DLL
	m_flRemoveTime = 0.f;
	m_nHitCount = 0;
	m_flLastBurnTime = 0.f;
#endif // GAME_DLL
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CTFProjectile_Napalm::~CTFProjectile_Napalm()
{
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFProjectile_Napalm::Precache()
{
	// PrecacheModel( TF_MODEL_NAPALM );
	PrecacheParticleSystem( "burninggibs" );
	PrecacheParticleSystem( "flaming_arrow" );
	
	PrecacheScriptSound( "Player.PlasmaDamage" );

	BaseClass::Precache();
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFProjectile_Napalm::Spawn()
{
	Precache();

#ifdef GAME_DLL
	m_flRemoveTime = gpGlobals->curtime + 2.2f;
	SetContextThink( &CTFProjectile_Napalm::NapalmThink, gpGlobals->curtime, NAPALM_THINK_CONTEXT );
#endif // GAME_DLL

	BaseClass::Spawn();

#ifdef GAME_DLL
	SetDetonateTimerLength( FLT_MAX );
#endif // GAME_DLL
}

#ifdef GAME_DLL
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CTFProjectile_Napalm *CTFProjectile_Napalm::Create( CBaseCombatCharacter *pOwner, CTFFlameThrower *pLauncher )
{
	if ( pOwner )
	{
		Vector vecForward;
		AngleVectors( pOwner->EyeAngles(), &vecForward, NULL, NULL );
		CTFProjectile_Napalm *pProjectile = static_cast< CTFProjectile_Napalm* >( CBaseEntity::Create( "tf_projectile_napalm", pLauncher->GetVisualMuzzlePos(), pOwner->EyeAngles() ) );
		if ( pProjectile )
		{
			pProjectile->ChangeTeam( pOwner->GetTeamNumber() );

			// Setup the initial velocity.
			float flVelocity = 1100.f;
			pProjectile->m_vecBaseVelocity = vecForward * flVelocity;
			pProjectile->m_vecBaseVelocity += RandomVector( -flVelocity * tf_flamethrower_vecrand.GetFloat(), flVelocity * tf_flamethrower_vecrand.GetFloat() );
			pProjectile->InitGrenade( pProjectile->m_vecBaseVelocity, vec3_origin, pOwner, pLauncher->GetTFWpnData() );
			pProjectile->m_hFlameThrower = pLauncher;

			return pProjectile;
		}
	}

	return NULL;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CTFProjectile_Napalm::UpdateTransmitState()
{
	return SetTransmitState( FL_EDICT_PVSCHECK );
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFProjectile_Napalm::NapalmThink( void )
{
	if ( gpGlobals->curtime > m_flRemoveTime )
	{
		SetContextThink( &CBaseGrenade::SUB_Remove, gpGlobals->curtime, "RemoveThink" );
		SetTouch( NULL );
		return;
	}

	SetContextThink( &CTFProjectile_Napalm::NapalmThink, gpGlobals->curtime + 0.1f, NAPALM_THINK_CONTEXT );
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFProjectile_Napalm::Explode( trace_t *pTrace, int bitsDamageType )
{
	if ( !m_nHitCount )
	{
		SetModelName( NULL_STRING );
		AddSolidFlags( FSOLID_TRIGGER );

		m_takedamage = DAMAGE_NO;

		// Pull out of the wall a bit.
		if ( pTrace->fraction != 1.f )
		{
			SetAbsOrigin( pTrace->endpos + ( pTrace->plane.normal * 1.0f ) );
		}

		CTFPlayer *pThrower = ToTFPlayer( GetThrower() );
		if ( pThrower )
		{
			const Vector& vecOrigin = GetAbsOrigin();

			// Any effects from the initial explosion
			if ( InitialExplodeEffects( pThrower, pTrace ) )
			{
				// Particle
				if ( GetImpactEffect() )
				{	
					CPVSFilter filter( vecOrigin );

					// Stick effect on the player
					CBaseEntity *pEnt = pTrace->m_pEnt;
					if ( pEnt && pEnt->IsPlayer() && !pThrower->InSameTeam( pEnt ) )
					{
						// TE_TFParticleEffect( filter, 0.f, GetImpactEffect(), pEnt->GetAbsOrigin(), vec3_angle, pEnt, PATTACH_ABSORIGIN_FOLLOW );
						m_flRemoveTime = gpGlobals->curtime;
					}
					// World
					else
					{
						TE_TFParticleEffect( filter, 0.0, GetImpactEffect(), vecOrigin, vec3_angle );
					}
				}

				// Sounds
				// EmitSound( "Player.PlasmaDamage" );

				// Treat this trace exactly like radius damage
				CTraceFilterIgnorePlayers traceFilter( pThrower, COLLISION_GROUP_PROJECTILE );

				// Burn players in impact range
				CBaseEntity *pListOfEntities[32];
				int iEntities = UTIL_EntitiesInSphere( pListOfEntities, 32, vecOrigin, GetDamageRadius(), FL_CLIENT | FL_FAKECLIENT | FL_NPC );
				for ( int i = 0; i < iEntities; ++i )
				{
					if ( pThrower->InSameTeam( pListOfEntities[i] ) )
						continue;

					CBaseCombatCharacter *pBaseCombatCharacter = NULL;
					CTFPlayer *pPlayer = ToTFPlayer( pListOfEntities[i] );
					if ( !pPlayer )
					{
						pBaseCombatCharacter = dynamic_cast< CBaseCombatCharacter* >( pListOfEntities[i] );
					}
					else
					{
						pBaseCombatCharacter = pPlayer;
					}

					if ( !pBaseCombatCharacter || !pBaseCombatCharacter->IsAlive() )
						continue;

					// Do a quick trace to see if there's any geometry in the way.
					trace_t pImpactTrace;
					UTIL_TraceLine( GetAbsOrigin(), pBaseCombatCharacter->GetAbsOrigin(), ( MASK_SHOT & ~( CONTENTS_HITBOX ) ), &traceFilter, &pImpactTrace );
					if ( pImpactTrace.DidHitWorld() )
						continue;

					// Effects on the individual players
					ExplodeEffectOnTarget( pThrower, pPlayer, pBaseCombatCharacter );
				}

				ApplyBlastDamage( pThrower, vecOrigin );
			}
		}

		AddEffects( EF_NODRAW );
		SetAbsVelocity( vec3_origin );
		SetMoveType( MOVETYPE_NONE );
	}

	m_nHitCount++;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFProjectile_Napalm::PipebombTouch( CBaseEntity *pOther )
{
	if ( gpGlobals->curtime - m_flLastBurnTime < 0.15f )
		return;

	if ( InSameTeam( pOther ) )
		return;

	if ( !m_hFlameThrower || !m_hFlameThrower->GetOwnerEntity() )
		return;

	CTakeDamageInfo info;
	info.SetAttacker( m_hFlameThrower->GetOwnerEntity() );
	info.SetInflictor( m_hFlameThrower ); 
	info.SetWeapon( m_hFlameThrower );
	info.SetDamage( 2.f );
	info.SetDamageCustom( GetCustomDamageType() );
	info.SetDamagePosition( GetAbsOrigin() );
	info.SetDamageType( DMG_BURN );
	pOther->TakeDamage( info );

	m_flLastBurnTime = gpGlobals->curtime;

	BaseClass::PipebombTouch( pOther );
}

//-----------------------------------------------------------------------------
// Purpose: Radius damage
//-----------------------------------------------------------------------------
void CTFProjectile_Napalm::ApplyBlastDamage( CTFPlayer *pThrower, Vector vecOrigin )
{
	CTakeDamageInfo info;
	info.SetAttacker( pThrower );
	info.SetInflictor( this ); 
	info.SetWeapon( m_hFlameThrower );
	info.SetDamage( 25.f );
	info.SetDamageCustom( GetCustomDamageType() );
	info.SetDamagePosition( vecOrigin );
	info.SetDamageType( DMG_BURN );

	CTFRadiusDamageInfo radiusinfo( &info, vecOrigin, 100.f, pThrower );
	TFGameRules()->RadiusDamage( radiusinfo );
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFProjectile_Napalm::InitialExplodeEffects( CTFPlayer *pThrower, const trace_t *pTrace )
{
	// Added Particle
	Vector vecOrigin = GetAbsOrigin();
	// Particle
	CPVSFilter filter( vecOrigin );
	TE_TFExplosion( filter, 0.0f, vecOrigin, pTrace->plane.normal, TF_WEAPON_FLAMETHROWER, kInvalidEHandleExplosion, -1, SPECIAL1, INVALID_STRING_INDEX );

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Direct hit
//-----------------------------------------------------------------------------
void CTFProjectile_Napalm::ExplodeEffectOnTarget( CTFPlayer *pThrower, CTFPlayer *pTarget, CBaseCombatCharacter *pBaseTarget )
{
	if ( pBaseTarget->GetTeamNumber() == GetTeamNumber() )
		return;

	if ( pTarget )
	{
		if ( pTarget->m_Shared.IsInvulnerable() )
			return;

		if ( pTarget->m_Shared.InCond( TF_COND_PHASE ) || pTarget->m_Shared.InCond( TF_COND_PASSTIME_INTERCEPTION ) )
			return;
	}

	Vector vecDir = pBaseTarget->WorldSpaceCenter() - GetAbsOrigin();
	VectorNormalize( vecDir );

	const trace_t *pTrace = &CBaseEntity::GetTouchTrace();
	trace_t *pNewTrace = const_cast<trace_t*>( pTrace );

	CBaseEntity *pInflictor = GetLauncher();
	CTakeDamageInfo info;
	info.SetAttacker( pThrower );
	info.SetInflictor( this ); 
	info.SetWeapon( pInflictor );
	info.SetDamage( 50 );
	info.SetDamageCustom( GetCustomDamageType() );
	info.SetDamagePosition( GetAbsOrigin() );
	info.SetDamageType( DMG_IGNITE );

	// Hurt 'em.
	Vector dir;
	AngleVectors( GetAbsAngles(), &dir );
	pBaseTarget->DispatchTraceAttack( info, dir, pNewTrace );
	ApplyMultiDamage();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
const char *CTFProjectile_Napalm::GetImpactEffect( void )
{ 
	return "burninggibs";
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFProjectile_Napalm::SetCustomPipebombModel( void )
{ 
	SetModel( "models/weapons/w_models/w_flaregun_shell.mdl" );
}
#endif // GAME_DLL

#ifdef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFProjectile_Napalm::OnDataChanged( DataUpdateType_t updateType )
{
	BaseClass::OnDataChanged( updateType );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
const char *CTFProjectile_Napalm::GetTrailParticleName( void )
{
	if ( GetTeamNumber() == TF_TEAM_BLUE )
	{
		return "flaming_arrow";
	}
	else
	{
		return "flaming_arrow";
	}
}
#endif // CLIENT_DLL
#endif // STAGING_ONLY