//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================
#include "cbase.h"
#include "basetfplayer_shared.h"
#include "in_buttons.h"
#include "weapon_combatshield.h"
#include "weapon_flame_thrower.h"
#include "gasoline_shared.h"
#include "ammodef.h"


#define FLAME_THROWER_FIRE_INTERVAL		0.3		// Eject a fire blob entity this often.

#define FLAMETHROWER_FLAME_DISTANCE		400.0

#define FLAMETHROWER_FLAME_SPEED		500.0	//

#define FLAMETHROWER_DAMAGE_PER_SEC		1000

// How far the flame particles will spread from the center.
#define FLAMETHROWER_SPREAD_ANGLE		15.0


// ------------------------------------------------------------------------------------------------ //
// Pretty little tables.
// ------------------------------------------------------------------------------------------------ //

#if defined( CLIENT_DLL )

	#include "vstdlib/random.h"
	#include "engine/ienginesound.h"

	#define FLAMETHROWER_PARTICLES_PER_SEC	100

	static const char *g_pFlameThrowerSound = "fire/burning1.wav";

#else

	#include "gasoline_blob.h"
	#include "fire_damage_mgr.h"
	#include "tf_gamerules.h"
	
	#define FLAMETHROWER_DAMAGE_INTERVAL	0.2

#endif

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


PRECACHE_WEAPON_REGISTER( weapon_flame_thrower );

LINK_ENTITY_TO_CLASS( weapon_flame_thrower, CWeaponFlameThrower );

IMPLEMENT_NETWORKCLASS_ALIASED( WeaponFlameThrower, DT_WeaponFlameThrower )

BEGIN_NETWORK_TABLE( CWeaponFlameThrower, DT_WeaponFlameThrower )
	#if defined( CLIENT_DLL )
		RecvPropInt( RECVINFO( m_bFiring ) )
	#else
		SendPropInt( SENDINFO( m_bFiring ), 1, SPROP_UNSIGNED )
	#endif
END_NETWORK_TABLE()
		  
BEGIN_PREDICTION_DATA( CWeaponFlameThrower )

	DEFINE_PRED_FIELD( CWeaponFlameThrower, m_bFiring, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),

END_PREDICTION_DATA()


static inline void GenerateRandomFlameThrowerVelocity( Vector &vOut, const Vector &vForward, const Vector &vRight, const Vector &vUp )
{
	static float radians = DEG2RAD( 90 - FLAMETHROWER_SPREAD_ANGLE );
	static float v = cos( radians ) / sin( radians );
	
	vOut = vForward + vRight * RandomFloat( -v, v ) + vUp * RandomFloat( -v, v );
	VectorNormalize( vOut );
}

template< class T >
int FindInArray( const T pTest, const T *pArray, int arrayLen )
{
	for ( int i=0; i < arrayLen; i++ )
	{
		if ( pTest == pArray[i] )
			return i;
	}
	return -1;
}


// ------------------------------------------------------------------------------------------------ //
// CWeaponFlameThrower implementation.
// ------------------------------------------------------------------------------------------------ //

CWeaponFlameThrower::CWeaponFlameThrower()
{
	InternalConstructor( false );
}


CWeaponFlameThrower::CWeaponFlameThrower( bool bCanister )
{
	InternalConstructor( bCanister );
}


void CWeaponFlameThrower::InternalConstructor( bool bCanister )
{
	m_bCanister = bCanister;

	m_bFiring = false;							   	
	m_flNextPrimaryAttack = -1;

	#if defined( CLIENT_DLL )
	{
		m_hFlameEmitter = CSimpleEmitter::Create( "flamethrower" );
		
		m_hFireMaterial = INVALID_MATERIAL_HANDLE;
		if ( IsGasCanister() )
		{
			m_hFireMaterial = m_hFlameEmitter->GetPMaterial( "particle/particle_noisesphere" );
		}
		else
		{
			m_hFireMaterial = m_hFlameEmitter->GetPMaterial( "particle/fire" );
		}
	
		m_FlameEvent.Init( FLAMETHROWER_PARTICLES_PER_SEC );

		m_bSoundOn = false;
	}
	#endif
}


CWeaponFlameThrower::~CWeaponFlameThrower()
{
	#if defined( CLIENT_DLL )
		StopFlameSound();
	#endif
}


bool CWeaponFlameThrower::IsGasCanister() const
{
	return m_bCanister;
}


bool CWeaponFlameThrower::IsPredicted() const
{
	return true;
}


