//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// TODO: 
//		Take advantage of new NPC fields like GetEnemy() and get rid of that OFFSET() stuff
//		Revisit enemy validation stuff, maybe it's not necessary with the newest NPC code
//
// $NoKeywords: $
//=============================================================================//

#include "cbase.h"
#include "Sprite.h"
#include "basecombatweapon.h"
#include "AI_BaseNPC.h"
#include "AI_Senses.h"
#include "AI_Memory.h"
#include "gamerules.h"
#include "ammodef.h"
#include "ndebugoverlay.h"
#include "IEffects.h"
#include "vstdlib/random.h"
#include "engine/IEngineSound.h"

class CSprite;

#define TURRET_RANGE	(100 * 12)
#define TURRET_SPREAD	VECTOR_CONE_5DEGREES
#define TURRET_TURNRATE	360		// max angles per second
#define TURRET_MAXWAIT	15		// seconds turret will stay active w/o a target
#define TURRET_MAXSPIN	5		// seconds turret barrel will spin w/o a target
#define TURRET_MACHINE_VOLUME	0.5

#define TURRET_BC_YAW	"aim_yaw"
#define TURRET_BC_PITCH	"aim_pitch"

#define TURRET_ORIENTATION_FLOOR 0
#define TURRET_ORIENTATION_CEILING 1

//=========================================================
// private activities
//=========================================================
int ACT_TURRET_OPEN;
int ACT_TURRET_CLOSE;
int ACT_TURRET_OPEN_IDLE;
int ACT_TURRET_CLOSED_IDLE;
int ACT_TURRET_FIRE;
int ACT_TURRET_RELOAD;

// ===============================================
//  Private spawn flags  (must be above (1<<15))
// ===============================================
#define SF_NPC_TURRET_AUTOACTIVATE		0x00000020
#define SF_NPC_TURRET_STARTINACTIVE		0x00000040

extern short		g_sModelIndexSmoke;			// (in combatweapon.cpp) holds the index for the smoke cloud

ConVar	sk_miniturret_health( "sk_miniturret_health","0");
ConVar	sk_sentry_health( "sk_sentry_health","0");
ConVar	sk_turret_health( "sk_turret_health","0");

class CBaseTurret : public CAI_BaseNPC
{
	DECLARE_CLASS( CBaseTurret, CAI_BaseNPC );
public:
	void Spawn(void);
	virtual void Precache(void);
	bool KeyValue( const char *szKeyName, const char *szValue );
	//void TurretUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
	
	virtual void		TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr );
	virtual int			OnTakeDamage( const CTakeDamageInfo &info );
	virtual Class_T		Classify(void);

	int BloodColor( void ) { return DONT_BLEED; }
	bool Event_Gibbed( void ) { return FALSE; }	// UNDONE: Throw turret gibs?

	Vector	EyeOffset( Activity nActivity );
	Vector	EyePosition( void );

	// Inputs
	void InputToggle( inputdata_t &inputdata );

	// Think functions

	void ActiveThink(void);
	void SearchThink(void);
	void AutoSearchThink(void);
	void TurretDeath(void);

	void Deploy(void);
	void Retire(void);
	
	void Initialize(void);

	virtual void Ping(void);
	virtual void EyeOn(void);
	virtual void EyeOff(void);

	DECLARE_DATADESC();

	// other functions
	int MoveTurret(void);
	virtual void Shoot(const Vector &vecSrc, const Vector &vecDirToEnemy) { };

	CSprite *m_pEyeGlow;
	int		m_eyeBrightness;

	int	m_iDeployHeight;
	int	m_iRetractHeight;
	int m_iMinPitch;

	int m_iBaseTurnRate;	// angles per second
	float m_fTurnRate;		// actual turn rate
	int	m_iOn;
	int m_fBeserk;			// Sometimes this bitch will just freak out
	int m_iAutoStart;		// true if the turret auto deploys when a target
							// enters its range

	Vector	m_vecLastSight;	// Last seen position
	float	m_flLastSight;	// Last time we saw a target
	float	m_flMaxWait;	// Max time to search w/o a target

	// movement
	float	m_flStartYaw;
	QAngle	m_vecGoalAngles;

	int		m_iAmmoType;

	float	m_flPingTime;	// Time until the next ping, used when searching
	float	m_flDamageTime;	// Time we last took damage.

	COutputEvent m_OnDeploy;
	COutputEvent m_OnRetire;

	// external
	//COutputEvent		m_OnDamaged;
	//COutputEvent		m_OnDeath;
	//COutputEvent		m_OnHalfHealth;
	//COutputEHANDLE		m_OnFoundEnemy; 
	//COutputEvent		m_OnLostEnemyLOS; 
	//COutputEvent		m_OnLostEnemy; 
	//COutputEHANDLE		m_OnFoundPlayer;
	//COutputEvent		m_OnLostPlayerLOS;
	//COutputEvent		m_OnLostPlayer; 
	//COutputEvent		m_OnHearWorld;
	//COutputEvent		m_OnHearPlayer;
	//COutputEvent		m_OnHearCombat;

};


