//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================

#include "cbase.h"
#include "AI_BaseNPC.h"
#include "AI_Senses.h"
#include "AI_Squad.h"
#include "grenade_homer.h"
#include "grenade_pathfollower.h"
#include "explode.h"
#include "ndebugoverlay.h"
#include "engine/IEngineSound.h"


#define	LAUNCHER_REST_TIME	3


//------------------------------------
// Spawnflags
//------------------------------------
#define SF_LAUNCHER_CHECK_LOS	(1 << 16)


//=========================================================
//	>> CNPC_Launcher
//=========================================================
class CNPC_Launcher : public CAI_BaseNPC
{
	DECLARE_CLASS( CNPC_Launcher, CAI_BaseNPC );

public:
	int					m_nStartOn;
	string_t			m_sMissileModel;
	string_t			m_sLaunchSound;
	string_t			m_sFlySound;
	int					m_nSmokeTrail;
	bool				m_bSmokeLaunch;
	int					m_nLaunchDelay;
	float				m_flLaunchSpeed;
	string_t			m_sPathCornerName;		// If following a path
	float				m_flHomingSpeed;
	int					m_nHomingStrength;
	float				m_flHomingDelay;		// How long before homing starts
	float				m_flHomingRampUp;		// How much time to ramp up to full homing
	float				m_flHomingDuration;		// How long does homing last
	float				m_flHomingRampDown;		// How long to ramp down to no homing
	float				m_flMissileGravity;
	float				m_flMinAttackDist;
	float				m_flMaxAttackDist;
	float				m_flSpinMagnitude;
	float				m_flSpinSpeed;
	float				m_flDamage;
	float				m_flDamageRadius;
	
	// ----------------
	//	Outputs
	// ----------------
	COutputEvent		m_OnLaunch;					// Triggered when missile is launched.

	// ----------------
	//	Inputs
	// ----------------
	void InputTurnOn( inputdata_t &inputdata );
	void InputTurnOff( inputdata_t &inputdata );
	void InputLOSCheckOn( inputdata_t &inputdata );
	void InputLOSCheckOff( inputdata_t &inputdata );
	void InputSetEnemy( inputdata_t &inputdata );
	void InputClearEnemy( inputdata_t &inputdata );
	void InputFireOnce( inputdata_t &inputdata );

	void LauncherTurnOn(void);
	
	void				Precache( void );
	void				Spawn( void );
	Class_T				Classify( void );
	bool				IsValidEnemy(CBaseEntity *pTarget );
	void				LaunchGrenade(CBaseEntity* pLauncher );
	void				LauncherThink(void );
	bool				FInViewCone( CBaseEntity *pEntity );

	int					DrawDebugTextOverlays(void);

	DECLARE_DATADESC();
};