void CWeaponFlameThrower::ItemPostFrame()
{
	CBaseTFPlayer *pOwner = ToBaseTFPlayer( GetOwner() );
	if ( !pOwner )
		return;

	if ( pOwner->IsAlive() && 
		(pOwner->m_nButtons & IN_ATTACK) && 
		GetShieldState() == SS_DOWN &&
		GetPrimaryAmmo() > 2 )
	{
		PrimaryAttack();
		m_bFiring = true;

		// Prevent shield post frame if we're not ready to attack, or we're healing
		AllowShieldPostFrame( false );
	}
	else
	{
		AllowShieldPostFrame( true );
		m_flNextPrimaryAttack = -1;
		m_bFiring = false;

#if defined( CLIENT_DLL )
#else
		m_hPrevBlob = NULL;

		// It's easy to lay down gasoline and forget to leave enough ammo to ignite it, so
		// this allows the pyro to ignite any nearby gasoline blobs for free.
		if ( !m_bCanister && GetPrimaryAmmo() <= 2 )
		{
			IgniteNearbyGasolineBlobs();
		}
#endif
	}
}


void CWeaponFlameThrower::PrimaryAttack()
{
	#if defined( CLIENT_DLL )
	
	#else

		CBasePlayer *pOwner = ToBaseTFPlayer( GetOwner() );
		if ( !pOwner )
			return;
		
		// Ok.. find eligible entities in a cone in front of us.
		Vector vOrigin = pOwner->Weapon_ShootPosition( );
		Vector vForward, vRight, vUp;
		AngleVectors( pOwner->GetAbsAngles(), &vForward, &vRight, &vUp );

		// Find some entities to burn.
		CBaseEntity *pHitEnts[64];
		int nHitEnts = 0;

		#define NUM_TEST_VECTORS	30
		for ( int iTest=0; iTest < NUM_TEST_VECTORS; iTest++ )
		{	
			Vector vVel;  
			GenerateRandomFlameThrowerVelocity( vVel, vForward, vRight, vUp );

			trace_t tr;
			UTIL_TraceLine( vOrigin, vOrigin + vVel * FLAMETHROWER_FLAME_DISTANCE, MASK_SHOT & (~CONTENTS_HITBOX), NULL, COLLISION_GROUP_NONE, &tr );
			if ( tr.m_pEnt )
			{
				if ( TFGameRules()->IsTraceBlockedByWorldOrShield( vOrigin, vOrigin + vVel * FLAMETHROWER_FLAME_DISTANCE, GetOwner(), DMG_BURN | DMG_PROBE, &tr ) == false )
				{
					CBaseEntity *pTestEnt = tr.m_pEnt;
					if ( pTestEnt && IsBurnableEnt( pTestEnt, GetTeamNumber() ) )
					{
						if ( FindInArray( pTestEnt, pHitEnts, nHitEnts ) == -1 )
						{
							pHitEnts[nHitEnts++] = pTestEnt;
							if ( nHitEnts >= ARRAYSIZE( pHitEnts ) )
								break;
						}
					}
				}	
			}
		}

		for ( int iHitEnt=0; iHitEnt < nHitEnts; iHitEnt++ )
		{
			CBaseEntity *pEnt = pHitEnts[iHitEnt];

			float flDist = (pEnt->GetAbsOrigin() - vOrigin).Length();
			float flPercent = 1.0 - flDist / FLAMETHROWER_FLAME_DISTANCE;
			if ( flPercent < 0.1 )
				flPercent = 0.1;

			float flDamage = flPercent * FLAMETHROWER_DAMAGE_PER_SEC;
			GetFireDamageMgr()->AddDamage( pEnt, GetOwner(), flDamage, !IsGasolineBlob( pEnt ) );
		}
		

		// Drop a new petrol blob.
		if ( gpGlobals->curtime >= m_flNextPrimaryAttack )
		{
			float flLifetime = MAX_LIT_GASOLINE_BLOB_LIFETIME;
			if ( IsGasCanister() )
				flLifetime = MAX_UNLIT_GASOLINE_BLOB_LIFETIME;

			CGasolineBlob *pBlob = CGasolineBlob::Create( GetOwner(), vOrigin, vForward * FLAMETHROWER_FLAME_SPEED, false, FLAMETHROWER_FLAME_DISTANCE / FLAMETHROWER_FLAME_SPEED, flLifetime );
			if ( pBlob )
			{
				if ( IsGasCanister() )
				{
					// Link the previous blob to this one.
					pBlob->AddAutoBurnBlob( m_hPrevBlob );
					if ( m_hPrevBlob.Get() )
						m_hPrevBlob->AddAutoBurnBlob( pBlob );

					m_hPrevBlob = pBlob;
				}
				else
				{
					pBlob->SetLit( true );
				}
			}

			pOwner->RemoveAmmo( 2, m_iPrimaryAmmoType );

			// Drop a blob every half second.
			m_flNextPrimaryAttack = gpGlobals->curtime + FLAME_THROWER_FIRE_INTERVAL;
		}

	#endif
}