BEGIN_DATADESC( CBaseTurret )

	DEFINE_FIELD( m_pEyeGlow, FIELD_CLASSPTR ),
	DEFINE_FIELD( m_eyeBrightness, FIELD_INTEGER ),
	DEFINE_FIELD( m_iDeployHeight, FIELD_INTEGER ),
	DEFINE_FIELD( m_iRetractHeight, FIELD_INTEGER ),
	DEFINE_FIELD( m_iMinPitch, FIELD_INTEGER ),

	DEFINE_FIELD( m_iBaseTurnRate, FIELD_INTEGER ),
	DEFINE_FIELD( m_fTurnRate, FIELD_FLOAT ),
	DEFINE_FIELD( m_iOn, FIELD_INTEGER ),
	DEFINE_FIELD( m_fBeserk, FIELD_INTEGER ),
	DEFINE_FIELD( m_iAutoStart, FIELD_INTEGER ),

	DEFINE_FIELD( m_vecLastSight, FIELD_POSITION_VECTOR ),
	DEFINE_FIELD( m_flLastSight, FIELD_TIME ),
	DEFINE_FIELD( m_flMaxWait, FIELD_FLOAT ),

	DEFINE_FIELD( m_flStartYaw, FIELD_FLOAT ),
	DEFINE_FIELD( m_vecGoalAngles, FIELD_VECTOR ),

	DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ),

	DEFINE_FIELD( m_flPingTime, FIELD_TIME ),
	DEFINE_FIELD( m_flDamageTime, FIELD_TIME ),

	// Function pointers
	//DEFINE_USEFUNC( TurretUse ),
	DEFINE_THINKFUNC( ActiveThink ),
	DEFINE_THINKFUNC( SearchThink ),
	DEFINE_THINKFUNC( AutoSearchThink ),
	DEFINE_THINKFUNC( TurretDeath ),
	DEFINE_THINKFUNC( Deploy ),
	DEFINE_THINKFUNC( Retire ),
	DEFINE_THINKFUNC( Initialize ),

	// Inputs
	DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
	
	// Outputs
	DEFINE_OUTPUT(m_OnDeploy, "OnDeploy"),
	DEFINE_OUTPUT(m_OnRetire, "OnRetire"),

END_DATADESC()



//------------------------------------------------------------------------------
// Purpose : 
// Input   :
// Output  :
//------------------------------------------------------------------------------
Vector	CBaseTurret::EyeOffset( Activity nActivity )
{
	return Vector( 0, 0, -20 );
}


Vector	CBaseTurret::EyePosition( void )
{
	Vector vecOrigin;
	QAngle vecAngles;

	GetAttachment( "eyes", vecOrigin, vecAngles );
	return vecOrigin;
}

bool CBaseTurret::KeyValue( const char *szKeyName, const char *szValue )
{
	if (FStrEq(szKeyName, "maxsleep"))
	{
		m_flMaxWait = atof(szValue);
	}
	else if (FStrEq(szKeyName, "turnrate"))
	{
		m_iBaseTurnRate = atoi(szValue);
	}
	else if (FStrEq(szKeyName, "style") ||
			 FStrEq(szKeyName, "height") ||
			 FStrEq(szKeyName, "value1") ||
			 FStrEq(szKeyName, "value2") ||
			 FStrEq(szKeyName, "value3"))
	{
	}
	else
		return BaseClass::KeyValue( szKeyName, szValue );

	return true;
}


