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

#include "cbase.h"
#include "flashbang_projectile.h"
#include "shake.h"
#include "engine/IEngineSound.h"
#include "cs_player.h"
#include "dlight.h"
#include "KeyValues.h"
#include "weapon_csbase.h"
#include "collisionutils.h"
#include "particle_smokegrenade.h"
#include "smoke_fog_overlay_shared.h"

#define GRENADE_MODEL "models/Weapons/w_eq_flashbang_thrown.mdl"


LINK_ENTITY_TO_CLASS( flashbang_projectile, CFlashbangProjectile );
PRECACHE_WEAPON_REGISTER( flashbang_projectile );

float PercentageOfFlashForPlayer(CBaseEntity *player, Vector flashPos, CBaseEntity *pevInflictor)
{
	float retval = 0.0f;

	trace_t tr;

	Vector pos = player->EyePosition();
	Vector vecRight, vecUp, vecForward;
	AngleVectors( player->EyeAngles(), &vecForward );

	QAngle tempAngle;
	VectorAngles(player->EyePosition() - flashPos, tempAngle);
	AngleVectors(tempAngle, NULL, &vecRight, &vecUp);

	vecRight.NormalizeInPlace();
	vecUp.NormalizeInPlace();

	UTIL_TraceLine( flashPos, pos,
		(CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_DEBRIS|CONTENTS_MONSTER),
		pevInflictor, COLLISION_GROUP_NONE, &tr );

	if ((tr.fraction == 1.0) || (tr.m_pEnt == player))
	{
		retval = 1.0;
	}
	else
	{
		return 0.0;
	}

	CBaseEntity *pSGren;

	for( pSGren = gEntList.FindEntityByClassname( NULL, "env_particlesmokegrenade" );
		pSGren;
		pSGren = gEntList.FindEntityByClassname( pSGren, "env_particlesmokegrenade" ) )
	{
		ParticleSmokeGrenade *pPSG =( ParticleSmokeGrenade* ) pSGren;

		if ( gpGlobals->curtime > pPSG->m_flSpawnTime + pPSG->m_FadeStartTime )		// ignore the smoke grenade if it's fading.
			continue;

		float flHit1, flHit2;

		float flInnerRadius = SMOKEGRENADE_PARTICLERADIUS;
//		float flOutterRadius = flInnerRadius + ( 0.5 * SMOKEPARTICLE_SIZE );

		Vector vPos = pSGren->GetAbsOrigin();

		/*debugoverlay->AddBoxOverlay( pSGren->GetAbsOrigin(), Vector( flInnerRadius, flInnerRadius, flInnerRadius ),
			Vector( -flInnerRadius, -flInnerRadius, -flInnerRadius ), QAngle( 0, 0, 0 ), 0, 255, 0, 30, 10 );
		debugoverlay->AddBoxOverlay( pSGren->GetAbsOrigin(), Vector( flOutterRadius, flOutterRadius, flOutterRadius ),
			Vector( -flOutterRadius, -flOutterRadius, -flOutterRadius ), QAngle( 0, 0, 0 ), 255, 0, 0, 30, 10 ); */

		if ( IntersectInfiniteRayWithSphere( pos, vecForward, vPos, flInnerRadius, &flHit1, &flHit2 ) )
		{
			retval *= 0.8;
		}
/*		else if ( IntersectInfiniteRayWithSphere( pos, vecForward, vPos, flOutterRadius, &flHit1, &flHit2 ) )
		{
			retval *= 0.9;
		}
*/
	}

	return retval;

}

// --------------------------------------------------------------------------------------------------- //
//
// RadiusDamage - this entity is exploding, or otherwise needs to inflict damage upon entities within a certain range.
// 
// only damage ents that can clearly be seen by the explosion!
// --------------------------------------------------------------------------------------------------- //