BEGIN_DATADESC( CNPC_Launcher )

	// Inputs
	DEFINE_KEYFIELD( CNPC_Launcher, m_nStartOn,					FIELD_INTEGER,	"StartOn" ),
	DEFINE_KEYFIELD( CNPC_Launcher, m_sMissileModel,			FIELD_STRING,	"MissileModel" ),
	DEFINE_KEYFIELD( CNPC_Launcher, m_sLaunchSound,				FIELD_STRING,	"LaunchSound" ),
	DEFINE_KEYFIELD( CNPC_Launcher, m_sFlySound,				FIELD_STRING,	"FlySound" ),
	DEFINE_KEYFIELD( CNPC_Launcher, m_nSmokeTrail,				FIELD_INTEGER,	"SmokeTrail" ),
	DEFINE_KEYFIELD( CNPC_Launcher, m_bSmokeLaunch,				FIELD_BOOLEAN,	"LaunchSmoke" ),
	DEFINE_KEYFIELD( CNPC_Launcher, m_nLaunchDelay,				FIELD_INTEGER,	"LaunchDelay" ),
	DEFINE_KEYFIELD( CNPC_Launcher, m_flLaunchSpeed,			FIELD_FLOAT,	"LaunchSpeed" ),
	DEFINE_KEYFIELD( CNPC_Launcher, m_sPathCornerName,			FIELD_STRING,	"PathCornerName" ),
	DEFINE_KEYFIELD( CNPC_Launcher, m_flHomingSpeed,			FIELD_FLOAT,	"HomingSpeed" ),
	DEFINE_KEYFIELD( CNPC_Launcher, m_nHomingStrength,			FIELD_INTEGER,	"HomingStrength" ),
	DEFINE_KEYFIELD( CNPC_Launcher, m_flHomingDelay,			FIELD_FLOAT,	"HomingDelay" ),
	DEFINE_KEYFIELD( CNPC_Launcher, m_flHomingRampUp,			FIELD_FLOAT,	"HomingRampUp" ),
	DEFINE_KEYFIELD( CNPC_Launcher, m_flHomingDuration,			FIELD_FLOAT,	"HomingDuration" ),
	DEFINE_KEYFIELD( CNPC_Launcher, m_flHomingRampDown,			FIELD_FLOAT,	"HomingRampDown" ),
	DEFINE_KEYFIELD( CNPC_Launcher, m_flGravity,				FIELD_FLOAT,	"Gravity" ),
	DEFINE_KEYFIELD( CNPC_Launcher, m_flMinAttackDist,			FIELD_FLOAT,	"MinRange" ),
	DEFINE_KEYFIELD( CNPC_Launcher, m_flMaxAttackDist,			FIELD_FLOAT,	"MaxRange" ),
	DEFINE_KEYFIELD( CNPC_Launcher, m_flSpinMagnitude,			FIELD_FLOAT,	"SpinMagnitude" ),
	DEFINE_KEYFIELD( CNPC_Launcher, m_flSpinSpeed,				FIELD_FLOAT,	"SpinSpeed" ),
	DEFINE_KEYFIELD( CNPC_Launcher, m_flDamage,					FIELD_FLOAT,	"Damage" ),
	DEFINE_KEYFIELD( CNPC_Launcher, m_flDamageRadius,			FIELD_FLOAT,	"DamageRadius" ),
	DEFINE_FIELD( CNPC_Launcher, m_flMissileGravity, FIELD_FLOAT ),

	DEFINE_INPUTFUNC( CNPC_Launcher, FIELD_VOID, "TurnOn", InputTurnOn ),
	DEFINE_INPUTFUNC( CNPC_Launcher, FIELD_VOID, "TurnOff", InputTurnOff ),
	DEFINE_INPUTFUNC( CNPC_Launcher, FIELD_VOID, "LOSCheckOn", InputLOSCheckOn ),
	DEFINE_INPUTFUNC( CNPC_Launcher, FIELD_VOID, "LOSCheckOn", InputLOSCheckOn ),
	DEFINE_INPUTFUNC( CNPC_Launcher, FIELD_VOID, "FireOnce", InputFireOnce ),
	DEFINE_INPUTFUNC( CNPC_Launcher, FIELD_EHANDLE, "SetEnemyEntity", InputSetEnemy ),
	DEFINE_INPUTFUNC( CNPC_Launcher, FIELD_VOID, "ClearEnemyEntity", InputClearEnemy ),

	DEFINE_OUTPUT( CNPC_Launcher, m_OnLaunch, "OnLaunch" ),

	// Function Pointers
	DEFINE_THINKFUNC( CNPC_Launcher, LauncherThink ),

END_DATADESC()

LINK_ENTITY_TO_CLASS( npc_launcher, CNPC_Launcher );


// ===================
//  Input Functions
// ===================
void CNPC_Launcher::InputTurnOn( inputdata_t &inputdata )
{
	LauncherTurnOn();
}

void CNPC_Launcher::InputTurnOff( inputdata_t &inputdata )
{
	SetThink(NULL);
}

void CNPC_Launcher::InputLOSCheckOn( inputdata_t &inputdata )
{
	m_spawnflags |= SF_LAUNCHER_CHECK_LOS;
}

void CNPC_Launcher::InputLOSCheckOff( inputdata_t &inputdata )
{
	m_spawnflags &= ~SF_LAUNCHER_CHECK_LOS;
}