void CBaseTurret::Spawn()
{ 
	Precache( );
	SetNextThink( gpGlobals->curtime + 1 );
	SetMoveType( MOVETYPE_FLY );
	m_nSequence		= 0;
	m_flCycle		= 0;
	SetSolid( SOLID_SLIDEBOX );
	m_takedamage		= DAMAGE_YES;
	AddFlag( FL_AIMTARGET );

	m_iAmmoType = g_pGameRules->GetAmmoDef()->Index("SMG1");

	AddFlag( FL_NPC );

	if (( m_spawnflags & SF_NPC_TURRET_AUTOACTIVATE ) && !( m_spawnflags & SF_NPC_TURRET_STARTINACTIVE ))
	{
		m_iAutoStart = true;
	}

	ResetSequenceInfo( );

	SetPoseParameter( TURRET_BC_YAW, 0 );
	SetPoseParameter( TURRET_BC_PITCH, 0 );

	// Activities
	ADD_CUSTOM_ACTIVITY( CBaseTurret, ACT_TURRET_OPEN );
	ADD_CUSTOM_ACTIVITY( CBaseTurret, ACT_TURRET_CLOSE );
	ADD_CUSTOM_ACTIVITY( CBaseTurret, ACT_TURRET_CLOSED_IDLE );
	ADD_CUSTOM_ACTIVITY( CBase`matTurret, ACT_TURRET_OPEN_IDLE );
	ADD_CUSTOM_ACTIVITY( CBaseTurret, ACT_TURRET_FIRE );
	ADD_CUSTOM_ACTIVITY( CBaseTurret, ACT_TURRET_RELOAD );
}


void CBaseTurret::Precache( )
{
	BaseClass::Precache();

	PrecacheScriptSound( "NPC_Turret.Ping" );
	PrecacheScriptSound( "NPC_Turret.Deploy" );
	PrecacheScriptSound( "NPC_Turret.Retire" );
	PrecacheScriptSound( "NPC_Turret.Alert" );
	PrecacheScriptSound( "NPC_Turret.Die" );
}

void CBaseTurret::Initialize(void)
{
	m_iOn = 0;
	m_fBeserk = 0;

	SetPoseParameter( TURRET_BC_YAW, 0 );
	SetPoseParameter( TURRET_BC_PITCH, 0 );

	if (m_iBaseTurnRate == 0) m_iBaseTurnRate = TURRET_TURNRATE;
	if (m_flMaxWait == 0) m_flMaxWait = TURRET_MAXWAIT;

	m_vecGoalAngles = GetAngles();

	if (m_iAutoStart)
	{
		m_flLastSight = gpGlobals->curtime + m_flMaxWait;
		SetThink(AutoSearchThink);		
		SetNextThink( gpGlobals->curtime + .1 );
	}
	else
		SetThink(SUB_DoNothing);
}


//-----------------------------------------------------------------------------
// Purpose: Input handler for toggling the turret on/off.
//-----------------------------------------------------------------------------
void CBaseTurret::InputToggle( inputdata_t &inputdata )
{
	//if ( !ShouldToggle( useType, m_iOn ) )
	//	return;

	if (m_iOn)
	{
		SetEnemy( NULL );
		SetNextThink( gpGlobals->curtime + 0.1f );
		m_iAutoStart = FALSE;// switching off a turret disables autostart
		//!!!! this should spin down first!!BUGBUG
		SetThink(Retire);
	}
	else 
	{
		SetNextThink( gpGlobals->curtime + 0.1f ); // turn on delay

		// if the turret is flagged as an autoactivate turret, re-enable its ability open self.
		if ( m_spawnflags & SF_NPC_TURRET_AUTOACTIVATE )
		{
			m_iAutoStart = TRUE;
		}
		
		SetThink(Deploy);
	}
}


void CBaseTurret::Ping( void )
{
	// make the pinging noise every second while searching
	if (m_flPingTime == 0)
		m_flPingTime = gpGlobals->curtime + 1;
	else if (m_flPingTime <= gpGlobals->curtime)
	{
		m_flPingTime = gpGlobals->curtime + 1;
		EmitSound( "NPC_Turret.Ping" );
		EyeOn( );
	}
	else if (m_eyeBrightness > 0)
	{
		EyeOff( );
	}
}


void CBaseTurret::EyeOn( )
{
	if (m_pEyeGlow)
	{
		if (m_eyeBrightness != 255)
		{
			m_eyeBrightness = 255;
		}
		m_pEyeGlow->SetBrightness( m_eyeBrightness );
	}
}


void CBaseTurret::EyeOff( )
{
	if (m_pEyeGlow)
	{
		if (m_eyeBrightness > 0)
		{
			m_eyeBrightness = max( 0, m_eyeBrightness - 30 );
			m_pEyeGlow->SetBrightness( m_eyeBrightness );
		}
	}
}


void CBaseTurret::ActiveThink(void)
{
	int fAttack = 0;
	Vector vecDirToEnemy;

	SetNextThink( gpGlobals->curtime + 0.1f );
	StudioFrameAdvance( );

	if ((!m_iOn) || (GetEnemy() == NULL))
	{
		SetEnemy( NULL );
		m_flLastSight = gpGlobals->curtime + m_flMaxWait;
		SetThink(SearchThink);
		return;
	}
	
	// if it's dead, look for something new
	if ( !GetEnemy()->IsAlive() )
	{
		if (!m_flLastSight)
		{
			m_flLastSight = gpGlobals->curtime + 0.5; // continue-shooting timeout
		}
		else
		{
			if (gpGlobals->curtime > m_flLastSight)
			{	
				SetEnemy( NULL );
				m_flLastSight = gpGlobals->curtime + m_flMaxWait;
				SetThink(SearchThink);
				return;
			}
		}
	}

	Vector vecMid = EyePosition( );
	Vector vecMidEnemy = GetEnemy()->BodyTarget(vecMid);

	// g_pEffects->Sparks( vecMid );
	// g_pEffects->Sparks( vecMidEnemy );

	// Look for our current enemy
	//int fEnemyVisible = FBoxVisible( this, GetEnemy(), vecMidEnemy );	
	int fEnemyVisible = FInViewCone( GetEnemy() ) && FVisible( GetEnemy() );	

	vecDirToEnemy = vecMidEnemy - vecMid;	// calculate dir and dist to enemy
	// NDebugOverlay::Line( vecMid, vecMidEnemy, 0, 255, 0, false, 0.1 );
	
	float flDistToEnemy = vecDirToEnemy.Length();

	QAngle vecAnglesToEnemy;
	VectorNormalize( vecDirToEnemy );
	VectorAngles( vecDirToEnemy, vecAnglesToEnemy );

	// Current enmey is not visible.
	if (!fEnemyVisible || (flDistToEnemy > TURRET_RANGE))
	{
		// DevMsg( "lost you\n" );

		if (!m_flLastSight)
		{
			m_flLastSight = gpGlobals->curtime + 0.5;
		}
		else
		{
			// Should we look for a new target?
			if (gpGlobals->curtime > m_flLastSight)
			{
				ClearEnemyMemory();
				SetEnemy( NULL );
				m_flLastSight = gpGlobals->curtime + m_flMaxWait;
				SetThink(SearchThink);
				return;
			}
		}
		fEnemyVisible = 0;
	}
	else
	{
		m_vecLastSight = vecMidEnemy;
	}

	Vector vecLOS = vecDirToEnemy; //vecMid - m_vecLastSight;
	VectorNormalize( vecLOS );

	Vector vecMuzzle, vecMuzzleDir;
	QAngle vecMuzzleAng;
	GetAttachment( "eyes", vecMuzzle, vecMuzzleAng );

	AngleVectors( vecMuzzleAng, &vecMuzzleDir );
	
	// Is the Gun looking at the target
	if (DotProduct(vecLOS, vecMuzzleDir) <= 0.9848) // 10 degree slop
	{
		fAttack = FALSE;
	}
	else
	{
		fAttack = TRUE;
	}

	// fire the gun
	if (fAttack || m_fBeserk)
	{
		m_Activity = ACT_RESET;
		SetActivity( (Activity)ACT_TURRET_FIRE );
		Shoot(vecMuzzle, vecMuzzleDir );
	} 
	else
	{
		SetActivity( (Activity)ACT_TURRET_OPEN_IDLE );
	}

	//move the gun
	if (m_fBeserk)
	{
		// DevMsg( "berserk" );

		if (random->RandomInt(0,9) == 0)
		{
			m_vecGoalAngles.y = random->RandomFloat(-180,180);
			m_vecGoalAngles.x = random->RandomFloat(-90,90);
			OnTakeDamage( CTakeDamageInfo( this, this, 1, DMG_GENERIC ) ); // don't beserk forever
			return;
		}
	} 
	else if (fEnemyVisible)
	{
		// DevMsg( "->[%.2f]\n", vec.x);
		m_vecGoalAngles.y = vecAnglesToEnemy.y;
		m_vecGoalAngles.x = vecAnglesToEnemy.x;

	}

	MoveTurret();
}


void CBaseTurret::Deploy(void)
{
	SetNextThink( gpGlobals->curtime + 0.1f );
	StudioFrameAdvance( );

	if ( m_Activity != ACT_TURRET_OPEN )
	{
		m_iOn = 1;
		SetActivity( (Activity)ACT_TURRET_OPEN );
		EmitSound( "NPC_Turret.Deploy" );

		m_OnDeploy.FireOutput(NULL, this);
	}

	if (m_fSequenceFinished)
	{
		Vector curmins, curmaxs;
		curmins = WorldAlignMins();
		curmaxs = WorldAlignMaxs();

		curmaxs.z = m_iDeployHeight;
		curmins.z = -m_iDeployHeight;

		SetCollisionBounds( curmins, curmaxs );

		Relink();

		SetActivity( (Activity)ACT_TURRET_OPEN_IDLE );

		m_flPlaybackRate = 0;
		SetThink(SearchThink);
	}

	m_flLastSight = gpGlobals->curtime + m_flMaxWait;
}

void CBaseTurret::Retire(void)
{
	// make the turret level
	m_vecGoalAngles = GetAngles( );

	SetNextThink( gpGlobals->curtime + 0.1f );

	StudioFrameAdvance( );

	EyeOff( );

	if ( m_Activity != ACT_TURRET_CLOSE )
	{
		SetActivity( (Activity)ACT_TURRET_OPEN_IDLE );
		
		if (!MoveTurret())
		{
			SetActivity( (Activity)ACT_TURRET_CLOSE );
			EmitSound( "NPC_Turret.Retire" );

			m_OnRetire.FireOutput(NULL, this);
		}
	}
	else if (m_fSequenceFinished) 
	{	
		m_iOn = 0;
		m_flLastSight = 0;

		SetActivity( (Activity)ACT_TURRET_CLOSED_IDLE );

		Vector curmins, curmaxs;
		curmins = WorldAlignMins();
		curmaxs = WorldAlignMaxs();

		curmaxs.z = m_iRetractHeight;
		curmins.z = -m_iRetractHeight;

		SetCollisionBounds( curmins, curmaxs );
		Relink();

		if (m_iAutoStart)
		{
			SetThink(AutoSearchThink);		
			SetNextThink( gpGlobals->curtime + .1 );
		}
		else
		{
			SetThink(SUB_DoNothing);
		}
	}
}


//
// This search function will sit with the turret deployed and look for a new target. 
// After a set amount of time, the barrel will spin down. After m_flMaxWait, the turret will
// retact.
//
void CBaseTurret::SearchThink(void)
{
	// ensure rethink
	SetActivity( (Activity)ACT_TURRET_OPEN_IDLE );

	StudioFrameAdvance( );
	SetNextThink( gpGlobals->curtime + 0.1f );

	Ping( );

	// If we have a target and we're still healthy
	if (GetEnemy() != NULL)
	{
		if (!GetEnemy()->IsAlive() )
			SetEnemy( NULL );// Dead enemy forces a search for new one
	}

	// Acquire Target
	if (GetEnemy() == NULL)
	{
		GetSenses()->Look(TURRET_RANGE);
		SetEnemy( BestEnemy() );
	}

	// If we've found a target, spin up the barrel and start to attack
	if (GetEnemy() != NULL)
	{
		m_flLastSight = 0;
		SetThink(ActiveThink);
	}
	else
	{
		// Are we out of time, do we need to retract?
 		if (gpGlobals->curtime > m_flLastSight)
		{
			//Before we retrace, make sure that we are spun down.
			m_flLastSight = 0;
			SetThink(Retire);
		}
		
		// generic hunt for new victims
		m_vecGoalAngles.y = (m_vecGoalAngles.y + 0.1 * m_iBaseTurnRate);
		if (m_vecGoalAngles.y >= 360)
			m_vecGoalAngles.y -= 360;

		MoveTurret();
	}
}


// 
// This think function will deploy the turret when something comes into range. This is for
// automatically activated turrets.
//
void CBaseTurret::AutoSearchThink(void)
{
	// ensure rethink
	StudioFrameAdvance( );
	SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.2, 0.3 ) );

	// If we have a target and we're still healthy

	if (GetEnemy() != NULL)
	{
		if (!GetEnemy()->IsAlive() )
			SetEnemy( NULL );// Dead enemy forces a search for new one
	}

	// Acquire Target

	if (GetEnemy() == NULL)
	{
		GetSenses()->Look( TURRET_RANGE );
		SetEnemy( BestEnemy() );
	}

	if (GetEnemy() != NULL)
	{
		SetThink(Deploy);
		EmitSound( "NPC_Turret.Alert" );
	}
}


void CBaseTurret ::	TurretDeath( void )
{
	StudioFrameAdvance( );
	SetNextThink( gpGlobals->curtime + 0.1f );

	if (m_lifeState != LIFE_DEAD)
	{
		m_lifeState = LIFE_DEAD;

		EmitSound( "NPC_Turret.Die" );

		SetActivity( (Activity)ACT_TURRET_CLOSE );

		EyeOn( );	
	}

	EyeOff( );

	if (m_flDamageTime + random->RandomFloat( 0, 2 ) > gpGlobals->curtime)
	{
		// lots of smoke
		Vector pos;
		CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1, 1, 1 ), &pos );
		pos.z = CollisionProp()->GetCollisionOrigin().z;
		
		CBroadcastRecipientFilter filter;
		te->Smoke( filter, 0.0, &pos,
			g_sModelIndexSmoke,
			2.5,
			10 );
	}
	
	if (m_flDamageTime + random->RandomFloat( 0, 5 ) > gpGlobals->curtime)
	{
		Vector vecSrc;
		CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1, 1, 1 ), &vecSrc );
		g_pEffects->Sparks( vecSrc );
	}

	if (m_fSequenceFinished && !MoveTurret( ) && m_flDamageTime + 5 < gpGlobals->curtime)
	{
		m_flPlaybackRate = 0;
		SetThink( NULL );
	}
}



void CBaseTurret::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr )
{
	CTakeDamageInfo info = inputInfo;

	if ( ptr->hitgroup == 10 )
	{
		// hit armor
		if ( m_flDamageTime != gpGlobals->curtime || (random->RandomInt(0,10) < 1) )
		{
			g_pEffects->Ricochet( ptr->endpos, (vecDir*-1.0f) );
			m_flDamageTime = gpGlobals->curtime;
		}

		info.SetDamage( 0.1 );// don't hurt the NPC much, but allow bits_COND_LIGHT_DAMAGE to be generated
	}

	if ( !m_takedamage )
		return;

	AddMultiDamage( info, this );
}


int CBaseTurret::OnTakeDamage( const CTakeDamageInfo &inputInfo )
{
	if ( !m_takedamage )
		return 0;

	CTakeDamageInfo info = inputInfo;

	if (!m_iOn)
		info.ScaleDamage( 0.1f );

	m_iHealth -= info.GetDamage();
	if (m_iHealth <= 0)
	{
		m_iHealth = 0;
		m_takedamage = DAMAGE_NO;
		m_flDamageTime = gpGlobals->curtime;

		RemoveFlag( FL_NPC ); // why are they set in the first place???

		SetThink(TurretDeath);

		m_OnDamaged.FireOutput( info.GetInflictor(), this );

		SetNextThink( gpGlobals->curtime + 0.1f );

		return 0;
	}

	if (m_iHealth <= 10)
	{
		if (m_iOn && (1 || random->RandomInt(0, 0x7FFF) > 800))
		{
			m_fBeserk = 1;
			SetThink(SearchThink);
		}
	}

	return 1;
}


int CBaseTurret::MoveTurret(void)
{
	bool bDidMove = false;
	int iPose;

	matrix3x4_t localToWorld;
	
	GetAttachment( LookupAttachment( "eyes" ), localToWorld );

	Vector vecGoalDir;
	AngleVectors( m_vecGoalAngles, &vecGoalDir );

	Vector vecGoalLocalDir;
	VectorIRotate( vecGoalDir, localToWorld, vecGoalLocalDir );

	QAngle vecGoalLocalAngles;
	VectorAngles( vecGoalLocalDir, vecGoalLocalAngles );

	float flDiff;
	QAngle vecNewAngles;

  // update pitch
	flDiff = AngleNormalize( UTIL_ApproachAngle(  vecGoalLocalAngles.x, 0.0, 0.1 * m_iBaseTurnRate ) );
	iPose = LookupPoseParameter( TURRET_BC_PITCH );
	SetPoseParameter( iPose, GetPoseParameter( iPose ) + flDiff / 1.5 );

	if (fabs(flDiff) > 0.1)
	{
		bDidMove = true;
	}

	// update yaw, with acceleration
#if 0
	float flDist = AngleNormalize( vecGoalLocalAngles.y );
	float flNewDist;
	float flNewTurnRate;

	ChangeDistance( 0.1, flDist, 0.0, m_fTurnRate, m_iBaseTurnRate, m_iBaseTurnRate * 4, flNewDist, flNewTurnRate );
	m_fTurnRate = flNewTurnRate;
	flDiff = flDist - flNewDist;
#else
	flDiff = AngleNormalize( UTIL_ApproachAngle(  vecGoalLocalAngles.y, 0.0, 0.1 * m_iBaseTurnRate ) );
#endif

	iPose = LookupPoseParameter( TURRET_BC_YAW );
	SetPoseParameter( iPose, GetPoseParameter( iPose ) + flDiff / 1.5 );
	if (fabs(flDiff) > 0.1)
	{
		bDidMove = true;
	}

	if (bDidMove)
	{
		// DevMsg( "(%.2f, %.2f)\n", AngleNormalize( vecGoalLocalAngles.x ), AngleNormalize( vecGoalLocalAngles.y ) );
	}
	return bDidMove;
}

//
// ID as a machine
//
Class_T	CBaseTurret::Classify ( void )

{
	if (m_iOn || m_iAutoStart)
	{
		return	CLASS_MILITARY;
	}

	return CLASS_MILITARY;
}




//////////////////////////////////////////////////////////////////////////////////////////////////////


class CCeilingTurret : public CBaseTurret
{
	DECLARE_CLASS( CCeilingTurret, CBaseTurret );
public:
	void Spawn(void);
	void Precache(void);

	// other functions
	void Shoot( const Vector &vecSrc, const Vector &vecDirToEnemy );
};

#define TURRET_GLOW_SPRITE "sprites/glow01.vmt"

LINK_ENTITY_TO_CLASS( npc_turret_ceiling, CCeilingTurret );

void CCeilingTurret::Spawn()
{ 
	Precache( );

	SetModel( "models/combine_turrets/ceiling_turret.mdl" );
	
	BaseClass::Spawn( );

	m_iHealth			= sk_turret_health.GetFloat();
	m_HackedGunPos		= Vector( 0, 0, 12.75 );

	AngleVectors( GetAngles(), NULL, NULL, &m_vecViewOffset );
	m_vecViewOffset		= m_vecViewOffset * Vector( 0, 0, -64 );

	m_flFieldOfView		= VIEW_FIELD_FULL;

	m_iRetractHeight = 16;
	m_iDeployHeight = 32;
	m_iMinPitch	= -45;
	UTIL_SetSize(this, Vector(-32, -32, -m_iRetractHeight), Vector(32, 32, m_iRetractHeight));
	
	SetThink(Initialize);	

	m_pEyeGlow = CSprite::SpriteCreate( TURRET_GLOW_SPRITE, GetOrigin(), FALSE );
	m_pEyeGlow->SetTransparency( kRenderGlow, 255, 0, 0, 0, kRenderFxNoDissipation );
	m_pEyeGlow->SetAttachment( this, 2 );
	m_eyeBrightness = 0;

	SetNextThink( gpGlobals->curtime + 0.3; );
}

void CCeilingTurret::Precache()
{
	PrecacheModel( "models/combine_turrets/ceiling_turret.mdl");	
	PrecacheModel( TURRET_GLOW_SPRITE );

	PrecacheScriptSound( "CeilingTurret.Shoot" );
	BaseClass::Precache();
}

void CCeilingTurret::Shoot(const Vector &vecSrc, const Vector &vecDirToEnemy)
{
	//NDebugOverlay::Line( vecSrc, vecSrc + vecDirToEnemy * 512, 0, 255, 255, false, 0.1 );
	FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, m_iAmmoType, 1 );
	EmitSound( "CeilingTurret.Shoot" );
	
	DoMuzzleFlash();
}

//////////////////////////////////////////////////////////////////////////////////////////////////////

#if 0

class CMiniTurret : public CBaseTurret
{
	DECLARE_CLASS( CMiniTurret, CBaseTurret );
public:
	void Spawn( );
	void Precache(void);
	// other functions
	void Shoot(const Vector &vecSrc, const Vector &vecDirToEnemy);
};

LINK_ENTITY_TO_CLASS( npc_miniturret, CMiniTurret );


void CMiniTurret::Spawn()
{ 
	Precache( );
	SetModel( "models/miniturret.mdl" );
	m_iHealth			= sk_miniturret_health.GetFloat();
	m_HackedGunPos		= Vector( 0, 0, 12.75 );
	m_vecViewOffset.z = 12.75;
	m_flFieldOfView		= VIEW_FIELD_NARROW;

	CBaseTurret::Spawn( );

	m_iAmmoType = g_pGameRules->GetAmmoDef()->Index("Pistol");

	m_iRetractHeight = 16;
	m_iDeployHeight = 32;
	m_iMinPitch	= -45;
	UTIL_SetSize(this, Vector(-16, -16, -m_iRetractHeight), Vector(16, 16, m_iRetractHeight));

	SetThink(Initialize);	
	SetNextThink( gpGlobals->curtime + 0.3; );
}


void CMiniTurret::Precache()
{
	PrecacheModel ("models/miniturret.mdl");	

	PrecacheScriptSound( "MiniTurret.Shoot" );

	BaseClass::Precache();
}



void CMiniTurret::Shoot(const Vector &vecSrc, const Vector &vecDirToEnemy)
{
	FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, m_iAmmoType, 1 );

	EmitSound( "MiniTurret.Shoot" );

	DoMuzzleFlash();
}


#endif


//=========================================================
// Sentry gun - smallest turret, placed near grunt entrenchments
//=========================================================
class CSentry : public CBaseTurret
{
	DECLARE_CLASS( CSentry, CBaseTurret );
public:
	void Spawn( );
	void Precache(void);
	// other functions
	void Shoot(const Vector &vecSrc, const Vector &vecDirToEnemy);
	int OnTakeDamage( const CTakeDamageInfo &info );
	void SentryTouch( CBaseEntity *pOther );
	void SentryDeath( void );

protected:

	DECLARE_DATADESC();
};


BEGIN_DATADESC( CSentry )

	// Function pointers
	DEFINE_ENTITYFUNC( SentryTouch ),
	DEFINE_THINKFUNC( SentryDeath ),

END_DATADESC()


LINK_ENTITY_TO_CLASS( NPC_sentry, CSentry );

void CSentry::Precache()
{
	PrecacheModel ("models/sentry.mdl");	

	PrecacheScriptSound( "Sentry.Shoot" );
	PrecacheScriptSound( "Sentry.Die" );

	BaseClass::Precache();
}

void CSentry::Spawn()
{ 
	Precache( );
	SetModel( "models/sentry.mdl" );
	m_iHealth			= sk_sentry_health.GetFloat();
	m_HackedGunPos		= Vector( 0, 0, 48 );
	m_vecViewOffset.z		= 48;
	m_flMaxWait = 1E6;

	CBaseTurret::Spawn();
	m_iRetractHeight = 64;
	m_iDeployHeight = 64;
	m_iMinPitch	= -60;
	UTIL_SetSize(this, Vector(-16, -16, -m_iRetractHeight), Vector(16, 16, m_iRetractHeight));

	SetTouch(SentryTouch);
	SetThink(Initialize);	
	SetNextThink( gpGlobals->curtime + 0.3; );
}

void CSentry::Shoot(const Vector &vecSrc, const Vector &vecDirToEnemy)
{
	FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, m_iAmmoType, 1 );

	EmitSound( "Sentry.Shoot" );

	DoMuzzleFlash();
}


int CSentry::OnTakeDamage( const CTakeDamageInfo &info )
{
	if ( !m_takedamage )
		return 0;

	if (!m_iOn)
	{
		SetThink( Deploy );
		SetNextThink( gpGlobals->curtime + 0.1f );
	}

	m_iHealth -= info.GetDamage();
	if (m_iHealth <= 0)
	{
		m_iHealth = 0;
		m_takedamage = DAMAGE_NO;
		m_flDamageTime = gpGlobals->curtime;

		RemoveFlag( FL_NPC ); // why are they set in the first place???

		SetThink(SentryDeath);
		m_OnDamaged.FireOutput( info.GetInflictor(), this );
		SetNextThink( gpGlobals->curtime + 0.1f );

		return 0;
	}

	return 1;
}


void CSentry::SentryTouch( CBaseEntity *pOther )
{
	if ( pOther && (pOther->IsPlayer() || (pOther->GetFlags() & FL_NPC)) )
	{
		OnTakeDamage( CTakeDamageInfo( pOther, pOther, 0, 0 ) );
	}
}


void CSentry ::	SentryDeath( void )
{
	StudioFrameAdvance( );
	SetNextThink( gpGlobals->curtime + 0.1f );

	if (m_lifeState != LIFE_DEAD)
	{
		m_lifeState = LIFE_DEAD;

		EmitSound( "Sentry.Die" );

		SetPoseParameter( TURRET_BC_YAW, 0 );
		SetPoseParameter( TURRET_BC_PITCH, 0 );

		SetActivity( (Activity)ACT_TURRET_CLOSE );

		SetSolid( SOLID_NOT );
		QAngle angles = GetAngles();
		angles.y = UTIL_AngleMod( GetAngles().y + random->RandomInt( 0, 2 ) * 120 );
		SetAngles( angles );

		EyeOn( );
	}

	EyeOff( );

	Vector vecSrc;
	QAngle vecAng;
	GetAttachment( "eyes", vecSrc, vecAng );

	if (m_flDamageTime + random->RandomFloat( 0, 2 ) > gpGlobals->curtime)
	{
		// lots of smoke
		Vector pos = vecSrc + Vector( random->RandomFloat( -16, 16 ), 
				random->RandomFloat( -16, 16 ),
				 -32 );

		CBroadcastRecipientFilter filter;
		te->Smoke( filter, 0.0, &pos,
			g_sModelIndexSmoke,
			1.5,
			8 );
	}
	
	if (m_flDamageTime + random->RandomFloat( 0, 8 ) > gpGlobals->curtime)
	{
		g_pEffects->Sparks( vecSrc );
	}

	if (m_fSequenceFinished && m_flDamageTime + 5 < gpGlobals->curtime)
	{
		m_flPlaybackRate = 0;
		SetThink( NULL );
	}
}

