//========= Copyright 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("Combine_Round");

	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);
	}
}