void RadiusFlash( 
	Vector vecSrc, 
	CBaseEntity *pevInflictor, 
	CBaseEntity *pevAttacker, 
	float flDamage, 
	int iClassIgnore, 
	int bitsDamageType )
{
	vecSrc.z += 1;// in case grenade is lying on the ground

	if ( !pevAttacker )
		pevAttacker = pevInflictor;
	
	trace_t		tr;
	float		flAdjustedDamage;
	variant_t	var;
	Vector		vecEyePos;
	float		fadeTime, fadeHold;
	Vector		vForward;
	Vector		vecLOS;
	float		flDot;
	
	CBaseEntity		*pEntity = NULL;
	static float	flRadius = 1500;
	float			falloff = flDamage / flRadius;

	bool bInWater = (UTIL_PointContents( vecSrc ) == CONTENTS_WATER);

	// iterate on all entities in the vicinity.
	while ((pEntity = gEntList.FindEntityInSphere( pEntity, vecSrc, flRadius )) != NULL)
	{	
		bool bPlayer = pEntity->IsPlayer();
		bool bHostage = ( Q_stricmp( pEntity->GetClassname(), "hostage_entity" ) == 0 );
		
		if( !bPlayer && !bHostage )
			continue;

		vecEyePos = pEntity->EyePosition();

		// blasts don't travel into or out of water
		if ( bInWater && pEntity->GetWaterLevel() == 0)
			continue;
		if (!bInWater && pEntity->GetWaterLevel() == 3)
			continue;

		float percentageOfFlash = PercentageOfFlashForPlayer(pEntity, vecSrc, pevInflictor);

		if ( percentageOfFlash > 0.0 )
		{
			// decrease damage for an ent that's farther from the grenade
			flAdjustedDamage = flDamage - ( vecSrc - pEntity->EyePosition() ).Length() * falloff;
		
			if ( flAdjustedDamage > 0 )
			{
				// See if we were facing the flash
				AngleVectors( pEntity->EyeAngles(), &vForward );

				vecLOS = ( vecSrc - vecEyePos );

				float flDistance = vecLOS.Length();

				// Normalize both vectors so the dotproduct is in the range -1.0 <= x <= 1.0 
				vecLOS.NormalizeInPlace();
					
				flDot = DotProduct (vecLOS, vForward);

				float startingAlpha = 255;
	
				// if target is facing the bomb, the effect lasts longer
				if( flDot >= 0.5 )
				{
					// looking at the flashbang
					fadeTime = flAdjustedDamage * 2.5f;
					fadeHold = flAdjustedDamage * 1.25f;
				}
				else if( flDot >= -0.5 )
				{
					// looking to the side
					fadeTime = flAdjustedDamage * 1.75f;
					fadeHold = flAdjustedDamage * 0.8f;
				}
				else
				{
					// facing away
					fadeTime = flAdjustedDamage * 1.0f;
					fadeHold = flAdjustedDamage * 0.75f;
					startingAlpha = 200;
				}

				fadeTime *= percentageOfFlash;
				fadeHold *= percentageOfFlash;

				if ( bPlayer )
				{
    				// blind players and bots
					CCSPlayer *player = static_cast< CCSPlayer * >( pEntity );

                    //=============================================================================
                    // HPE_BEGIN:
                    // [tj] Store who was responsible for the most recent flashbang blinding.
                    //=============================================================================
                     
                    CCSPlayer *attacker = ToCSPlayer (pevAttacker);
                    if (attacker && player)
                    {
                        player->SetLastFlashbangAttacker(attacker);
                    }
                     
                    //=============================================================================
                    // HPE_END
                    //=============================================================================
                    

                                         
					player->Blind( fadeHold, fadeTime, startingAlpha );

					// deafen players and bots
					player->Deafen( flDistance );
				}
				else if ( bHostage )
				{
					variant_t val;					
					val.SetFloat( fadeTime );
					pEntity->AcceptInput( "flashbang", pevInflictor, pevAttacker, val, 0 );
				}
			}	
		}
	}

	CPVSFilter filter(vecSrc);
	te->DynamicLight( filter, 0.0, &vecSrc, 255, 255, 255, 2, 400, 0.1, 768 );
}

// --------------------------------------------------------------------------------------------------- //
// CFlashbangProjectile implementation.
// --------------------------------------------------------------------------------------------------- //

CFlashbangProjectile* CFlashbangProjectile::Create( 
	const Vector &position, 
	const QAngle &angles, 
	const Vector &velocity, 
	const AngularImpulse &angVelocity, 
	CBaseCombatCharacter *pOwner )
{
	CFlashbangProjectile *pGrenade = (CFlashbangProjectile*)CBaseEntity::Create( "flashbang_projectile", position, angles, pOwner );
	
	// Set the timer for 1 second less than requested. We're going to issue a SOUND_DANGER
	// one second before detonation.
	pGrenade->SetAbsVelocity( velocity );
	pGrenade->SetupInitialTransmittedGrenadeVelocity( velocity );
	pGrenade->SetThrower( pOwner );
	pGrenade->m_flDamage = 100;
	pGrenade->ChangeTeam( pOwner->GetTeamNumber() );

	pGrenade->SetTouch( &CBaseGrenade::BounceTouch );

	pGrenade->SetThink( &CBaseCSGrenadeProjectile::DangerSoundThink );
	pGrenade->SetNextThink( gpGlobals->curtime );

	pGrenade->SetDetonateTimerLength( 1.5 );

	pGrenade->ApplyLocalAngularVelocityImpulse( angVelocity );

	pGrenade->SetGravity( BaseClass::GetGrenadeGravity() );
	pGrenade->SetFriction( BaseClass::GetGrenadeFriction() );
	pGrenade->SetElasticity( BaseClass::GetGrenadeElasticity() );

	pGrenade->m_pWeaponInfo = GetWeaponInfo( WEAPON_FLASHBANG );


	return pGrenade;
}

void CFlashbangProjectile::Spawn()
{
	SetModel( GRENADE_MODEL );
	BaseClass::Spawn();
}

void CFlashbangProjectile::Precache()
{
	PrecacheModel( GRENADE_MODEL );

	PrecacheScriptSound( "Flashbang.Explode" );
	PrecacheScriptSound( "Flashbang.Bounce" );

	BaseClass::Precache();
}

void CFlashbangProjectile::Detonate()
{
	RadiusFlash ( GetAbsOrigin(), this, GetThrower(), 4, CLASS_NONE, DMG_BLAST );
	EmitSound( "Flashbang.Explode" );	

	// tell the bots a flashbang grenade has exploded
	CCSPlayer *player = ToCSPlayer(GetThrower());
	if ( player )
	{
		IGameEvent * event = gameeventmanager->CreateEvent( "flashbang_detonate" );
		if ( event )
		{
			event->SetInt( "userid", player->GetUserID() );
			event->SetFloat( "x", GetAbsOrigin().x );
			event->SetFloat( "y", GetAbsOrigin().y );
			event->SetFloat( "z", GetAbsOrigin().z );
			gameeventmanager->FireEvent( event );
		}
	}

	UTIL_Remove( this );
}

//TODO: Let physics handle the sound!
void CFlashbangProjectile::BounceSound( void )
{
	EmitSound( "Flashbang.Bounce" );
}