#if defined( CLIENT_DLL )

	bool CWeaponFlameThrower::ShouldPredict()
	{
		if ( GetOwner() == C_BasePlayer::GetLocalPlayer() )
			return true;

		return BaseClass::ShouldPredict();
	}


	void CWeaponFlameThrower::NotifyShouldTransmit( ShouldTransmitState_t state )
	{
		BaseClass::NotifyShouldTransmit( state );

		if ( state == SHOULDTRANSMIT_START )
		{
			SetNextClientThink( CLIENT_THINK_ALWAYS );
		}		
		else if ( state == SHOULDTRANSMIT_END )
		{
			SetNextClientThink( CLIENT_THINK_NEVER );
			StopFlameSound();
		}
	}

	void CWeaponFlameThrower::ClientThink()
	{
		// Spew some particles out.
		if ( !IsDormant() && IsCarrierAlive() && m_bFiring && m_hFlameEmitter.IsValid() )
		{
			unsigned char color[4] = { 255, 128, 0, 255 };
			if ( IsGasCanister() )
			{
				color[0] = color[1] = color[2] = 200;
				color[3] = 255;
			}

			StartSound();
			
			Vector vForward, vUp, vRight, vOrigin;
			QAngle vAngles;
			GetShootPosition( vOrigin, vAngles );
			AngleVectors( vAngles, &vForward, &vRight, &vUp );
			
			// Spew out flame particles.
			float dt = gpGlobals->frametime;
			while ( m_FlameEvent.NextEvent( dt ) )
			{
				SimpleParticle *p = m_hFlameEmitter->AddSimpleParticle( 
					m_hFireMaterial, 
					vOrigin + RandomVector( -3, 3 ),
					FLAMETHROWER_FLAME_DISTANCE / FLAMETHROWER_FLAME_SPEED,	// lifetime,
					9	// size
					);

				if ( p )
				{
					p->m_uchColor[0] = color[0];
					p->m_uchColor[1] = color[1];
					p->m_uchColor[2] = color[2];
					p->m_uchStartAlpha = color[3];
					p->m_uchEndAlpha = 0;
					GenerateRandomFlameThrowerVelocity( p->m_vecVelocity, vForward, vRight, vUp );
					p->m_vecVelocity *= RandomFloat( FLAMETHROWER_FLAME_SPEED * 0.9, FLAMETHROWER_FLAME_SPEED * 1.1 );
				}
			}
		}
		else
		{
			StopFlameSound();
		}
	}


	void CWeaponFlameThrower::StartSound()
	{
		if ( !m_bSoundOn )
		{
			CLocalPlayerFilter filter;
			EmitSound( filter, entindex(), CHAN_WEAPON, g_pFlameThrowerSound, 1, ATTN_NORM );

			m_bSoundOn = true;
		}
	}


	void CWeaponFlameThrower::StopFlameSound()
	{
		if ( m_bSoundOn )
		{
			StopSound( entindex(), CHAN_WEAPON, g_pFlameThrowerSound );
			m_bSoundOn = false;
		}
	}


#else

	bool CWeaponFlameThrower::Holster( CBaseCombatWeapon *pSwitchingTo )
	{
		m_bFiring = false;
		
		return BaseClass::Holster( pSwitchingTo );
	}


	void CWeaponFlameThrower::IgniteNearbyGasolineBlobs()
	{
		CBasePlayer *pOwner = ToBaseTFPlayer( GetOwner() );
		if ( !pOwner )
			return;

		Vector vOrigin = pOwner->Weapon_ShootPosition( );
		CBaseEntity *ents[128];
		float dists[128];
		int nEnts = FindBurnableEntsInSphere(
			ents,
			dists,
			ARRAYSIZE( dists ),
			vOrigin,
			50,
			pOwner );

		for ( int i=0; i < nEnts; i++ )
		{
			CGasolineBlob *pBlob = dynamic_cast< CGasolineBlob* >( ents[i] );
			if ( pBlob )
			{
				GetFireDamageMgr()->AddDamage( pBlob, pOwner, 500, false );
			}
		}
	}

#endif