void CNPC_Launcher::InputSetEnemy( inputdata_t &inputdata )
{
	SetEnemy( inputdata.value.Entity().Get() );
}

void CNPC_Launcher::InputClearEnemy( inputdata_t &inputdata )
{
	SetEnemy( NULL );
}

void CNPC_Launcher::InputFireOnce( inputdata_t &inputdata )
{
	m_flNextAttack = 0;

	// If I using path following missiles just launch
	if (m_sPathCornerName != NULL_STRING)
	{
		LaunchGrenade(NULL);
	}
	// Otherwise only launch if I have an enemy
	else
	{
		LauncherThink();
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_Launcher::Precache( void )
{
	// This is a dummy model that is never used!
	engine->PrecacheModel("models/player.mdl");
	engine->PrecacheModel(STRING(m_sMissileModel));
	enginesound->PrecacheSound( STRING(m_sLaunchSound));
	enginesound->PrecacheSound( STRING(m_sFlySound));
	
	UTIL_PrecacheOther( "grenade_homer");
	BaseClass::Precache();
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_Launcher::LauncherTurnOn(void)
{
	SetThink(LauncherThink);
	SetNextThink( gpGlobals->curtime );
	m_flNextAttack = 0;
}


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

	// This is a dummy model that is never used!
	SetModel( "models/player.mdl" );

	UTIL_SetSize(this, vec3_origin, vec3_origin);

	m_takedamage		= DAMAGE_NO;

	if (m_nHomingStrength > 100)
	{
		m_nHomingStrength = 100;
		Warning("WARNING: NPC_Launcher Homing Strength must be between 0 and 100\n");
	}
	
	SetSolid( SOLID_NONE );
	SetMoveType( MOVETYPE_NONE );
	SetBloodColor( DONT_BLEED );
	m_fEffects			|= EF_NODRAW;

	AddFlag( FL_NPC );

	CapabilitiesAdd( bits_CAP_SQUAD );

	InitRelationshipTable();
	
	if (m_nStartOn)
	{
		LauncherTurnOn();
	}

	// -------------------------------------------------------
	//  If I form squads add me to a squad
	// -------------------------------------------------------
	// @TODO (toml 12-05-02): RECONCILE WITH SAVE/RESTORE	
	if (!m_pSquad)
	{
		m_pSquad = g_AI_SquadManager.FindCreateSquad(this, m_SquadName);
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
Class_T	CNPC_Launcher::Classify( void )
{
	return	CLASS_NONE;
}

//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CNPC_Launcher::LaunchGrenade( CBaseEntity* pEnemy )
{
	// If a path following missile, create a path following missile
	if (m_sPathCornerName != NULL_STRING)
	{
		CGrenadePathfollower *pGrenade = CGrenadePathfollower::CreateGrenadePathfollower( m_sMissileModel, m_sFlySound,  GetAbsOrigin(), vec3_angle, edict() );
		pGrenade->SetDamage(m_flDamage);
		pGrenade->SetDamageRadius(m_flDamageRadius);
		pGrenade->Launch(m_flLaunchSpeed,m_sPathCornerName);
	}
	else
	{
		Vector vUp;
		AngleVectors( GetAbsAngles(), NULL, NULL, &vUp );
		Vector vLaunchVelocity = (vUp * m_flLaunchSpeed);

		CGrenadeHomer *pGrenade = CGrenadeHomer::CreateGrenadeHomer( m_sMissileModel, m_sFlySound,  GetAbsOrigin(), vec3_angle, edict() );
		pGrenade->Spawn( );
		pGrenade->SetSpin(m_flSpinMagnitude,m_flSpinSpeed);
		pGrenade->SetHoming((0.01*m_nHomingStrength),m_flHomingDelay,m_flHomingRampUp,m_flHomingDuration,m_flHomingRampDown);
		pGrenade->SetDamage(m_flDamage);
		pGrenade->SetDamageRadius(m_flDamageRadius);
		pGrenade->Launch(this,pEnemy,vLaunchVelocity,m_flHomingSpeed,GetGravity(),m_nSmokeTrail);
	}

	CPASAttenuationFilter filter( this, 0.3 );

	EmitSound( filter, entindex(), CHAN_WEAPON, STRING(m_sLaunchSound), 1.0, 0.3, 1,100);
	if (m_bSmokeLaunch)
	{
		UTIL_Smoke(GetAbsOrigin(), random->RandomInt(20,30), random->RandomInt(10,15));
	}
	m_flNextAttack = gpGlobals->curtime + LAUNCHER_REST_TIME;

}

//------------------------------------------------------------------------------
// Purpose : Launcher sees 360 degrees
//------------------------------------------------------------------------------
bool CNPC_Launcher::FInViewCone( CBaseEntity *pEntity )
{
	return true;
}

//------------------------------------------------------------------------------
// Purpose : Override base class to check range and visibility
//------------------------------------------------------------------------------
bool CNPC_Launcher::IsValidEnemy( CBaseEntity *pTarget )
{
	// ---------------------------------
	//  Check range
	// ---------------------------------
	float flTargetDist = (GetAbsOrigin() - pTarget->GetAbsOrigin()).Length();
	if (flTargetDist < m_flMinAttackDist)
	{
		return false;
	}
	if (flTargetDist > m_flMaxAttackDist)
	{
		return false;
	}

	if (!FBitSet (m_spawnflags, SF_LAUNCHER_CHECK_LOS))
	{
		return true;
	}
	// ------------------------------------------------------
	// Make sure I can see the target from above my position
	// ------------------------------------------------------
	trace_t tr;

	// Trace from launch position to target position.  
	// Use position above actual barral based on vertical launch speed
	Vector vStartPos = GetAbsOrigin() + Vector(0,0,0.2*m_flLaunchSpeed);
	Vector vEndPos	 = pTarget->GetAbsOrigin();
	AI_TraceLine( vStartPos, vEndPos, MASK_SHOT, pTarget, COLLISION_GROUP_NONE, &tr );

	if (tr.fraction == 1.0)
	{
		return true;
	}
	return false;
}

//------------------------------------------------------------------------------
// Purpose :
//------------------------------------------------------------------------------
void CNPC_Launcher::LauncherThink( void )
{
	if (gpGlobals->curtime > m_flNextAttack)
	{
		// If enemy was set, fire at enemy
		if (GetEnemy())
		{
			LaunchGrenade(GetEnemy());
			m_OnLaunch.FireOutput(GetEnemy(), this);
			m_flNextAttack = gpGlobals->curtime + m_nLaunchDelay;
		}
		// Otherwise look for enemy to fire at
		else
		{
			GetSenses()->Look(m_flMaxAttackDist);
			CBaseEntity* pBestEnemy = BestEnemy();

			if (pBestEnemy)
			{
				LaunchGrenade(pBestEnemy);
				m_OnLaunch.FireOutput(pBestEnemy, this);
				m_flNextAttack = gpGlobals->curtime + m_nLaunchDelay;
			}
		}
	}
	SetNextThink( gpGlobals->curtime + 0.1f );
}

//-----------------------------------------------------------------------------
// Purpose: Draw any debug text overlays
// Output : Current text offset from the top
//-----------------------------------------------------------------------------
int CNPC_Launcher::DrawDebugTextOverlays(void) 
{
	int text_offset = BaseClass::DrawDebugTextOverlays();

	if (m_debugOverlays & OVERLAY_TEXT_BIT) 
	{
		char tempstr[512];
		Q_snprintf(tempstr,sizeof(tempstr),"State: %s", (m_pfnThink) ? "On" : "Off" );
		NDebugOverlay::EntityText(entindex(),text_offset,tempstr,0);
		text_offset++;

		Q_snprintf(tempstr,sizeof(tempstr),"LOS: %s", (FBitSet (m_spawnflags, SF_LAUNCHER_CHECK_LOS)) ? "On" : "Off" );
		NDebugOverlay::EntityText(entindex(),text_offset,tempstr,0);
		text_offset++;
	}
	return text_offset;
}