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

#include "cbase.h"
#include "baseanimating.h"
#include "ai_network.h"
#include "ai_default.h"
#include "ai_schedule.h"
#include "ai_hull.h"
#include "ai_node.h"
#include "ai_task.h"
#include "ai_motor.h"
#include "entitylist.h"
#include "basecombatweapon.h"
#include "soundenvelope.h"
#include "gib.h"
#include "gamerules.h"
#include "ammodef.h"
#include "cbasehelicopter.h"
#include "npcevent.h"
#include "ndebugoverlay.h"
#include "decals.h"
#include "explode.h" // temp (sjb)
#include "smoke_trail.h" // temp (sjb)
#include "IEffects.h"
#include "vstdlib/random.h"
#include "engine/IEngineSound.h"
#include "ar2_explosion.h"
#include "te_effect_dispatch.h"
#include "rope.h"
#include "effect_dispatch_data.h"
#include "trains.h"
#include "globals.h"
#include "physics_prop_ragdoll.h"
#include "iservervehicle.h"
#include "soundent.h"
#include "npc_citizen17.h"
#include "physics_saverestore.h"
#include "hl2_shareddefs.h"
#include "props.h"
#include "npc_attackchopper.h"
#include "citadel_effects_shared.h"
#include "eventqueue.h"
#include "beam_flags.h"
#include "ai_eventresponse.h"

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

#define GUNSHIP_MSG_BIG_SHOT			1
#define GUNSHIP_MSG_STREAKS				2

#define GUNSHIP_NUM_DAMAGE_OUTPUTS		4

extern short		g_sModelIndexFireball;		// holds the index for the fireball

int g_iGunshipEffectIndex = -1;

#define GUNSHIP_ACCEL_RATE 500

// Spawnflags
#define SF_GUNSHIP_NO_GROUND_ATTACK		( 1 << 12 )	
#define SF_GUNSHIP_USE_CHOPPER_MODEL	( 1 << 13 )

ConVar sk_gunship_burst_size("sk_gunship_burst_size", "15" );
ConVar sk_gunship_burst_min("sk_gunship_burst_min", "800" );
ConVar sk_gunship_burst_dist("sk_gunship_burst_dist", "768" );

// Number of times the gunship must be struck by explosive damage
ConVar	sk_gunship_health_increments( "sk_gunship_health_increments", "0" );

/*

Wedge's notes:

  Gunship should move its head according to flight model when the target is behind the gunship,
  or when the target is too far away to shoot at. Otherwise, the head should aim at the target.

	Negative angvelocity.y is a RIGHT turn.
	Negative angvelocity.x is UP

*/

#define GUNSHIP_AP_MUZZLE	5

#define GUNSHIP_MAX_SPEED			1056.0f

#define GUNSHIP_MAX_FIRING_SPEED	200.0f
#define GUNSHIP_MIN_ROCKET_DIST		1000.0f
#define GUNSHIP_MAX_GUN_DIST		2000.0f
#define GUNSHIP_ARRIVE_DIST			128.0f

#define GUNSHIP_HOVER_SPEED			300.0f // play hover animation if moving slower than this.

#define GUNSHIP_AE_THRUST			1

#define GUNSHIP_HEAD_MAX_UP			-65
#define GUNSHIP_HEAD_MAX_DOWN		60
#define GUNSHIP_HEAD_MAX_LEFT		60
#define GUNSHIP_HEAD_MAX_RIGHT		-60

#define	BASE_STITCH_VELOCITY		800		//Units per second
#define	MAX_STITCH_VELOCITY			1000	//Units per second

#define	GUNSHIP_LEAD_DISTANCE		800.0f
#define	GUNSHIP_AVOID_DIST			512.0f
#define	GUNSHIP_STITCH_MIN			512.0f

#define	GUNSHIP_MIN_CHASE_DIST_DIFF	128.0f	// Distance threshold used to determine when a target has moved enough to update our navigation to it

#define	MIN_GROUND_ATTACK_DIST			500.0f // Minimum distance a target has to be for the gunship to consider using the ground attack weapon
#define	MIN_GROUND_ATTACK_HEIGHT_DIFF	128.0f // Target's position and hit position must be within this threshold vertically

#define GUNSHIP_WASH_ALTITUDE		1024.0f

#define	GUNSHIP_MIN_DAMAGE_THRESHOLD	50.0f

#define GUNSHIP_INNER_NAV_DIST			400.0f
#define GUNSHIP_OUTER_NAV_DIST			800.0f

#define GUNSHIP_BELLYBLAST_TARGET_HEIGHT	512.0		// Height above targets that the gunship wants to be when bellyblasting

#define GUNSHIP_MISSILE_MAX_RESPONSE_TIME	0.4
#define GUNSHIP_MAX_HITS_PER_BURST			5

#define GUNSHIP_FLARE_IGNORE_TIME		6.0

//=====================================
// Custom activities
//=====================================
Activity ACT_GUNSHIP_PATROL;
Activity ACT_GUNSHIP_HOVER;
Activity ACT_GUNSHIP_CRASH;

#define	GUNSHIP_DEBUG_LEADING	1
#define	GUNSHIP_DEBUG_PATH		2
#define	GUNSHIP_DEBUG_STITCHING	3
#define GUNSHIP_DEBUG_BELLYBLAST 4

ConVar g_debug_gunship( "g_debug_gunship", "0", FCVAR_CHEAT );

//-----------------------------------------------------------------------------
// Purpose: Dying gunship ragdoll controller
//-----------------------------------------------------------------------------
class CGunshipRagdollMotion : public IMotionEvent
{
	DECLARE_SIMPLE_DATADESC();
public:
	virtual simresult_e	Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular )
	{
		linear = Vector(0,0,400);
		angular = Vector(0,600,100);

		return SIM_GLOBAL_ACCELERATION;
	}
};

BEGIN_SIMPLE_DATADESC( CGunshipRagdollMotion )
END_DATADESC()

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
class CTargetGunshipCrash : public CPointEntity
{
	DECLARE_CLASS( CTargetGunshipCrash, CPointEntity );
public:
	DECLARE_DATADESC();	

	void	InputEnable( inputdata_t &inputdata )
	{
		m_bDisabled = false;
	}
	void	InputDisable( inputdata_t &inputdata )
	{
		m_bDisabled = true;
	}
	bool	IsDisabled( void )
	{
		return m_bDisabled;
	}
	void	GunshipCrashedOnTarget( void )
	{
		m_OnCrashed.FireOutput( this, this );
	}

private:
	bool			m_bDisabled;

	COutputEvent	m_OnCrashed;
};

LINK_ENTITY_TO_CLASS( info_target_gunshipcrash, CTargetGunshipCrash );

BEGIN_DATADESC( CTargetGunshipCrash )
	DEFINE_FIELD( m_bDisabled, FIELD_BOOLEAN ),

	// Inputs
	DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
	DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),

	// Outputs
	DEFINE_OUTPUT( m_OnCrashed,			"OnCrashed" ),
END_DATADESC()


//===================================================================
// Gunship - the combine dugongic like attack vehicle.
//===================================================================
class CNPC_CombineGunship : public CBaseHelicopter
{
public:
	DECLARE_CLASS( CNPC_CombineGunship, CBaseHelicopter );

	CNPC_CombineGunship( void );
	~CNPC_CombineGunship( void );

	DECLARE_DATADESC();
	DECLARE_SERVERCLASS();
	DEFINE_CUSTOM_AI;

	void	PlayPatrolLoop( void );
	void	PlayAngryLoop( void );

	void	Spawn( void );
	void	Precache( void );
	void	OnRestore( void );
	void	PrescheduleThink( void );
	void	HelicopterPostThink( void );
	void	StopLoopingSounds( void );

	bool	IsValidEnemy( CBaseEntity *pEnemy );
	void	GatherEnemyConditions( CBaseEntity *pEnemy );

	void	Flight( void );

	bool	FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL );
	int		OnTakeDamage_Alive( const CTakeDamageInfo &info );
	void	FireDamageOutputsUpto( int iDamageNumber );

	virtual float GetAcceleration( void ) { return 15; }

	virtual void	MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType );
	virtual void	DoImpactEffect( trace_t &tr, int nDamageType );

	void	MoveHead( void );
	void	UpdateDesiredPosition( void );
	void	DoCombat( void );
	bool	ChooseEnemy( void );
	void	DoMuzzleFlash( void );
	void	Ping( void );

	void	FireCannonRound( void );

	// Gunship death process
	void	Event_Killed( const CTakeDamageInfo &info );
	void	BeginCrash( void );				// I'm going to go to a crash point and die there
	void	BeginDestruct( void );			// I want to die now, so create my ragdoll
	void	SelfDestruct( void );			// I'm now fully dead, so remove myself.
	void	CreateSmokeTrail( void );
	bool	FindNearestGunshipCrash( void );
	
	int		BloodColor( void ) { return DONT_BLEED; }
	void	GibMonster( void );

	void	UpdateRotorSoundPitch( int iPitch );
	void	InitializeRotorSound( void );

	void	ApplyGeneralDrag( void );
	void	ApplySidewaysDrag( const Vector &vecRight );

	void	TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr );

	void	UpdateEnemyTarget( void );

	Vector	GetEnemyTarget( void );
	Vector	GetMissileTarget( void );

	float	GroundDistToPosition( const Vector &pos );

	bool	FireGun( void );
	bool	IsTargettingMissile( void );
	
	Class_T Classify( void ) { return CLASS_COMBINE_GUNSHIP; } // for now
	float	GetAutoAimRadius() { return 144.0f; }

	// Input functions
	void	InputSetPenetrationDepth( inputdata_t &inputdata );
	void	InputOmniscientOn( inputdata_t &inputdata );
	void	InputOmniscientOff( inputdata_t &inputdata );
	void	InputBlindfireOn( inputdata_t &inputdata );
	void	InputBlindfireOff( inputdata_t &inputdata );
	void	InputSelfDestruct( inputdata_t &inputdata );
	void	InputSetDockingBBox( inputdata_t &inputdata );
	void	InputSetNormalBBox( inputdata_t &inputdata );
	void	InputEnableGroundAttack( inputdata_t &inputdata );
	void	InputDisableGroundAttack( inputdata_t &inputdata );
	void	InputDoGroundAttack( inputdata_t &inputdata );
	
	//NOTENOTE: I'm rather queasy about adding these, as they can lead to nasty bugs...
	void	InputBecomeInvulnerable( inputdata_t &inputdata );
	void	InputBecomeVulnerable( inputdata_t &inputdata );

	bool	PoseGunTowardTargetDirection( const Vector &vTargetDir );
	void	StartCannonBurst( int iBurstSize );
	void	StopCannonBurst( void );

	bool	CheckGroundAttack( void );
	void	StartGroundAttack( void );
	void	StopGroundAttack( bool bDoAttack );
	Vector	GetGroundAttackHitPosition( void );
	void	DoGroundAttackExplosion( void );
	void	DrawRotorWash( float flAltitude, const Vector &vecRotorOrigin );

	void	ManageWarningBeam( void );
	void	DoBellyBlastDamage( trace_t &tr, Vector vMins, Vector vMaxs );

	// Updates the facing direction
	void	UpdateFacingDirection( void );
	void	CreateBellyBlastEnergyCore( void );

protected:
	// Because the combine gunship is a leaf class, we can use
	// static variables to store this information, and save some memory.
	// Should the gunship end up having inheritors, their activate may
	// stomp these numbers, in which case you should make these ordinary members
	// again.
	static int m_poseFlex_Horz, m_poseFlex_Vert, m_posePitch, m_poseYaw, m_poseFin_Accel, m_poseFin_Sway;
	static int m_poseWeapon_Pitch, m_poseWeapon_Yaw;

	static bool m_sbStaticPoseParamsLoaded;
	virtual void	PopulatePoseParameters( void );

private:
	// Outputs
	COutputEvent	m_OnFireCannon;
	COutputEvent	m_OnCrashed;

	COutputEvent	m_OnFirstDamage;	// First damage tick
	COutputEvent	m_OnSecondDamage;
	COutputEvent	m_OnThirdDamage;
	COutputEvent	m_OnFourthDamage;
	// Keep track of which damage outputs we've fired. This is necessary
	// to ensure that the game doesn't break if a mapmaker has outputs that
	// must be fired on gunships, and the player switches skill levels 
	// midway through a gunship battle.
	bool			m_bDamageOutputsFired[GUNSHIP_NUM_DAMAGE_OUTPUTS];	

	float	m_flNextGroundAttack;	// Time to wait before the next ground attack
	bool	m_bIsGroundAttacking;	// Denotes that we are ground attacking
	bool	m_bCanGroundAttack;		// Denotes whether we can ground attack or not
	float	m_flGroundAttackTime;	// Delay before blast happens from ground attack

	CHandle<SmokeTrail>	m_pSmokeTrail;
	EHANDLE			m_hGroundAttackTarget;

	CSoundPatch		*m_pAirExhaustSound;
	CSoundPatch		*m_pAirBlastSound;
	CSoundPatch		*m_pCannonSound;

	CBaseEntity		*m_pRotorWashModel;
	QAngle			m_vecAngAcceleration;

	float			m_flEndDestructTime;

	int				m_iDoSmokePuff;
	int				m_iAmmoType;
	int				m_iBurstSize;
	
	bool			m_fBlindfire;
	bool			m_fOmniscient;
	bool			m_bIsFiring;
	int				m_iBurstHits;
	bool			m_bPreFire;
	bool			m_bInvulnerable;

	float			m_flTimeNextPing;
	float			m_flPenetrationDepth; 
	float			m_flDeltaT;
	float			m_flTimeNextAttack;
	float			m_flNextSeeEnemySound;
	float			m_flNextRocket;
	float			m_flBurstDelay;

	Vector			m_vecAttackPosition;
	Vector			m_vecAttackVelocity;

	// Used when the gunships using the chopper model
	Vector			m_angGun;

	// For my death throes
	IPhysicsMotionController		*m_pCrashingController;
	CGunshipRagdollMotion			m_crashCallback;
	EHANDLE							m_hRagdoll;
	CHandle<CTargetGunshipCrash>	m_hCrashTarget;
	float							m_flNextGunshipCrashFind;

	CHandle<CCitadelEnergyCore>		m_hEnergyCore;

	CNetworkVector( m_vecHitPos );

	// If true, playing patrol loop.
	// Else, playing angry.
	bool			m_fPatrolLoopPlaying;
};

LINK_ENTITY_TO_CLASS( npc_combinegunship, CNPC_CombineGunship );

IMPLEMENT_SERVERCLASS_ST( CNPC_CombineGunship, DT_CombineGunship )
	SendPropVector(SENDINFO(m_vecHitPos), -1, SPROP_COORD),
END_SEND_TABLE()

BEGIN_DATADESC( CNPC_CombineGunship )

	DEFINE_ENTITYFUNC( FlyTouch ),

	DEFINE_FIELD( m_flNextGroundAttack,FIELD_TIME ),
	DEFINE_FIELD( m_bIsGroundAttacking,FIELD_BOOLEAN ),
	DEFINE_FIELD( m_bCanGroundAttack,	FIELD_BOOLEAN ),
	DEFINE_FIELD( m_flGroundAttackTime,FIELD_TIME ),
	DEFINE_FIELD( m_pRotorWashModel,	FIELD_CLASSPTR ),
	DEFINE_FIELD( m_pSmokeTrail,		FIELD_EHANDLE ),
	DEFINE_FIELD( m_hGroundAttackTarget, FIELD_EHANDLE ),
	DEFINE_SOUNDPATCH( m_pAirExhaustSound ),
	DEFINE_SOUNDPATCH( m_pAirBlastSound ),
	DEFINE_SOUNDPATCH( m_pCannonSound ),
	DEFINE_FIELD( m_vecAngAcceleration,FIELD_VECTOR ),
	DEFINE_FIELD( m_flDeltaT,			FIELD_FLOAT ),
	DEFINE_FIELD( m_flTimeNextAttack,	FIELD_TIME ),
	DEFINE_FIELD( m_flNextSeeEnemySound,	FIELD_TIME ),
	DEFINE_FIELD( m_flEndDestructTime,	FIELD_TIME ),
	DEFINE_FIELD( m_flNextRocket,		FIELD_TIME ),
	DEFINE_FIELD( m_iDoSmokePuff,		FIELD_INTEGER ),
	DEFINE_FIELD( m_iAmmoType,			FIELD_INTEGER ),
	DEFINE_FIELD( m_iBurstSize,		FIELD_INTEGER ),
	DEFINE_FIELD( m_flBurstDelay,		FIELD_FLOAT ),
	DEFINE_FIELD( m_fBlindfire,		FIELD_BOOLEAN ),
	DEFINE_FIELD( m_fOmniscient,		FIELD_BOOLEAN ),
	DEFINE_FIELD( m_bIsFiring,			FIELD_BOOLEAN ),
	DEFINE_FIELD( m_iBurstHits,			FIELD_INTEGER ),
	DEFINE_FIELD( m_flTimeNextPing,	FIELD_TIME ),
	DEFINE_FIELD( m_flPenetrationDepth,FIELD_FLOAT ),
	DEFINE_FIELD( m_vecAttackPosition,	FIELD_VECTOR ),
	DEFINE_FIELD( m_vecAttackVelocity,	FIELD_VECTOR ),
	DEFINE_FIELD( m_angGun,				FIELD_VECTOR ),
	DEFINE_PHYSPTR( m_pCrashingController ),
	DEFINE_EMBEDDED( m_crashCallback ),
	DEFINE_FIELD( m_hRagdoll,			FIELD_EHANDLE ),
	DEFINE_FIELD( m_hCrashTarget,		FIELD_EHANDLE ),
	DEFINE_FIELD( m_vecHitPos,			FIELD_VECTOR ),
	DEFINE_FIELD( m_fPatrolLoopPlaying,FIELD_BOOLEAN ),
	DEFINE_FIELD( m_bPreFire,			FIELD_BOOLEAN ),
	DEFINE_FIELD( m_bInvulnerable,		FIELD_BOOLEAN ),
	DEFINE_FIELD( m_flNextGunshipCrashFind, FIELD_TIME ),

	DEFINE_FIELD( m_hEnergyCore, FIELD_EHANDLE ),

	DEFINE_ARRAY( m_bDamageOutputsFired, FIELD_BOOLEAN, GUNSHIP_NUM_DAMAGE_OUTPUTS ),

	// Function pointers
	DEFINE_INPUTFUNC( FIELD_VOID, "OmniscientOn", InputOmniscientOn ),
	DEFINE_INPUTFUNC( FIELD_VOID, "OmniscientOff", InputOmniscientOff ),
	DEFINE_INPUTFUNC( FIELD_FLOAT, "SetPenetrationDepth", InputSetPenetrationDepth ),
	DEFINE_INPUTFUNC( FIELD_VOID, "BlindfireOn", InputBlindfireOn ),
	DEFINE_INPUTFUNC( FIELD_VOID, "BlindfireOff", InputBlindfireOff ),
	DEFINE_INPUTFUNC( FIELD_VOID, "SelfDestruct", InputSelfDestruct ),
	DEFINE_INPUTFUNC( FIELD_VOID, "SetDockingBBox", InputSetDockingBBox ),
	DEFINE_INPUTFUNC( FIELD_VOID, "SetNormalBBox", InputSetNormalBBox ),
	DEFINE_INPUTFUNC( FIELD_VOID, "EnableGroundAttack", InputEnableGroundAttack ),
	DEFINE_INPUTFUNC( FIELD_VOID, "DisableGroundAttack", InputDisableGroundAttack ),
	DEFINE_INPUTFUNC( FIELD_STRING, "DoGroundAttack", InputDoGroundAttack ),

	DEFINE_OUTPUT( m_OnFireCannon,		"OnFireCannon" ),
	DEFINE_OUTPUT( m_OnFirstDamage,	"OnFirstDamage" ),
	DEFINE_OUTPUT( m_OnSecondDamage,	"OnSecondDamage" ),
	DEFINE_OUTPUT( m_OnThirdDamage,	"OnThirdDamage" ),
	DEFINE_OUTPUT( m_OnFourthDamage,	"OnFourthDamage" ),
	DEFINE_OUTPUT( m_OnCrashed,			"OnCrashed" ),

END_DATADESC()

//-----------------------------------------------------------------------------
// Constructor
//-----------------------------------------------------------------------------
CNPC_CombineGunship::CNPC_CombineGunship( void )
{ 
	m_hGroundAttackTarget = NULL;
	m_pSmokeTrail	= NULL;
	m_iAmmoType		= -1; 
	m_pCrashingController = NULL;
	m_hRagdoll = NULL;
	m_hCrashTarget = NULL;
}


void CNPC_CombineGunship::CreateBellyBlastEnergyCore( void )
{
	CCitadelEnergyCore *pCore = static_cast<CCitadelEnergyCore*>( CreateEntityByName( "env_citadel_energy_core" ) );

	if ( pCore == NULL )
		return;

	m_hEnergyCore = pCore;

	int iAttachment = LookupAttachment( "BellyGun" );

	Vector vOrigin;
	QAngle vAngle;

	GetAttachment( iAttachment, vOrigin, vAngle );

	pCore->SetAbsOrigin( vOrigin );
	pCore->SetAbsAngles( vAngle );

	DispatchSpawn( pCore );
	pCore->Activate();

	pCore->SetParent( this, iAttachment );
	pCore->SetScale( 4.0f );
}

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

	if ( HasSpawnFlags( SF_GUNSHIP_USE_CHOPPER_MODEL ) )
	{
		SetModel( "models/combine_helicopter.mdl" );
	}
	else
	{
		SetModel( "models/gunship.mdl" );
	}
	
	ExtractBbox( SelectHeaviestSequence( ACT_GUNSHIP_PATROL ), m_cullBoxMins, m_cullBoxMaxs ); 
	BaseClass::Spawn();

	InitPathingData( GUNSHIP_ARRIVE_DIST, GUNSHIP_MIN_CHASE_DIST_DIFF, sk_gunship_burst_min.GetFloat() );
	AddEFlags( EFL_NO_DISSOLVE | EFL_NO_MEGAPHYSCANNON_RAGDOLL | EFL_NO_PHYSCANNON_INTERACTION );

	m_takedamage = DAMAGE_YES;

	SetHullType(HULL_LARGE_CENTERED);
	SetHullSizeNormal();

	m_iMaxHealth = m_iHealth = 100;

	m_flFieldOfView = -0.707; // 270 degrees

	m_fHelicopterFlags |= BITS_HELICOPTER_GUN_ON;

	InitBoneControllers();

	InitCustomSchedules();

	SetActivity( (Activity)ACT_GUNSHIP_PATROL );
	SetCollisionGroup( HL2COLLISION_GROUP_GUNSHIP );

	m_flMaxSpeed = GUNSHIP_MAX_SPEED;
	m_flMaxSpeedFiring = GUNSHIP_MAX_SPEED;

	m_flTimeNextAttack = gpGlobals->curtime;
	m_flNextSeeEnemySound = gpGlobals->curtime;

	// Init the pose parameters
	SetPoseParameter( "flex_horz", 0 );
	SetPoseParameter( "flex_vert", 0 );
	SetPoseParameter( "fin_accel", 0 );
	SetPoseParameter( "fin_sway", 0 );

	if( m_iAmmoType == -1 )
	{
		// Since there's no weapon to index the ammo type,
		// do it manually here.
		m_iAmmoType = GetAmmoDef()->Index("CombineCannon"); 
	}

	//!!!HACKHACK
	// This tricks the AI code that constantly complains that the gunship has no schedule.
	SetSchedule( SCHED_IDLE_STAND );

	AddRelationship( "env_flare D_LI 9",	NULL );
	AddRelationship( "rpg_missile D_HT 99", NULL );

	m_flTimeNextPing = gpGlobals->curtime + 2;

	m_flPenetrationDepth = 24;
	m_flBurstDelay = 2.0f;

	// Blindfire and Omniscience default to off
	m_fBlindfire		= false;
	m_fOmniscient		= false;
	m_bIsFiring			= false;
	m_bPreFire			= false;
	m_bInvulnerable		= false;
	
	// See if we should start being able to attack
	m_bCanGroundAttack	= ( m_spawnflags & SF_GUNSHIP_NO_GROUND_ATTACK ) ? false : true;

	m_flEndDestructTime = 0;

	m_iBurstSize = 0;
	m_iBurstHits = 0;

	// Do not dissolve
	AddEFlags( EFL_NO_DISSOLVE );

	for ( int i = 0; i < GUNSHIP_NUM_DAMAGE_OUTPUTS; i++ )
	{
		m_bDamageOutputsFired[i] = false;
	}

	CapabilitiesAdd( bits_CAP_SQUAD);

	if ( hl2_episodic.GetBool() == true )
	{
		CreateBellyBlastEnergyCore();
	}

	// Allows autoaim to help attack the gunship.
	if( g_pGameRules->GetAutoAimMode() == AUTOAIM_ON_CONSOLE )
	{
		AddFlag( FL_AIMTARGET );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Restore the motion controller
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::OnRestore( void )
{
	BaseClass::OnRestore();

	if ( m_pCrashingController )
	{
		m_pCrashingController->SetEventHandler( &m_crashCallback );
	}
}

//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CNPC_CombineGunship::Precache( void )
{
	if ( HasSpawnFlags( SF_GUNSHIP_USE_CHOPPER_MODEL ) )
	{
		PrecacheModel( "models/combine_helicopter.mdl" );
		Chopper_PrecacheChunks( this );
	}
	else
	{
		PrecacheModel("models/gunship.mdl");
	}

	PrecacheModel("sprites/lgtning.vmt");

	PrecacheMaterial( "effects/ar2ground2" );
	PrecacheMaterial( "effects/blueblackflash" );
	
	PrecacheScriptSound( "NPC_CombineGunship.SearchPing" );
	PrecacheScriptSound( "NPC_CombineGunship.PatrolPing" );
	PrecacheScriptSound( "NPC_Strider.Charge" );
	PrecacheScriptSound( "NPC_Strider.Shoot" );
	PrecacheScriptSound( "NPC_CombineGunship.SeeEnemy" );
	PrecacheScriptSound( "NPC_CombineGunship.CannonStartSound" );
	PrecacheScriptSound( "NPC_CombineGunship.Explode");
	PrecacheScriptSound( "NPC_CombineGunship.Pain" );
	PrecacheScriptSound( "NPC_CombineGunship.CannonStopSound" );

	PrecacheScriptSound( "NPC_CombineGunship.DyingSound" );
	PrecacheScriptSound( "NPC_CombineGunship.CannonSound" );
	PrecacheScriptSound( "NPC_CombineGunship.RotorSound" );
	PrecacheScriptSound( "NPC_CombineGunship.ExhaustSound" );
	PrecacheScriptSound( "NPC_CombineGunship.RotorBlastSound" );

	if ( hl2_episodic.GetBool() == true )
	{
		UTIL_PrecacheOther( "env_citadel_energy_core" );
		g_iGunshipEffectIndex = PrecacheModel( "sprites/physbeam.vmt" );
	}

	PropBreakablePrecacheAll( MAKE_STRING("models/gunship.mdl") );

	BaseClass::Precache();
}

//-----------------------------------------------------------------------------
// Purpose: Cache whatever pose parameters we intend to use
//-----------------------------------------------------------------------------
bool CNPC_CombineGunship::m_sbStaticPoseParamsLoaded = false;
int CNPC_CombineGunship::m_poseFlex_Horz = 0; 
int CNPC_CombineGunship::m_poseFlex_Vert = 0; 
int CNPC_CombineGunship::m_posePitch = 0; 
int CNPC_CombineGunship::m_poseYaw = 0; 
int CNPC_CombineGunship::m_poseFin_Accel = 0; 
int CNPC_CombineGunship::m_poseFin_Sway = 0; 
int CNPC_CombineGunship::m_poseWeapon_Pitch = 0; 
int CNPC_CombineGunship::m_poseWeapon_Yaw = 0;
void	CNPC_CombineGunship::PopulatePoseParameters( void )
{
	if (!m_sbStaticPoseParamsLoaded)
	{
		m_poseFlex_Horz		= LookupPoseParameter( "flex_horz");
		m_poseFlex_Vert			= LookupPoseParameter( "flex_vert" );
		m_posePitch  = LookupPoseParameter( "pitch" );
		m_poseYaw   = LookupPoseParameter( "yaw" );
		m_poseFin_Accel   = LookupPoseParameter( "fin_accel" );
		m_poseFin_Sway   = LookupPoseParameter( "fin_sway" );

		m_poseWeapon_Pitch		= LookupPoseParameter( "weapon_pitch" );
		m_poseWeapon_Yaw		= LookupPoseParameter( "weapon_yaw" );

		m_sbStaticPoseParamsLoaded = true;
	}

	BaseClass::PopulatePoseParameters();
}


//------------------------------------------------------------------------------
// Purpose : 
//------------------------------------------------------------------------------
CNPC_CombineGunship::~CNPC_CombineGunship(void)
{
	StopLoopingSounds();

	if ( m_pCrashingController )
	{
		physenv->DestroyMotionController( m_pCrashingController );
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::Ping( void )
{
	if( IsCrashing() )
		return;

	if( GetEnemy() != NULL )
	{
		if( !HasCondition(COND_SEE_ENEMY) && gpGlobals->curtime > m_flTimeNextPing )
		{
			EmitSound( "NPC_CombineGunship.SearchPing" );
			m_flTimeNextPing = gpGlobals->curtime + 3;
		}
	}
	else
	{
		if( gpGlobals->curtime > m_flTimeNextPing )
		{
			EmitSound( "NPC_CombineGunship.PatrolPing" );
			m_flTimeNextPing = gpGlobals->curtime + 3;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &pos - 
// Output : float
//-----------------------------------------------------------------------------
float CNPC_CombineGunship::GroundDistToPosition( const Vector &pos )
{
	Vector vecDiff;
	VectorSubtract( GetAbsOrigin(), pos, vecDiff );

	// Only interested in the 2d dist
	vecDiff.z = 0;

	return vecDiff.Length();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::PlayPatrolLoop( void )
{
	m_fPatrolLoopPlaying = true;
	/*
	CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
	controller.SoundChangeVolume( m_pPatrolSound, 1.0, 1.0 );
	controller.SoundChangeVolume( m_pAngrySound, 0.0, 1.0 );
	*/
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::PlayAngryLoop( void )
{
	m_fPatrolLoopPlaying = false;
	/*
	CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
	controller.SoundChangeVolume( m_pPatrolSound, 0.0, 1.0 );
	controller.SoundChangeVolume( m_pAngrySound, 1.0, 1.0 );
	*/
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::HelicopterPostThink( void )
{
	// After HelicopterThink()
	if ( HasCondition( COND_ENEMY_DEAD ) )
	{
		if ( m_bIsFiring )
		{
			// Fire more shots at the dead body for effect
			if ( m_iBurstSize > 8 )
			{
				m_iBurstSize = 8;
			}
		}

		// Fade out search sound, fade in patrol sound.
		PlayPatrolLoop();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Vector
//-----------------------------------------------------------------------------
Vector CNPC_CombineGunship::GetGroundAttackHitPosition( void )
{
	trace_t	tr;
	Vector	vecShootPos, vecShootDir;

	GetAttachment( "BellyGun", vecShootPos, &vecShootDir, NULL, NULL );

	AI_TraceLine( vecShootPos, vecShootPos + Vector( 0, 0, -MAX_TRACE_LENGTH ), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );

	if ( m_hGroundAttackTarget )
	{
		return Vector( tr.endpos.x, tr.endpos.y, m_hGroundAttackTarget->WorldSpaceCenter().z );
	}
	return tr.endpos;	
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_CombineGunship::CheckGroundAttack( void )
{
	if ( m_bCanGroundAttack == false )
		return false;

	if ( m_bIsGroundAttacking )
		return false;

	// Must have an enemy
	if ( GetEnemy() == NULL )
		return false;

	// Must not have done it too recently
	if ( m_flNextGroundAttack > gpGlobals->curtime )
		return false;

	Vector	predPos, predDest;
	
	// Find where the enemy is most likely to be in two seconds
	UTIL_PredictedPosition( GetEnemy(), 1.0f, &predPos );
	UTIL_PredictedPosition( this, 1.0f, &predDest );

	Vector	predGap = ( predDest - predPos );
	predGap.z = 0;

	float	predDistance = predGap.Length();

	// Must be within distance
	if ( predDistance > MIN_GROUND_ATTACK_DIST )
		return false;

	// Can't ground attack missiles
	if ( IsTargettingMissile() )
		return false;

	//FIXME: Check to make sure we're not firing too far above or below the target
	if ( fabs( GetGroundAttackHitPosition().z - GetEnemy()->WorldSpaceCenter().z ) > MIN_GROUND_ATTACK_HEIGHT_DIFF )
		return false;

	//FIXME: Check for ground movement capabilities?

	//TODO: Check for friendly-fire

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::StartGroundAttack( void )
{
	// Mark us as attacking
	m_bIsGroundAttacking = true;
	m_flGroundAttackTime = gpGlobals->curtime + 3.0f;

	// Setup the attack effects
	Vector	vecShootPos;

	GetAttachment( "BellyGun", vecShootPos );

	EntityMessageBegin( this, true );
		WRITE_BYTE( GUNSHIP_MSG_STREAKS );
		WRITE_VEC3COORD( vecShootPos );
	MessageEnd();

	CPASAttenuationFilter filter2( this, "NPC_Strider.Charge" );
	EmitSound( filter2, entindex(), "NPC_Strider.Charge" );

	Vector	endpos = GetGroundAttackHitPosition();
	
	CSoundEnt::InsertSound ( SOUND_DANGER, endpos, 1024, 0.5f );

	if ( hl2_episodic.GetBool() == true )
	{
		if ( m_hEnergyCore )
		{
			variant_t value;
			value.SetFloat( 3.0f );

			g_EventQueue.AddEvent( m_hEnergyCore, "StartCharge", value, 0, this, this );
		}
	}
}

#define GUNSHIP_BELLY_BLAST_RADIUS 256.0f
#define BELLY_BLAST_MAX_PUNCH 5

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::ManageWarningBeam( void )
{
	Vector vecSrc, vecShootDir;
	GetAttachment( "BellyGun", vecSrc, NULL, NULL, NULL );

	trace_t	tr;
	CTraceFilterSkipTwoEntities filter( m_hGroundAttackTarget, this, COLLISION_GROUP_NONE );

	UTIL_TraceLine( vecSrc, m_vecHitPos, MASK_SOLID, &filter, &tr );

	int iPunch = 0;

	while ( tr.endpos != m_vecHitPos )
	{
		iPunch++;

		if ( iPunch > BELLY_BLAST_MAX_PUNCH )
			break;

		if ( tr.fraction != 1.0 )
		{
			if ( tr.m_pEnt )
			{
				CTakeDamageInfo	info( this, this, 1.0f, DMG_ENERGYBEAM );

				Vector vTargetDir = tr.m_pEnt->BodyTarget( tr.endpos, false ) - tr.endpos;

				VectorNormalize( vTargetDir );

				info.SetDamagePosition( tr.endpos + ( tr.plane.normal * 64.0f ) );
				info.SetDamageForce( vTargetDir * 100 );

				if ( tr.m_pEnt->m_takedamage != DAMAGE_NO )
				{
					// Deal damage
					tr.m_pEnt->TakeDamage( info );
				}
			}

			Vector vDir = m_vecHitPos - vecSrc;
			VectorNormalize( vDir );

			Vector vStartPunch = tr.endpos + vDir * 1;

			UTIL_TraceLine( vStartPunch, m_vecHitPos, MASK_SOLID, &filter, &tr );

			if ( tr.startsolid )
			{
				float flLength = (vStartPunch - tr.endpos).Length();

				Vector vEndPunch = vStartPunch + vDir * ( flLength * tr.fractionleftsolid );

				UTIL_TraceLine( vEndPunch, m_vecHitPos, MASK_SOLID, &filter, &tr );

				trace_t tr2;
				UTIL_TraceLine( vEndPunch, vEndPunch - vDir * 2, MASK_SOLID, &filter, &tr2 );

				if ( (m_flGroundAttackTime - gpGlobals->curtime) <= 2.0f )
				{
					g_pEffects->EnergySplash( tr2.endpos + vDir * 8, tr2.plane.normal, true );
				}

				g_pEffects->Sparks( tr2.endpos, (int)(3.0f - (m_flGroundAttackTime-gpGlobals->curtime)), (int)(3.5f - (m_flGroundAttackTime-gpGlobals->curtime)), &tr2.plane.normal );

			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::DoBellyBlastDamage( trace_t &tr, Vector vMins, Vector vMaxs )
{
	CBaseEntity*	pList[100];

	if ( g_debug_gunship.GetInt() == GUNSHIP_DEBUG_BELLYBLAST )
	{
		NDebugOverlay::Box( tr.endpos, vMins, vMaxs, 255, 255, 0, true, 5.0f );
	}

	int count = UTIL_EntitiesInBox( pList, 100, tr.endpos + vMins, tr.endpos + vMaxs, 0 );

	for ( int i = 0; i < count; i++ )
	{
		CBaseEntity *pEntity = pList[i];

		if ( pEntity == this )
			continue;

		if ( pEntity->m_takedamage == DAMAGE_NO )
			continue;

		float damage = 150;

		if ( pEntity->IsPlayer() )
		{
			float damageDist = ( pEntity->GetAbsOrigin() - tr.endpos ).Length();
			damage = RemapValClamped( damageDist, 0, 300, 200, 0 );
		}

		CTakeDamageInfo	info( this, this, damage, DMG_DISSOLVE );

		Vector vTargetDir = pEntity->BodyTarget( tr.endpos, false ) - tr.endpos;

		VectorNormalize( vTargetDir );

		info.SetDamagePosition( tr.endpos + ( tr.plane.normal * 64.0f ) );
		info.SetDamageForce( vTargetDir * 25000 );

		// Deal damage
		pEntity->TakeDamage( info );

		trace_t	groundTrace;
		UTIL_TraceLine( pEntity->GetAbsOrigin(), pEntity->GetAbsOrigin() - Vector( 0, 0, 256 ), MASK_SOLID, pEntity, COLLISION_GROUP_NONE, &groundTrace );

		if ( tr.fraction < 1.0f )
		{
			CEffectData	data;

			// Find the floor and add a dissolve explosion at that point
			data.m_flRadius = GUNSHIP_BELLY_BLAST_RADIUS * 0.5f;
			data.m_vNormal	= groundTrace.plane.normal;
			data.m_vOrigin	= groundTrace.endpos;

			DispatchEffect( "AR2Explosion", data );
		}

		// If the creature was killed, then dissolve it
		if ( pEntity->GetHealth() <= 0.0f )
		{
			if ( pEntity->GetBaseAnimating() != NULL && !pEntity->IsEFlagSet( EFL_NO_DISSOLVE ) )
			{
				pEntity->GetBaseAnimating()->Dissolve( NULL, gpGlobals->curtime );
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::DoGroundAttackExplosion( void )
{
	// Fire the bullets
	Vector vecSrc, vecShootDir;
	Vector vecAttachmentOrigin;
	GetAttachment( "BellyGun", vecAttachmentOrigin, &vecShootDir, NULL, NULL );

	vecSrc = vecAttachmentOrigin;

	if ( m_hGroundAttackTarget )
	{
		vecSrc = m_hGroundAttackTarget->GetAbsOrigin();
	}

	Vector impactPoint = vecSrc + ( Vector( 0, 0, -1 ) * MAX_TRACE_LENGTH );

	trace_t	tr;
	UTIL_TraceLine( vecSrc, impactPoint, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
	UTIL_DecalTrace( &tr, "Scorch" );

	if ( hl2_episodic.GetBool() == true )
	{
		g_pEffects->EnergySplash( tr.endpos, tr.plane.normal );

		CBroadcastRecipientFilter filter;
		te->BeamRingPoint( filter, 0.0, 
			tr.endpos,							//origin
			0,									//start radius
			GUNSHIP_BELLY_BLAST_RADIUS,			//end radius
			g_iGunshipEffectIndex,				//texture
			0,									//halo index
			0,									//start frame
			0,									//framerate
			0.2,								//life
			10,									//width
			0,									//spread
			0,									//amplitude
			255,								//r
			255,								//g
			255,								//b
			50,									//a
			0,									//speed
			FBEAM_FADEOUT
			);
	}

	// Send the effect over
	CEffectData	data;

	// Do an extra effect if we struck the world
	if ( tr.m_pEnt && tr.m_pEnt->IsWorld() )
	{
		data.m_flRadius = GUNSHIP_BELLY_BLAST_RADIUS;
		data.m_vNormal	= tr.plane.normal;
		data.m_vOrigin	= tr.endpos;
		
		DispatchEffect( "AR2Explosion", data );
	}

	float flZLength = vecAttachmentOrigin.z - tr.endpos.z;

	Vector vBeamMins = Vector( -16, -16, 0 );
	Vector vBeamMaxs = Vector( 16, 16, flZLength );

	DoBellyBlastDamage( tr, vBeamMins, vBeamMaxs );

	Vector vBlastMins = Vector( -GUNSHIP_BELLY_BLAST_RADIUS, -GUNSHIP_BELLY_BLAST_RADIUS, 0 );
	Vector vBlastMaxs = Vector( GUNSHIP_BELLY_BLAST_RADIUS, GUNSHIP_BELLY_BLAST_RADIUS, 96 );

	DoBellyBlastDamage( tr, vBlastMins, vBlastMaxs );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::StopGroundAttack( bool bDoAttack )
{
	if ( !m_bIsGroundAttacking )
		return;

	// Mark us as no longer attacking
	m_bIsGroundAttacking = false;
	m_flNextGroundAttack = gpGlobals->curtime + 4.0f;
	m_flTimeNextAttack	 = gpGlobals->curtime + 2.0f;

	Vector	hitPos = GetGroundAttackHitPosition();

	// tell the client side effect to complete
	EntityMessageBegin( this, true );
		WRITE_BYTE( GUNSHIP_MSG_BIG_SHOT );
		WRITE_VEC3COORD( hitPos );
	MessageEnd();

	if ( hl2_episodic.GetBool() == true )
	{
		if ( m_hEnergyCore )
		{
			variant_t value;
			value.SetFloat( 1.0f );

			g_EventQueue.AddEvent( m_hEnergyCore, "Stop", value, 0, this, this );
		}
	}

	// Only attack if told to
	if ( bDoAttack )
	{
		CPASAttenuationFilter filter2( this, "NPC_Strider.Shoot" );
		EmitSound( filter2, entindex(), "NPC_Strider.Shoot");

		ApplyAbsVelocityImpulse( Vector( 0, 0, 200.0f ) );

		//ExplosionCreate( hitPos, QAngle( 0, 0, 1 ), this, 500, 500, true );
		DoGroundAttackExplosion();
	}

	// If we were attacking a target, revert to our previous target
	if ( m_hGroundAttackTarget )
	{
		m_hGroundAttackTarget = NULL;
		if ( GetDestPathTarget() )
		{
			// Return to our old path
			SetupNewCurrentTarget( GetDestPathTarget() );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::DrawRotorWash( float flAltitude, const Vector &vecRotorOrigin )
{
	// If we have a ragdoll, we want the wash under that, not me
	if ( m_hRagdoll )
	{
		BaseClass::DrawRotorWash( flAltitude, m_hRagdoll->GetAbsOrigin() );
		return;
	}

	BaseClass::DrawRotorWash( flAltitude, vecRotorOrigin );
}

//------------------------------------------------------------------------------
// Purpose : Override the desired position if your derived helicopter is doing something special
//------------------------------------------------------------------------------
void CNPC_CombineGunship::UpdateDesiredPosition( void )
{
	if ( m_hCrashTarget )
	{
		SetDesiredPosition( m_hCrashTarget->WorldSpaceCenter() + Vector(0,0,128) );
	}
	else if ( m_hGroundAttackTarget )
	{
		SetDesiredPosition( m_hGroundAttackTarget->GetAbsOrigin() + Vector(0,0,GUNSHIP_BELLYBLAST_TARGET_HEIGHT) );
	}
}

//-----------------------------------------------------------------------------
// Purpose: do all of the stuff related to having an enemy, attacking, etc.
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::DoCombat( void )
{
	// Check for enemy change-overs
	if ( HasEnemy() )
	{
		if ( HasCondition( COND_NEW_ENEMY ) )
		{
			if ( GetEnemy() && GetEnemy()->IsPlayer() && m_flNextSeeEnemySound < gpGlobals->curtime )
			{
				m_flNextSeeEnemySound = gpGlobals->curtime + 5.0;

				if ( !HasSpawnFlags( SF_GUNSHIP_USE_CHOPPER_MODEL ) )
				{
					EmitSound( "NPC_CombineGunship.SeeEnemy" );
				}
			}

			// If we're shooting at a missile, do it immediately!
			if ( IsTargettingMissile() )
			{
				EmitSound( "NPC_CombineGunship.SeeMissile" );

				// Allow the gunship to attack again immediately
				if ( ( m_flTimeNextAttack > gpGlobals->curtime ) && ( ( m_flTimeNextAttack - gpGlobals->curtime ) > GUNSHIP_MISSILE_MAX_RESPONSE_TIME ) )
				{
					m_flTimeNextAttack = gpGlobals->curtime + GUNSHIP_MISSILE_MAX_RESPONSE_TIME;
					m_iBurstSize = sk_gunship_burst_size.GetInt();
				}
			}

			// Fade in angry sound, fade out patrol sound.
			PlayAngryLoop();
		}
	}

	// Do we have a belly blast target?
	if ( m_hGroundAttackTarget && !m_bIsGroundAttacking )
	{
		// If we're over it, blast. Can't use GetDesiredPosition() because it's not updated yet.
		Vector vecTarget = m_hGroundAttackTarget->GetAbsOrigin() + Vector(0,0,GUNSHIP_BELLYBLAST_TARGET_HEIGHT);
		Vector vecToTarget = (vecTarget - GetAbsOrigin());
		float flDistance = vecToTarget.Length();

		// Get the difference between our velocity & the target's velocity
		Vector vec2DVelocity = GetAbsVelocity();
		Vector vec2DTargetVelocity = m_hGroundAttackTarget->GetAbsVelocity();
		vec2DVelocity.z = vec2DTargetVelocity.z = 0;
		float flVelocityDiff = (vec2DVelocity - vec2DTargetVelocity).Length();
		if ( flDistance < 100 && flVelocityDiff < 200 )
		{
			StartGroundAttack();
		}
	}

	// Update our firing
	if ( m_bIsFiring )
	{
		// Fire if we have rounds remaining in this burst
		if ( ( m_iBurstSize > 0 ) && ( gpGlobals->curtime > m_flTimeNextAttack ) )
		{
			UpdateEnemyTarget();
			FireCannonRound();
		}
		else if ( m_iBurstSize < 1 )
		{
			// We're done firing
			StopCannonBurst();
			
			if ( IsTargettingMissile() )
			{
				m_flTimeNextAttack = gpGlobals->curtime + 0.5f;
			}
		}
	}
	else
	{
		// If we're not firing, look at the enemy
		if ( GetEnemy() )
		{
			m_vecAttackPosition = GetEnemy()->EyePosition();
		}

#ifdef BELLYBLAST
		// Check for a ground attack
		if ( CheckGroundAttack() )
		{
			StartGroundAttack();
		}
#endif

		// See if we're attacking
		if ( m_bIsGroundAttacking )
		{
			m_vecHitPos = GetGroundAttackHitPosition();

			ManageWarningBeam();

			// If our time is up, fire the blast and be done
			if ( m_flGroundAttackTime < gpGlobals->curtime )
			{
				// Fire!
				StopGroundAttack( true );
			}
		}
	}

	// If we're using the chopper model, align the gun towards the target
	if ( HasSpawnFlags( SF_GUNSHIP_USE_CHOPPER_MODEL ) )
	{
		Vector vGunPosition;
		GetAttachment( "gun", vGunPosition );
		Vector vecToAttackPos = (m_vecAttackPosition - vGunPosition);
		PoseGunTowardTargetDirection( vecToAttackPos );
	}

	// Forget flares once I've seen them for a while
	float flDeltaSeen = m_flLastSeen - m_flPrevSeen;
	if ( GetEnemy() != NULL && GetEnemy()->Classify() == CLASS_FLARE && flDeltaSeen > GUNSHIP_FLARE_IGNORE_TIME )
	{
		AddEntityRelationship( GetEnemy(), D_NU, 5 );

		PlayPatrolLoop();

		// Forget the flare now.
		SetEnemy( NULL );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_CombineGunship::ChooseEnemy( void )
{
	// If we're firing, don't switch enemies. This stops the gunship occasionally 
	// stopping a burst before he's really fired at all, which makes him look indecisive.
	if ( m_bIsFiring )
		return true;

	return BaseClass::ChooseEnemy();
}

//-----------------------------------------------------------------------------
// Purpose: There's a lot of code in here now. We should consider moving 
//			helicopters and such to scheduled AI. (sjb)
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::MoveHead( void )
{
	float flYaw = GetPoseParameter( m_poseFlex_Horz );
	float flPitch = GetPoseParameter( m_poseFlex_Vert );

/*
	This head-turning code will cause the head to POP when switching from looking at the enemy
	to looking according to the flight model. I will fix this later. Right now I'm turning
	the code over to Ken for some aiming fixups. (sjb)
*/

	while( 1 )
	{
		if ( GetEnemy() != NULL )
		{
			Vector vecToEnemy, vecAimDir;
			float	flDot;

			Vector vTargetPos, vGunPosition;
			Vector vecTargetOffset;
			QAngle vGunAngles;

			GetAttachment( "muzzle", vGunPosition, vGunAngles );

			vTargetPos = GetEnemyTarget();

			VectorSubtract( vTargetPos, vGunPosition, vecToEnemy );
			VectorNormalize( vecToEnemy );
			
			// get angles relative to body position
			AngleVectors( GetAbsAngles(), &vecAimDir );
			flDot = DotProduct( vecAimDir, vecToEnemy );

			// Look at Enemy!!
			if ( flDot > 0.3f )
			{
				float flDiff;

				float flDesiredYaw = VecToYaw(vTargetPos - vGunPosition);
				flDiff = UTIL_AngleDiff( flDesiredYaw, vGunAngles.y ) * 0.90;
				flYaw = UTIL_Approach( flYaw + flDiff, flYaw, 5.0 );	

				float flDesiredPitch = UTIL_VecToPitch(vTargetPos - vGunPosition);
				flDiff = UTIL_AngleDiff( flDesiredPitch, vGunAngles.x ) * 0.90;
				flPitch = UTIL_Approach( flPitch + flDiff, flPitch, 5.0 );	

				break;
			}
		}
 
		// Look where going!
#if 1 // old way- look according to rotational velocity
		flYaw = UTIL_Approach( GetLocalAngularVelocity().y, flYaw, 2.0 * 10 * m_flDeltaT );	
		flPitch = UTIL_Approach( GetLocalAngularVelocity().x, flPitch, 2.0 * 10 * m_flDeltaT );	
#else // new way- look towards the next waypoint?
		// !!!UNDONE
#endif
		break;
	}

	// Set the body flexes
	SetPoseParameter( m_poseFlex_Vert, flPitch );
	SetPoseParameter( m_poseFlex_Horz, flYaw );
}


//-----------------------------------------------------------------------------
// Purpose: There's a lot of code in here now. We should consider moving 
//			helicopters and such to scheduled AI. (sjb)
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::PrescheduleThink( void )
{
	m_flDeltaT = gpGlobals->curtime - GetLastThink();

	// Are we crashing?
	if ( m_flEndDestructTime && gpGlobals->curtime > m_flEndDestructTime )
	{
		// We're dead, remove ourselves
		SelfDestruct();
		return;
	}

	if( m_lifeState == LIFE_ALIVE )
	{
		// Chopper doesn't ping
		if ( !HasSpawnFlags( SF_GUNSHIP_USE_CHOPPER_MODEL ) )
		{
			Ping();
		}

		DoCombat();
		MoveHead();
	}
	else if( m_lifeState == LIFE_DYING )
	{
		// Increase the number of explosions as he gets closer to death
		bool bCreateExplosion = false;
		float flTimeLeft = m_flEndDestructTime - gpGlobals->curtime;
		if ( flTimeLeft > 1.5 )
		{
			bCreateExplosion = (random->RandomInt( 0, 3 ) == 0);
		}
		else 
		{
			bCreateExplosion = (random->RandomInt( 0, 2 ) == 0);
		}

		if ( bCreateExplosion )
		{
			Vector explodePoint;
			if ( m_hRagdoll )
			{
				m_hRagdoll->CollisionProp()->RandomPointInBounds( Vector(0.25,0.25,0.25), Vector(0.75,0.75,0.75), &explodePoint );
			}
			else
			{
				CollisionProp()->RandomPointInBounds( Vector(0.25,0.25,0.25), Vector(0.75,0.75,0.75), &explodePoint );

				// Knock the gunship a little, but not if we're trying to fly to a point
				if ( !m_hCrashTarget )
				{
					Vector vecPush = (GetAbsOrigin() - explodePoint);
					VectorNormalize( vecPush );
					ApplyAbsVelocityImpulse( vecPush * 128 );
				}
			}

			ExplosionCreate( explodePoint, QAngle(0,0,1), this, 100, 128, false );
		}

		// Have we reached our crash point?
		if ( m_flNextGunshipCrashFind && !m_hRagdoll )
		{
			// Update nearest crash point. The RPG that killed us may have knocked us
			// closer to a different point than the one we were near when we first died.
			if ( m_flNextGunshipCrashFind < gpGlobals->curtime )
			{
				FindNearestGunshipCrash();
			}

			if ( m_hCrashTarget )
			{
				MoveHead();

				UpdateDesiredPosition();

				// If we're over it, destruct
				Vector vecToTarget = (GetDesiredPosition() - GetAbsOrigin());
				if ( vecToTarget.LengthSqr() < (384 * 384) )
				{
					BeginDestruct();
					m_OnCrashed.FireOutput( this, this );
					m_hCrashTarget->GunshipCrashedOnTarget();
					return;
				}
			}
		}
	}

	BaseClass::PrescheduleThink();

#ifdef JACOBS_GUNSHIP	
	SetPoseParameter( m_posePitch, random->RandomFloat( GUNSHIP_HEAD_MAX_LEFT, GUNSHIP_HEAD_MAX_RIGHT ) );
	SetPoseParameter( m_poseYaw, random->RandomFloat( GUNSHIP_HEAD_MAX_UP, GUNSHIP_HEAD_MAX_DOWN ) );
#endif

}

//------------------------------------------------------------------------------
// Purpose :	If the enemy is in front of the gun, load up a burst. 
//				Actual gunfire is handled in PrescheduleThink
// Input   : 
// Output  : 
//------------------------------------------------------------------------------
bool CNPC_CombineGunship::FireGun( void )
{
	if ( m_lifeState != LIFE_ALIVE )
		return false;

	if ( m_bIsGroundAttacking )
		return false;

	if ( GetEnemy() && !m_bIsFiring && gpGlobals->curtime > m_flTimeNextAttack )
	{
		// We want to decelerate to attack
		if (m_flGoalSpeed > GetMaxSpeedFiring() )
		{
			m_flGoalSpeed = GetMaxSpeedFiring();
		}

		bool bTargetingMissile = IsTargettingMissile();
		if ( !bTargetingMissile && !m_bPreFire )
		{
			m_bPreFire = true;
			m_flTimeNextAttack = gpGlobals->curtime + 0.5f;
			
			EmitSound( "NPC_CombineGunship.CannonStartSound" );
			return false;
		}

		//TODO: Emit the danger noise and wait until it's finished

		// Don't fire at an occluded enemy unless blindfire is on.
		if ( HasCondition( COND_ENEMY_OCCLUDED ) && ( m_fBlindfire == false ) )
			return false;

		// Don't shoot if the enemy is too close
		if ( !bTargetingMissile && GroundDistToPosition( GetEnemy()->GetAbsOrigin() ) < GUNSHIP_STITCH_MIN )
			return false;

		Vector vecAimDir, vecToEnemy;
		Vector vecMuzzle, vecEnemyTarget;

		GetAttachment( "muzzle", vecMuzzle, &vecAimDir, NULL, NULL );
		vecEnemyTarget = GetEnemyTarget();

		// Aim with the muzzle's attachment point.
		VectorSubtract( vecEnemyTarget, vecMuzzle, vecToEnemy );

		VectorNormalize( vecToEnemy );
		VectorNormalize( vecAimDir );

		if ( DotProduct( vecToEnemy, vecAimDir ) > 0.9 )
		{
			StartCannonBurst( sk_gunship_burst_size.GetInt() );
			return true;
		}

		return false;
	}

	return false;
}

//------------------------------------------------------------------------------
// Purpose: Fire a round from the cannon
// Notes:	Only call this if you have an enemy.
//------------------------------------------------------------------------------
void CNPC_CombineGunship::FireCannonRound( void )
{
	Vector vecPenetrate;
	trace_t tr;

	Vector vecToEnemy, vecEnemyTarget;
	Vector vecMuzzle;
	Vector vecAimDir;

	GetAttachment( "muzzle", vecMuzzle, &vecAimDir );
	vecEnemyTarget = GetEnemyTarget();
	
	// Aim with the muzzle's attachment point.
	VectorSubtract( vecEnemyTarget, vecMuzzle, vecToEnemy );
	VectorNormalize( vecToEnemy );

	// If the gun is wildly off target, stop firing!
	// FIXME  - this should use a vector pointing 
	// to the enemy's location PLUS the stitching 
	// error! (sjb) !!!BUGBUG

	if ( g_debug_gunship.GetInt() == GUNSHIP_DEBUG_STITCHING )
	{
		QAngle vecAimAngle;
		Vector	vForward, vRight, vUp;
		GetAttachment( "muzzle", vecMuzzle, &vForward, &vRight, &vUp );
		AngleVectors( vecAimAngle, &vForward, &vRight, &vUp );
		NDebugOverlay::Line( vecMuzzle, vecEnemyTarget, 255, 255, 0, true, 1.0f );

		NDebugOverlay::Line( vecMuzzle, vecMuzzle + ( vForward * 64.0f ), 255, 0, 0, true, 1.0f );
		NDebugOverlay::Line( vecMuzzle, vecMuzzle + ( vRight * 32.0f ), 0, 255, 0, true, 1.0f );
		NDebugOverlay::Line( vecMuzzle, vecMuzzle + ( vUp * 32.0f ), 0, 0, 255, true, 1.0f );
	}

	// Robin: Check the dotproduct to the enemy, NOT to the offsetted firing angle
	// Fixes problems firing at close enemies, where the enemy is valid but
	// the offset firing stitch isn't.
	Vector vecDotCheck = vecToEnemy;
	if ( GetEnemy() )
	{
		VectorSubtract( GetEnemy()->GetAbsOrigin(), vecMuzzle, vecDotCheck );
		VectorNormalize( vecDotCheck );
	}

	if ( DotProduct( vecDotCheck, vecAimDir ) < 0.8f )
	{
		StopCannonBurst();
		return;
	}

	DoMuzzleFlash();

	m_OnFireCannon.FireOutput( this, this, 0 );

	m_flTimeNextAttack = gpGlobals->curtime + 0.05f;

	float flPrevHealth = 0;
	if ( GetEnemy() )
	{
		flPrevHealth = GetEnemy()->GetHealth();
	}

	// Make sure we hit missiles
	if ( IsTargettingMissile() )
	{
		// Fire a fake shot
		FireBullets( 1, vecMuzzle, vecToEnemy, VECTOR_CONE_5DEGREES, 8192, m_iAmmoType, 1 );

		CBaseEntity *pMissile = GetEnemy();

		Vector	missileDir, threatDir;

		AngleVectors( pMissile->GetAbsAngles(), &missileDir );

		threatDir = ( WorldSpaceCenter() - pMissile->GetAbsOrigin() );
		float	threatDist = VectorNormalize( threatDir );

		// Check that the target is within some threshold
		if ( ( DotProduct( threatDir, missileDir ) > 0.95f ) && ( threatDist < 1024.0f ) )
		{
			if ( random->RandomInt( 0, 1 ) == 0 )
			{
				CTakeDamageInfo info( this, this, 200, DMG_MISSILEDEFENSE );
				CalculateBulletDamageForce( &info, m_iAmmoType, -threatDir, WorldSpaceCenter() );
				GetEnemy()->TakeDamage( info );
			}
		}
		else
		{
			//FIXME: Some other metric
		}
	}
	else
	{
		m_iBurstSize--;

		// Fire directly at the target
		FireBulletsInfo_t info( 1, vecMuzzle, vecToEnemy, vec3_origin, MAX_COORD_RANGE, m_iAmmoType );
		info.m_iTracerFreq = 1;
		CAmmoDef *pAmmoDef = GetAmmoDef();
		info.m_iPlayerDamage = pAmmoDef->PlrDamage( m_iAmmoType );

		// If we've already hit the player, do 0 damage. This ensures we don't hit the
		// player multiple times during a single burst.
		if ( m_iBurstHits >= GUNSHIP_MAX_HITS_PER_BURST )
		{
			info.m_iPlayerDamage = 1;
		}

		FireBullets( info );

		if ( GetEnemy() && flPrevHealth != GetEnemy()->GetHealth() )
		{
			m_iBurstHits++;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::DoMuzzleFlash( void )
{
	BaseClass::DoMuzzleFlash();
	
	CEffectData data;

	data.m_nAttachmentIndex = LookupAttachment( "muzzle" );
	data.m_nEntIndex = entindex();
	DispatchEffect( "GunshipMuzzleFlash", data );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_CombineGunship::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
{
	bool fReturn = BaseClass::FVisible( pEntity, traceMask, ppBlocker );

	if( m_fOmniscient )
	{
		if( !fReturn )
		{
			// Set this condition so that we can check it later and know that the 
			// enemy truly is occluded, but the gunship regards it as visible due 
			// to omniscience.
			SetCondition( COND_ENEMY_OCCLUDED );
		}
		else
		{
			ClearCondition( COND_ENEMY_OCCLUDED );
		}

		return true;
	}

	if( fReturn )
	{
		ClearCondition( COND_ENEMY_OCCLUDED );
	}
	else
	{
		SetCondition( COND_ENEMY_OCCLUDED );
	}

	return fReturn;
}


//-----------------------------------------------------------------------------
// Purpose: Change the depth that gunship bullets can penetrate through solids
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::InputSetPenetrationDepth( inputdata_t &inputdata )
{
	m_flPenetrationDepth = inputdata.value.Float();
}


//-----------------------------------------------------------------------------
// Purpose: Allow the gunship to sense its enemy's location even when enemy
//			is hidden from sight.
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::InputOmniscientOn( inputdata_t &inputdata )
{
	m_fOmniscient = true;
}


//-----------------------------------------------------------------------------
// Purpose: Returns the gunship to its default requirement that it see the 
//			enemy to know its current position
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::InputOmniscientOff( inputdata_t &inputdata )
{
	m_fOmniscient = false;
}


//-----------------------------------------------------------------------------
// Purpose: Allows the gunship to fire at an unseen enemy. The gunship is relying
//			on hitting the target with bullets that will punch through the 
//			cover that the enemy is hiding behind. (Such as the Depot lighthouse)
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::InputBlindfireOn( inputdata_t &inputdata )
{
	m_fBlindfire = true;
}


//-----------------------------------------------------------------------------
// Purpose: Returns the gunship to default rules for attacking the enemy. The
//			enemy must be seen to be fired at.
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::InputBlindfireOff( inputdata_t &inputdata )
{
	m_fBlindfire = false;
}

//-----------------------------------------------------------------------------
// Purpose: Set the gunship's paddles flailing!
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::Event_Killed( const CTakeDamageInfo &info )
{
	m_takedamage = DAMAGE_NO;

	StopCannonBurst();

	// Replace the rotor sound with broken engine sound.
	CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
	controller.SoundDestroy( m_pRotorSound );

	// BUGBUG: Isn't this sound just going to get stomped when the base class calls StopLoopingSounds() ??
	CPASAttenuationFilter filter2( this );
	m_pRotorSound = controller.SoundCreate( filter2, entindex(), "NPC_CombineGunship.DyingSound" );
	controller.Play( m_pRotorSound, 1.0, 100 );

	m_OnDeath.FireOutput( info.GetAttacker(), this );
	SendOnKilledGameEvent( info );

	BeginCrash();

	// we deliberately do not call BaseClass::EventKilled
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::BeginCrash( void )
{
	m_lifeState = LIFE_DYING;
	StopGroundAttack( false );

	// Increase our smoke trail
	CreateSmokeTrail();
	if ( m_pSmokeTrail )
	{
		m_pSmokeTrail->SetLifetime( -1 );
		m_pSmokeTrail->m_StartSize = 64;
		m_pSmokeTrail->m_EndSize = 128;
		m_pSmokeTrail->m_Opacity = 0.5f;
	}

	if ( !FindNearestGunshipCrash() )
	{
		// We couldn't find a crash target, so just die right here.
		BeginDestruct();
		return;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CNPC_CombineGunship::FindNearestGunshipCrash( void )
{
	// Find the nearest crash point. If we find one, we'll try to fly to it and die.
	// If we can't find one, we'll die right here.
	bool bFoundAnyCrashTargets = false;
 	float flNearest = MAX_TRACE_LENGTH * MAX_TRACE_LENGTH;
	CTargetGunshipCrash *pNearest = NULL;
	CBaseEntity *pEnt = NULL;
	while( (pEnt = gEntList.FindEntityByClassname(pEnt, "info_target_gunshipcrash")) != NULL )
	{
		CTargetGunshipCrash *pCrashTarget = assert_cast<CTargetGunshipCrash*>(pEnt);
		if ( pCrashTarget->IsDisabled() )
			continue;

		bFoundAnyCrashTargets = true;

		float flDist = ( pEnt->WorldSpaceCenter() - WorldSpaceCenter() ).LengthSqr();
		if( flDist < flNearest )
		{
			trace_t tr;
			UTIL_TraceLine( WorldSpaceCenter(), pEnt->WorldSpaceCenter(), MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr );
			if( tr.fraction == 1.0 )
			{
				pNearest = pCrashTarget;
				flNearest = flDist;
			}
			else if ( g_debug_gunship.GetInt() )
			{
				NDebugOverlay::Line( WorldSpaceCenter(), tr.endpos, 255,0,0, true, 99);
			}
		}
	}

	if ( !pNearest )
	{
		// If we found a gunship crash, but none near enough, claim we did find one, so that we
		// don't blow up yet. This will give us 3 seconds to attempt to find one before dying.
		if ( !m_hCrashTarget && bFoundAnyCrashTargets )
		{
			m_flNextGunshipCrashFind = gpGlobals->curtime + 0.5;
			m_flEndDestructTime = gpGlobals->curtime + 3.0;
			return true;
		}

		return false;
	}

	// Fly to the crash point and destruct there
  	m_hCrashTarget = pNearest;
	m_flNextGunshipCrashFind = gpGlobals->curtime + 0.5;
	m_flEndDestructTime = 0;

	if ( g_debug_gunship.GetInt() )
	{
		NDebugOverlay::Line(GetAbsOrigin(), m_hCrashTarget->GetAbsOrigin(), 0,255,0, true, 0.5);
		NDebugOverlay::Box( m_hCrashTarget->GetAbsOrigin(), -Vector(200,200,200), Vector(200,200,200), 0,255,0, 128, 0.5 );
	}

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: I'm now ready to die. Create my ragdoll & hide myself.
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::BeginDestruct( void )
{
	m_flEndDestructTime = gpGlobals->curtime + 3.0;

	// Clamp velocity
	if( hl2_episodic.GetBool() && GetAbsVelocity().Length() > 700.0f )
	{
		Vector vecVelocity = GetAbsVelocity(); 
		VectorNormalize( vecVelocity );
		SetAbsVelocity( vecVelocity * 700.0f );
	}

	CTakeDamageInfo info;
	info.SetDamage( 40000 );
	CalculateExplosiveDamageForce( &info, GetAbsVelocity(), GetAbsOrigin() );

	// Don't create a ragdoll if we're going to explode into gibs
	if ( !m_hCrashTarget )
		return;

	// Switch to damaged skin
	m_nSkin = 1;

	if ( HasSpawnFlags( SF_GUNSHIP_USE_CHOPPER_MODEL ) )
	{
		Chopper_BecomeChunks( this );
		SetThink( &CNPC_CombineGunship::SUB_Remove );
		SetNextThink( gpGlobals->curtime + 0.1f );
		AddEffects( EF_NODRAW );
		return;
	}

	// Create the ragdoll
	m_hRagdoll = CreateServerRagdoll( this, 0, info, COLLISION_GROUP_NONE );
	if ( !m_hRagdoll )
	{
		// Failed, just explode
		SelfDestruct();
		return;
	}

	m_hRagdoll->SetName( AllocPooledString( UTIL_VarArgs("%s_ragdoll", STRING(GetEntityName())) ) );

	// Tell the smoke trail to follow the ragdoll
	CreateSmokeTrail();
	if ( m_pSmokeTrail )
	{
		// Force the smoke trail to stay on, and tell it to follow the ragdoll
		m_pSmokeTrail->SetLifetime( -1 );
		m_pSmokeTrail->FollowEntity( m_hRagdoll );
		
		m_pSmokeTrail->m_StartSize = 64;
		m_pSmokeTrail->m_EndSize = 128;
		m_pSmokeTrail->m_Opacity = 0.5f;
	}

	/* 
	// ROBIN: Disabled this for now.
	//
	// Create the crashing controller and attach it to the ragdoll physics objects
	m_pCrashingController = physenv->CreateMotionController( &m_crashCallback );
	IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
	int count = m_hRagdoll->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
	for ( int i = 0; i < count; i++ )
	{
		m_pCrashingController->AttachObject( pList[i], false );
	}
	*/

	// Hide myself, because the ragdoll's now taken my place
	AddEffects( EF_NODRAW );
	AddSolidFlags( FSOLID_NOT_SOLID );
}

//-----------------------------------------------------------------------------
// Purpose: Create a smoke trail
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::CreateSmokeTrail( void )
{
	if ( m_pSmokeTrail )
		return;

	m_pSmokeTrail = SmokeTrail::CreateSmokeTrail();
	
	if ( m_pSmokeTrail )
	{
		m_pSmokeTrail->m_SpawnRate			= 48;
		m_pSmokeTrail->m_ParticleLifetime	= 2.5f;
		
		m_pSmokeTrail->m_StartColor.Init( 0.25f, 0.25f, 0.25f );
		m_pSmokeTrail->m_EndColor.Init( 0.0, 0.0, 0.0 );
		
		m_pSmokeTrail->m_StartSize		= 24;
		m_pSmokeTrail->m_EndSize		= 128;
		m_pSmokeTrail->m_SpawnRadius	= 4;
		m_pSmokeTrail->m_MinSpeed		= 8;
		m_pSmokeTrail->m_MaxSpeed		= 64;
		m_pSmokeTrail->m_Opacity		= 0.2f;

		m_pSmokeTrail->SetLifetime( -1 );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::ApplyGeneralDrag( void )
{
	Vector vecNewVelocity = GetAbsVelocity();
	
	// See if we need to stop more quickly
	if ( m_bIsGroundAttacking )
	{
		vecNewVelocity *= 0.95f;
	}
	else
	{
		vecNewVelocity *= 0.995;
	}

	SetAbsVelocity( vecNewVelocity );
}

//-----------------------------------------------------------------------------
// Purpose:	
//-----------------------------------------------------------------------------

void CNPC_CombineGunship::Flight( void )
{
	if( GetFlags() & FL_ONGROUND )
	{
		//This would be really bad.
		SetGroundEntity( NULL );
	}

	if ( g_debug_gunship.GetInt() == GUNSHIP_DEBUG_PATH )
	{
		NDebugOverlay::Line(GetLocalOrigin(), GetDesiredPosition(), 0,0,255, true, 0.1);
	}

	// calc desired acceleration
	float dt = 1.0f;

	Vector	accel;
	float	accelRate = GUNSHIP_ACCEL_RATE;
	float	maxSpeed = GetMaxSpeed(); 

	if ( m_lifeState == LIFE_DYING && m_hCrashTarget != NULL )
	{
		// Gunship can fly faster to the place where it's supposed to crash, but
		// maintain normal speeds if we haven't found a place to crash.
		accelRate *= 2.0;
		maxSpeed *= 4.0;
	}

	float flCurrentSpeed = GetAbsVelocity().Length();
	float flDist = min( flCurrentSpeed + accelRate, maxSpeed );

	Vector deltaPos;
	if ( m_lifeState == LIFE_DYING || m_hGroundAttackTarget )
	{
		// Move directly to the target point
		deltaPos = GetDesiredPosition();
	}
	else
	{
		ComputeActualTargetPosition( flDist, dt, 0.0f, &deltaPos );
	}
	deltaPos -= GetAbsOrigin();

	// calc goal linear accel to hit deltaPos in dt time.
	accel.x = 2.0 * (deltaPos.x - GetAbsVelocity().x * dt) / (dt * dt);
	accel.y = 2.0 * (deltaPos.y - GetAbsVelocity().y * dt) / (dt * dt);
	accel.z = 2.0 * (deltaPos.z - GetAbsVelocity().z * dt + 0.5 * 384 * dt * dt) / (dt * dt);
	
	float flDistFromPath = 0.0f;
	Vector vecPoint, vecDelta;
	if ( m_lifeState != LIFE_DYING && IsOnPathTrack() )
	{
		// Also, add in a little force to get us closer to our current line segment if we can
		ClosestPointToCurrentPath( &vecPoint );
		VectorSubtract( vecPoint, GetAbsOrigin(), vecDelta );
 		flDistFromPath = VectorNormalize( vecDelta );
		if ( flDistFromPath > GUNSHIP_OUTER_NAV_DIST )
		{
			// Strongly constrain to an n unit pipe around the current path
			// by damping out all impulse forces that would push us further from the pipe
			float flAmount = (flDistFromPath - GUNSHIP_OUTER_NAV_DIST) / 200.0f;
			flAmount = clamp( flAmount, 0, 1 );
			VectorMA( accel, flAmount * 200.0f, vecDelta, accel );
		}
	}

	Vector vecAvoidForce;
	CAvoidSphere::ComputeAvoidanceForces( this, 350.0f, 2.0f, &vecAvoidForce );
	accel += vecAvoidForce;
	CAvoidBox::ComputeAvoidanceForces( this, 350.0f, 2.0f, &vecAvoidForce );
	accel += vecAvoidForce;
	
	if ( m_lifeState != LIFE_DYING || m_hCrashTarget == NULL )
	{
		// don't fall faster than 0.2G or climb faster than 2G
		accel.z = clamp( accel.z, 384 * 0.2, 384 * 2.0 );
	}

	Vector forward, right, up;
	GetVectors( &forward, &right, &up );

	Vector goalUp = accel;
	VectorNormalize( goalUp );

	// calc goal orientation to hit linear accel forces
	float goalPitch = RAD2DEG( asin( DotProduct( forward, goalUp ) ) );
	float goalYaw = UTIL_VecToYaw( m_vecDesiredFaceDir );
	float goalRoll = RAD2DEG( asin( DotProduct( right, goalUp ) ) );

	// clamp goal orientations
	goalPitch = clamp( goalPitch, -45, 60 );
	goalRoll = clamp( goalRoll, -45, 45 );

	// calc angular accel needed to hit goal pitch in dt time.
	dt = 0.6;
	QAngle goalAngAccel;
	goalAngAccel.x = 2.0 * (AngleDiff( goalPitch, AngleNormalize( GetLocalAngles().x ) ) - GetLocalAngularVelocity().x * dt) / (dt * dt);
	goalAngAccel.y = 2.0 * (AngleDiff( goalYaw, AngleNormalize( GetLocalAngles().y ) ) - GetLocalAngularVelocity().y * dt) / (dt * dt);
	goalAngAccel.z = 2.0 * (AngleDiff( goalRoll, AngleNormalize( GetLocalAngles().z ) ) - GetLocalAngularVelocity().z * dt) / (dt * dt);

	goalAngAccel.x = clamp( goalAngAccel.x, -300, 300 );
	//goalAngAccel.y = clamp( goalAngAccel.y, -60, 60 );
	goalAngAccel.y = clamp( goalAngAccel.y, -120, 120 );
	goalAngAccel.z = clamp( goalAngAccel.z, -300, 300 );

	// limit angular accel changes to similate mechanical response times
	dt = 0.1;
	QAngle angAccelAccel;
	angAccelAccel.x = (goalAngAccel.x - m_vecAngAcceleration.x) / dt;
	angAccelAccel.y = (goalAngAccel.y - m_vecAngAcceleration.y) / dt;
	angAccelAccel.z = (goalAngAccel.z - m_vecAngAcceleration.z) / dt;

	angAccelAccel.x = clamp( angAccelAccel.x, -1000, 1000 );
	angAccelAccel.y = clamp( angAccelAccel.y, -1000, 1000 );
	angAccelAccel.z = clamp( angAccelAccel.z, -1000, 1000 );

	m_vecAngAcceleration += angAccelAccel * 0.1;

	// DevMsg( "pitch %6.1f (%6.1f:%6.1f)  ", goalPitch, GetLocalAngles().x, m_vecAngVelocity.x );
	// DevMsg( "roll %6.1f (%6.1f:%6.1f) : ", goalRoll, GetLocalAngles().z, m_vecAngVelocity.z );
	// DevMsg( "%6.1f %6.1f %6.1f  :  ", goalAngAccel.x, goalAngAccel.y, goalAngAccel.z );
	// DevMsg( "%6.0f %6.0f %6.0f\n", angAccelAccel.x, angAccelAccel.y, angAccelAccel.z );

	ApplySidewaysDrag( right );
	ApplyGeneralDrag();
	
	QAngle angVel = GetLocalAngularVelocity();
	angVel += m_vecAngAcceleration * 0.1;

	//angVel.y = clamp( angVel.y, -60, 60 );
	//angVel.y = clamp( angVel.y, -120, 120 );
	angVel.y = clamp( angVel.y, -120, 120 );

	SetLocalAngularVelocity( angVel );

	m_flForce = m_flForce * 0.8 + (accel.z + fabs( accel.x ) * 0.1 + fabs( accel.y ) * 0.1) * 0.1 * 0.2;

	Vector vecImpulse = m_flForce * up;
	
	if ( !m_hCrashTarget && m_lifeState == LIFE_DYING && !hl2_episodic.GetBool() )
	{
		// Force gunship to the ground if it doesn't have a specific place to crash.
		// EXCEPT In episodic, where forcing it to the ground means it crashes where the player can't see (attic showdown) (sjb)
		vecImpulse.z = -10;
	}
	else
	{
		vecImpulse.z -= 38.4;  // 32ft/sec	
	}
	
	// Find our current velocity
	Vector vecVelDir = GetAbsVelocity();
	VectorNormalize( vecVelDir );

	if ( flDistFromPath > GUNSHIP_INNER_NAV_DIST )
	{
		// Strongly constrain to an n unit pipe around the current path
		// by damping out all impulse forces that would push us further from the pipe
		float flDot = DotProduct( vecImpulse, vecDelta );
		if ( flDot < 0.0f )
		{
			VectorMA( vecImpulse, -flDot * 0.1f, vecDelta, vecImpulse );
		}

		// Also apply an extra impulse to compensate for the current velocity
		flDot = DotProduct( vecVelDir, vecDelta );
		if ( flDot < 0.0f )
		{
			VectorMA( vecImpulse, -flDot * 0.1f, vecDelta, vecImpulse );
		}
	}
	
	// Find our acceleration direction
	Vector	vecAccelDir = vecImpulse;
	VectorNormalize( vecAccelDir );

	// Level out our plane of movement
	vecAccelDir.z	= 0.0f;
	vecVelDir.z		= 0.0f;
	forward.z		= 0.0f;
	right.z			= 0.0f;

	// Find out how "fast" we're moving in relation to facing and acceleration
	float speed = m_flForce * DotProduct( vecVelDir, vecAccelDir );// * DotProduct( forward, vecVelDir );

	// Apply the acceleration blend to the fins
	float finAccelBlend = SimpleSplineRemapVal( speed, -60, 60, -1, 1 );
	float curFinAccel = GetPoseParameter( m_poseFin_Accel );
	
	curFinAccel = UTIL_Approach( finAccelBlend, curFinAccel, 0.5f );
	SetPoseParameter( m_poseFin_Accel, curFinAccel );

	speed = m_flForce * DotProduct( vecVelDir, right );

	// Apply the spin sway to the fins
	float finSwayBlend = SimpleSplineRemapVal( speed, -60, 60, -1, 1 );
	float curFinSway = GetPoseParameter( m_poseFin_Sway );

	curFinSway = UTIL_Approach( finSwayBlend, curFinSway, 0.5f );
	SetPoseParameter( m_poseFin_Sway, curFinSway );

	if ( g_debug_gunship.GetInt() == GUNSHIP_DEBUG_PATH )
	{
		NDebugOverlay::Line(GetLocalOrigin(), GetLocalOrigin() + vecImpulse, 255,0,0, true, 0.1);
	}

	// Add in our velocity pulse for this frame
	ApplyAbsVelocityImpulse( vecImpulse );
}

//------------------------------------------------------------------------------
// Updates the facing direction
//------------------------------------------------------------------------------
void CNPC_CombineGunship::UpdateFacingDirection( void )
{
	if ( GetEnemy() )
	{
		if ( !IsCrashing() && m_flLastSeen + 5 > gpGlobals->curtime )
		{
			// If we've seen the target recently, face the target.
			//Msg( "Facing Target \n" );
			m_vecDesiredFaceDir = m_vecTargetPosition - GetAbsOrigin();
		}
		else
		{
			// Remain facing the way you were facing...
		}
	}
	else
	{
		// Face our desired position.
		if ( GetDesiredPosition().DistToSqr( GetAbsOrigin() ) > 1 )
		{
			m_vecDesiredFaceDir = GetDesiredPosition() - GetAbsOrigin();
		}
		else
		{
			GetVectors( &m_vecDesiredFaceDir, NULL, NULL );
		}
	}
	VectorNormalize( m_vecDesiredFaceDir ); 
}

//------------------------------------------------------------------------------
// Purpose : Fire up the Gunships 'second' rotor sound. The Search sound.
// Input   :
// Output  :
//------------------------------------------------------------------------------
void CNPC_CombineGunship::InitializeRotorSound( void )
{
	CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
	
	CPASAttenuationFilter filter( this );

	m_pCannonSound		= controller.SoundCreate( filter, entindex(), "NPC_CombineGunship.CannonSound" );
	m_pRotorSound		= controller.SoundCreate( filter, entindex(), "NPC_CombineGunship.RotorSound" );
	m_pAirExhaustSound	= controller.SoundCreate( filter, entindex(), "NPC_CombineGunship.ExhaustSound" );
	m_pAirBlastSound	= controller.SoundCreate( filter, entindex(), "NPC_CombineGunship.RotorBlastSound" );
	
	controller.Play( m_pCannonSound, 0.0, 100 );
	controller.Play( m_pAirExhaustSound, 0.0, 100 );
	controller.Play( m_pAirBlastSound, 0.0, 100 );

	BaseClass::InitializeRotorSound();
}


//------------------------------------------------------------------------------
// Purpose :
// Input   :
// Output  :
//------------------------------------------------------------------------------
void CNPC_CombineGunship::UpdateRotorSoundPitch( int iPitch )
{
	CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();

	// Apply the pitch to both sounds. 
	controller.SoundChangePitch( m_pAirExhaustSound, iPitch, 0.1 );

	// FIXME: Doesn't work in multiplayer
	CBaseEntity *pPlayer = UTIL_PlayerByIndex(1);
	if (pPlayer)
	{
		Vector pos;
		Vector up;
		GetAttachment( "rotor", pos, NULL, NULL, &up );

		float flDistance = (pPlayer->WorldSpaceCenter() - pos).Length2DSqr();

		// Fade in exhaust when we're far from the player
		float flVolume = clamp( RemapVal( flDistance, (900*900), (1800*1800), 1, 0 ), 0, 1 );
		controller.SoundChangeVolume( m_pAirExhaustSound, flVolume * GetRotorVolume(), 0.1 );

		// Fade in the blast when it's close to the player (in 2D)
		flVolume = clamp( RemapVal( flDistance, (600*600), (700*700), 1, 0 ), 0, 1 );
		controller.SoundChangeVolume( m_pAirBlastSound, flVolume * GetRotorVolume(), 0.1 );
	}

	BaseClass::UpdateRotorSoundPitch( iPitch );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  :
// Output : 
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::ApplySidewaysDrag( const Vector &vecRight )
{
	Vector vecVelocity = GetAbsVelocity();
	if( m_lifeState == LIFE_ALIVE )
	{
		vecVelocity.x *= (1.0 - fabs( vecRight.x ) * 0.04);
		vecVelocity.y *= (1.0 - fabs( vecRight.y ) * 0.04);
		vecVelocity.z *= (1.0 - fabs( vecRight.z ) * 0.04);
	}
	else
	{
		vecVelocity.x *= (1.0 - fabs( vecRight.x ) * 0.03);
		vecVelocity.y *= (1.0 - fabs( vecRight.y ) * 0.03);
		vecVelocity.z *= (1.0 - fabs( vecRight.z ) * 0.09);
	}
	SetAbsVelocity( vecVelocity );
}

//------------------------------------------------------------------------------
// Purpose: Explode the gunship.
//------------------------------------------------------------------------------
void CNPC_CombineGunship::SelfDestruct( void )
{
	SetThink( NULL );
	m_lifeState = LIFE_DEAD;
	
	StopLoopingSounds();
	StopCannonBurst();

	Vector vecVelocity = GetAbsVelocity();
	vecVelocity.z = 0.0; // stop falling.
	SetAbsVelocity( vecVelocity );

	CBaseEntity *pBreakEnt = this;

	// If we've ragdolled, play the explosions on the ragdoll instead
	Vector vecOrigin;
	if ( m_hRagdoll )
	{
		m_hRagdoll->EmitSound( "NPC_CombineGunship.Explode" );
		vecOrigin = m_hRagdoll->GetAbsOrigin();
		pBreakEnt = m_hRagdoll;
	}
	else
	{
		EmitSound( "NPC_CombineGunship.Explode" );
		vecOrigin = GetAbsOrigin();
	}

	// Create some explosions on the gunship body
	Vector vecDelta;
	for( int i = 0 ; i < 6 ; i++ )
	{
		vecDelta = RandomVector( -200,200 );
		ExplosionCreate( vecOrigin + vecDelta, QAngle( -90, 0, 0 ), this, 10, 10, false );
	}

	AR2Explosion *pExplosion = AR2Explosion::CreateAR2Explosion( vecOrigin );
	if ( pExplosion )
	{
		pExplosion->SetLifetime( 10 );
	}

	// If we don't have a crash target, explode into chunks
	if ( !m_hCrashTarget )
	{
		Vector angVelocity;
		QAngleToAngularImpulse( pBreakEnt->GetLocalAngularVelocity(), angVelocity );
		PropBreakableCreateAll( pBreakEnt->GetModelIndex(), pBreakEnt->VPhysicsGetObject(), pBreakEnt->GetAbsOrigin(), pBreakEnt->GetAbsAngles(), pBreakEnt->GetAbsVelocity(), angVelocity, 1.0, 800, COLLISION_GROUP_NPC, pBreakEnt );

		// Throw out some small chunks too
		CPVSFilter filter( GetAbsOrigin() );
	 	for ( int i = 0; i < 20; i++ )
		{
			Vector gibVelocity = RandomVector(-100,100) * 10;
			int iModelIndex = modelinfo->GetModelIndex( g_PropDataSystem.GetRandomChunkModel( "MetalChunks" ) );	
			te->BreakModel( filter, 0.0, GetAbsOrigin(), vec3_angle, Vector(40,40,40), gibVelocity, iModelIndex, 400, 1, 2.5, BREAK_METAL );
		}

		if ( m_hRagdoll )
		{
			UTIL_Remove( m_hRagdoll );
		}
	}
	else
	{
		if ( m_pSmokeTrail )
		{
			// If we have a ragdoll, let it smoke for a few more seconds
			if ( m_hRagdoll )
			{
				m_pSmokeTrail->SetLifetime(3.0f);
			}
			else
			{
				m_pSmokeTrail->SetLifetime(0.1f);
			}
			m_pSmokeTrail = NULL;
		}
	}

	UTIL_Remove( this );

	// Record this so a nearby citizen can respond.
	if ( GetCitizenResponse() )
	{
		GetCitizenResponse()->AddResponseTrigger( CR_PLAYER_KILLED_GUNSHIP );
	}

#ifdef HL2_EPISODIC
	NPCEventResponse()->TriggerEvent( "TLK_CITIZEN_RESPONSE_KILLED_GUNSHIP", false, false );
#endif
}


//------------------------------------------------------------------------------
// Purpose : Explode the gunship.
// Input   :
// Output  :
//------------------------------------------------------------------------------
void CNPC_CombineGunship::InputSelfDestruct( inputdata_t &inputdata )
{
	BeginCrash();
}

//------------------------------------------------------------------------------
// Purpose : Shrink the gunship's bbox so that it fits in docking bays
// Input   :
// Output  :
//------------------------------------------------------------------------------
void CNPC_CombineGunship::InputSetDockingBBox( inputdata_t &inputdata )
{
	Vector vecSize( 32, 32, 32 );

	UTIL_SetSize( this, vecSize * -1, vecSize );
}

//------------------------------------------------------------------------------
// Purpose : Set the gunship BBox to normal size
// Input   :
// Output  :
//------------------------------------------------------------------------------
void CNPC_CombineGunship::InputSetNormalBBox( inputdata_t &inputdata )
{
	Vector vecBBMin, vecBBMax;

	ExtractBbox( SelectHeaviestSequence( ACT_GUNSHIP_PATROL ), vecBBMin, vecBBMax ); 

	// Trim the bounding box a bit. It's huge.
#define GUNSHIP_TRIM_BOX 38
	vecBBMin.x += GUNSHIP_TRIM_BOX;
	vecBBMax.x -= GUNSHIP_TRIM_BOX;
	vecBBMin.y += GUNSHIP_TRIM_BOX;
	vecBBMax.y -= GUNSHIP_TRIM_BOX;

	UTIL_SetSize( this, vecBBMin, vecBBMax );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &inputdata - 
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::InputEnableGroundAttack( inputdata_t &inputdata )
{
	m_bCanGroundAttack = true;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &inputdata - 
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::InputDisableGroundAttack( inputdata_t &inputdata )
{
	m_bCanGroundAttack = false;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &inputdata - 
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::InputDoGroundAttack( inputdata_t &inputdata )
{
	// Was a target node specified?
	CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, inputdata.value.StringID(), NULL, inputdata.pActivator, inputdata.pCaller );
	if ( pEntity )
	{
		// Mapmaker wants us to ground attack a specific target
		m_hGroundAttackTarget = pEntity;
	}
	else
	{
		StartGroundAttack();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &vGunPosition - 
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::UpdateEnemyTarget( void )
{
	Vector	vGunPosition;

	GetAttachment( "muzzle", vGunPosition );

	// Follow mode
	Vector	enemyPos;
	bool	bTargettingPlayer;
	
	if ( GetEnemy() != NULL )
	{
		CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer();
		if ( pCCEnemy != NULL && pCCEnemy->IsInAVehicle() )
		{
			// Update against a driving target
			enemyPos = GetEnemy()->WorldSpaceCenter();
		}
		else 
		{
			enemyPos = GetEnemy()->EyePosition();
		}
		bTargettingPlayer = GetEnemy()->IsPlayer();
	}
	else
	{
		enemyPos = m_vecAttackPosition;
		bTargettingPlayer = false;
	}

	// Direction towards the enemy
	Vector	targetDir = enemyPos - m_vecAttackPosition;
	VectorNormalize( targetDir );

	// Direction from the gunship to the enemy
	Vector	enemyDir = enemyPos - vGunPosition;
	VectorNormalize( enemyDir );

	float	lastSpeed = VectorNormalize( m_vecAttackVelocity );
	QAngle	chaseAngles, lastChaseAngles;

	VectorAngles( targetDir, chaseAngles );
	VectorAngles( m_vecAttackVelocity, lastChaseAngles );

	// Debug info
	if ( g_debug_gunship.GetInt() == GUNSHIP_DEBUG_STITCHING )
	{
		// Final position
		NDebugOverlay::Cross3D( m_vecAttackPosition, -Vector(2,2,2), Vector(2,2,2), 0, 0, 255, true, 4.0f );
	}

	float yawDiff = UTIL_AngleDiff( lastChaseAngles[YAW], chaseAngles[YAW] );

	int	maxYaw;
	if ( bTargettingPlayer )
	{
		maxYaw = 6;
	}
	else
	{
		maxYaw = 30;
	}	

	yawDiff = clamp( yawDiff, -maxYaw, maxYaw );

	chaseAngles[PITCH]	= 0.0f;
	chaseAngles[ROLL]	= 0.0f;

	bool bMaxHits = ( m_iBurstHits >= GUNSHIP_MAX_HITS_PER_BURST || (GetEnemy() && !GetEnemy()->IsAlive()) );

	if ( bMaxHits )
	{
		// We've hit our target. Stop chasing, and return to max speed.
		chaseAngles[YAW] = lastChaseAngles[YAW];
		lastSpeed = BASE_STITCH_VELOCITY;
	}
	else
	{
		// Move towards the target yaw
		chaseAngles[YAW] = UTIL_AngleMod( lastChaseAngles[YAW] - yawDiff );
	}

	// If we've hit the target already, or we're not close enough to it, then just stitch along
	if ( bMaxHits || ( m_vecAttackPosition - enemyPos ).LengthSqr() > (64 * 64) )
	{
		AngleVectors( chaseAngles, &targetDir );

		// Update our new velocity
		m_vecAttackVelocity = targetDir * lastSpeed;

		if ( g_debug_gunship.GetInt() == GUNSHIP_DEBUG_STITCHING )
		{
			NDebugOverlay::Line( m_vecAttackPosition, m_vecAttackPosition + (m_vecAttackVelocity * 0.1), 255, 0, 0, true, 4.0f );
		}

		// Move along that velocity for this step in time
		m_vecAttackPosition += ( m_vecAttackVelocity * 0.1f );
		m_vecAttackPosition.z = enemyPos.z;
	}
	else
	{
		// Otherwise always continue to hit an NPC when close enough
		m_vecAttackPosition = enemyPos;
	}
}

//------------------------------------------------------------------------------
// Purpose: Utility function to aim the helicopter gun at the direction
//------------------------------------------------------------------------------
bool CNPC_CombineGunship::PoseGunTowardTargetDirection( const Vector &vTargetDir )
{
	Vector vecOut;
	VectorIRotate( vTargetDir, EntityToWorldTransform(), vecOut );

	QAngle angles;
	VectorAngles(vecOut, angles);
	angles.y = AngleNormalize( angles.y );
	angles.x = AngleNormalize( angles.x );

	if (angles.x > m_angGun.x)
	{
		m_angGun.x = min( angles.x, m_angGun.x + 12 );
	}
	if (angles.x < m_angGun.x)
	{
		m_angGun.x = max( angles.x, m_angGun.x - 12 );
	}
	if (angles.y > m_angGun.y)
	{
		m_angGun.y = min( angles.y, m_angGun.y + 12 );
	}
	if (angles.y < m_angGun.y)
	{
		m_angGun.y = max( angles.y, m_angGun.y - 12 );
	}

	SetPoseParameter( m_poseWeapon_Pitch, -m_angGun.x );
	SetPoseParameter( m_poseWeapon_Yaw, m_angGun.y );

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Vector
//-----------------------------------------------------------------------------
Vector CNPC_CombineGunship::GetMissileTarget( void )
{
	return GetEnemy()->GetAbsOrigin();
}

//------------------------------------------------------------------------------
// Purpose : Get the target position for the enemy- the position we fire upon.
//			 this is often modified by m_flAttackOffset to provide the 'stitching'
//			 behavior that's so popular with the kids these days (sjb)
//
// Input   : vGunPosition - location of gunship's muzzle
//		   : pTarget = vector to paste enemy target into.
// Output  :
//------------------------------------------------------------------------------
Vector CNPC_CombineGunship::GetEnemyTarget( void )
{
	// Make sure we have an enemy
	if ( GetEnemy() == NULL )
		return m_vecAttackPosition;

	// If we're locked onto a missile, use special code to try and destroy it
	if ( IsTargettingMissile() )
		return GetMissileTarget();

	return m_vecAttackPosition;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &tr - 
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::DoImpactEffect( trace_t &tr, int nDamageType )
{
	UTIL_ImpactTrace( &tr, nDamageType, "ImpactGunship" );

	// These glow effects don't sort properly, so they're cut for E3 2003 (sjb)
#if 0 
	CEffectData data;

	data.m_vOrigin = tr.endpos;
	data.m_vNormal = vec3_origin;
	data.m_vAngles = vec3_angle;

	DispatchEffect( "GunshipImpact", data );
#endif
}

//-----------------------------------------------------------------------------
// Purpose: Make the gunship's signature blue tracer!
// Input  : &vecTracerSrc - 
//			&tr - 
//			iTracerType - 
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType )
{
	switch ( iTracerType )
	{
	case TRACER_LINE:
		{
			float flTracerDist;
			Vector vecDir;
			Vector vecEndPos;

			vecDir = tr.endpos - vecTracerSrc;

			flTracerDist = VectorNormalize( vecDir );

			UTIL_Tracer( vecTracerSrc, tr.endpos, 0, TRACER_DONT_USE_ATTACHMENT, 8000, true, "GunshipTracer" );
		}
		break;

	default:
		BaseClass::MakeTracer( vecTracerSrc, tr, iTracerType );
		break;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &info - 
//			&vecDir - 
//			*ptr - 
// Output : int
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr )
{
	// Reflect bullets
	if ( info.GetDamageType() & DMG_BULLET )
	{
		if ( random->RandomInt( 0, 2 ) == 0 )
		{
			Vector vecRicochetDir = vecDir * -1;

			vecRicochetDir.x += random->RandomFloat( -0.5, 0.5 );
			vecRicochetDir.y += random->RandomFloat( -0.5, 0.5 );
			vecRicochetDir.z += random->RandomFloat( -0.5, 0.5 );

			VectorNormalize( vecRicochetDir );

			Vector end = ptr->endpos + vecRicochetDir * 1024;
			UTIL_Tracer( ptr->endpos, end, entindex(), TRACER_DONT_USE_ATTACHMENT, 3500 );
		}

		// If this is from a player, record it so a nearby citizen can respond.
		if ( info.GetAttacker()->IsPlayer() )
		{
			if ( GetCitizenResponse() )
			{
				GetCitizenResponse()->AddResponseTrigger( CR_PLAYER_SHOT_GUNSHIP );
			}

#ifdef HL2_EPISODIC
			NPCEventResponse()->TriggerEvent( "TLK_CITIZEN_RESPONSE_SHOT_GUNSHIP", false, false );
#endif
		}

		return;
	}

	BaseClass::TraceAttack( info, vecDir, ptr );
}

//-----------------------------------------------------------------------------
// Purpose: This is necessary to ensure that the game doesn't break if a mapmaker has outputs that
//			must be fired on gunships, and the player switches skill levels 
//			midway through a gunship battle.
// Input  : iDamageNumber - 
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::FireDamageOutputsUpto( int iDamageNumber )
{
	for ( int i = 0; i <= iDamageNumber; i++ )
	{
		if ( !m_bDamageOutputsFired[i] )
		{
			m_bDamageOutputsFired[i] = true;

			switch ( i )
			{
			case 0:
				//Msg("Fired first\n");
				m_OnFirstDamage.FireOutput( this, this );
				break;
			
			case 1:
				//Msg("Fired second\n");
				m_OnSecondDamage.FireOutput( this, this );
				break;
			
			case 2:
				//Msg("Fired third\n");
				m_OnThirdDamage.FireOutput( this, this );
				break;
			
			case 3:
				//Msg("Fired fourth\n");
				m_OnFourthDamage.FireOutput( this, this );
				break;
			}
		}
	}
}

//------------------------------------------------------------------------------
// Damage filtering
//------------------------------------------------------------------------------
int	CNPC_CombineGunship::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
{
	// Allow npc_kill to kill me
	if ( inputInfo.GetDamageType() != DMG_GENERIC )
	{
		// Ignore mundane bullet damage.
		if ( ( inputInfo.GetDamageType() & DMG_BLAST ) == false )
			return 0;

		// Ignore blasts less than this amount
		if ( inputInfo.GetDamage() < GUNSHIP_MIN_DAMAGE_THRESHOLD )
			return 0;
	}

	// Only take blast damage
	CTakeDamageInfo info = inputInfo;

	// Make a pain sound
	if ( !HasSpawnFlags( SF_GUNSHIP_USE_CHOPPER_MODEL ) )
	{
		EmitSound( "NPC_CombineGunship.Pain" );
	}

	Vector	damageDir = info.GetDamageForce();
	VectorNormalize( damageDir );

	// Don't get knocked around if I'm ground attacking
	if ( !m_bIsGroundAttacking )
	{
		ApplyAbsVelocityImpulse( damageDir * 200.0f );
	}
	
	if ( m_bInvulnerable == false )
	{
		// Take a percentage of our health away
		// Adjust health for damage
		int iHealthIncrements = sk_gunship_health_increments.GetInt();
		if ( g_pGameRules->IsSkillLevel( SKILL_EASY ) )
		{
			iHealthIncrements = (int)ceil( iHealthIncrements * 0.5 );
		}
		else if ( g_pGameRules->IsSkillLevel( SKILL_HARD ) )
		{
			iHealthIncrements = (int)floor( iHealthIncrements * 1.5 );
		}
		info.SetDamage( ( GetMaxHealth() / (float)iHealthIncrements ) + 1 );
		
		// Find out which "stage" we're at in our health
		int healthIncrement = iHealthIncrements - ( GetHealth() / (( GetMaxHealth() / iHealthIncrements )) );
		switch ( healthIncrement )
		{
		case 1:
			// If we're on Easy, we're half dead now, so fire the rest of our outputs too
			// This is done in case the mapmaker's connected those inputs to something important 
			// that has to happen before the gunship dies.
			if ( g_pGameRules->IsSkillLevel( SKILL_EASY ) )
			{
				FireDamageOutputsUpto( 3 );
			}
			else
			{
				FireDamageOutputsUpto( 1 );
			}
			break;

		default:
			FireDamageOutputsUpto( healthIncrement );
			break;
		}

		// Start smoking when we're almost dead
		CreateSmokeTrail();

		if ( m_pSmokeTrail )
		{
			if ( healthIncrement < 2 )
			{
				m_pSmokeTrail->SetLifetime( 8.0 );
			}

			m_pSmokeTrail->FollowEntity( this, "exhaustl" );
		}

		// Move with the target
		Vector	gibVelocity = GetAbsVelocity() + (-damageDir * 200.0f);

		// Dump out metal gibs
		CPVSFilter filter( GetAbsOrigin() );
	 	for ( int i = 0; i < 10; i++ )
		{
			int iModelIndex = modelinfo->GetModelIndex( g_PropDataSystem.GetRandomChunkModel( "MetalChunks" ) );	
			te->BreakModel( filter, 0.0, GetAbsOrigin(), vec3_angle, Vector(40,40,40), gibVelocity, iModelIndex, 400, 1, 2.5, BREAK_METAL );
		}
	}

	return BaseClass::OnTakeDamage_Alive( info );
}


//------------------------------------------------------------------------------
// Purpose : The proper way to begin the gunship cannon firing at the enemy.
// Input   : iBurstSize - the size of the burst, in rounds.
//------------------------------------------------------------------------------
void CNPC_CombineGunship::StartCannonBurst( int iBurstSize )
{
	m_iBurstSize = iBurstSize;
	m_iBurstHits = 0;

	m_flTimeNextAttack = gpGlobals->curtime;

	// Start up the cannon sound.
	CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
	controller.SoundChangeVolume( m_pCannonSound, 1.0, 0 );

	m_bIsFiring = true;

	// Setup the initial position of the burst
	if ( GetEnemy() )
	{
		// Follow mode
		Vector	enemyPos;
		UTIL_PredictedPosition( GetEnemy(), 2.0f, &enemyPos );

		QAngle offsetAngles;
		Vector offsetDir = ( WorldSpaceCenter() - enemyPos );
		VectorNormalize( offsetDir );
		VectorAngles( offsetDir, offsetAngles );

		int angleOffset = random->RandomInt( 15, 30 );
		if ( random->RandomInt( 0, 1 ) )
		{
			angleOffset *= -1;
		}
		offsetAngles[YAW] += angleOffset;
		offsetAngles[PITCH] = 0;
		offsetAngles[ROLL] = 0;

		AngleVectors( offsetAngles, &offsetDir );

		float stitchOffset;
		float enemyDist = GroundDistToPosition( GetEnemy()->GetAbsOrigin() );
		if ( enemyDist < ( sk_gunship_burst_dist.GetFloat() + GUNSHIP_STITCH_MIN ) )
		{
			stitchOffset = GUNSHIP_STITCH_MIN;
		}
		else
		{
			stitchOffset = sk_gunship_burst_dist.GetFloat();
		}

		// Move out to the start of our stitch run
		m_vecAttackPosition = enemyPos + ( offsetDir * stitchOffset );
		m_vecAttackPosition.z = enemyPos.z;

		// Point at our target
		m_vecAttackVelocity = -offsetDir * BASE_STITCH_VELOCITY;

		CSoundEnt::InsertSound( SOUND_DANGER | SOUND_CONTEXT_REACT_TO_SOURCE, enemyPos, 512, 0.2f, this );
	}
}


//------------------------------------------------------------------------------
// Purpose : The proper way to cease the gunship cannon firing. 
//------------------------------------------------------------------------------
void CNPC_CombineGunship::StopCannonBurst( void )
{
	m_iBurstHits = 0;
	m_bIsFiring = false;
	m_bPreFire = false;

	// Reduce the burst time when we get lower in health
	float flPerc = (float)GetHealth() / (float)GetMaxHealth();
	float flDelay = clamp( flPerc * m_flBurstDelay, 0.5, m_flBurstDelay );

	// If we didn't finish the burst, don't wait so long
	flPerc = 1.0 - (m_iBurstSize / sk_gunship_burst_size.GetFloat());
	flDelay *= flPerc;

	m_flTimeNextAttack = gpGlobals->curtime + flDelay;
	m_iBurstSize = 0;

	// Stop the cannon sound.
	if ( m_pCannonSound != NULL )
	{
		CSoundEnvelopeController::GetController().SoundChangeVolume( m_pCannonSound, 0.0, 0.05 );
	}

	EmitSound( "NPC_CombineGunship.CannonStopSound" );
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::StopLoopingSounds( void )
{
	CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();

	if ( m_pCannonSound )
	{
		controller.SoundDestroy( m_pCannonSound );
		m_pCannonSound = NULL;
	}

	if ( m_pRotorSound )
	{
		controller.SoundDestroy( m_pRotorSound );
		m_pRotorSound = NULL;
	}

	if ( m_pAirExhaustSound )
	{
		controller.SoundDestroy( m_pAirExhaustSound );
		m_pAirExhaustSound = NULL;
	}

	if ( m_pAirBlastSound )
	{
		controller.SoundDestroy( m_pAirBlastSound );
		m_pAirBlastSound = NULL;
	}

	BaseClass::StopLoopingSounds();
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pEnemy - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_CombineGunship::IsValidEnemy( CBaseEntity *pEnemy )
{
	// Always track missiles
	if ( pEnemy->IsAlive() && !pEnemy->MyNPCPointer() && FClassnameIs( pEnemy, "rpg_missile" ) )
		return true;

	// If we're shooting off a burst, don't pick up a new enemy
	if ( ( m_bIsFiring ) && ( ( GetEnemy() == NULL ) || ( GetEnemy() != pEnemy ) ) )
		return false;

	return BaseClass::IsValidEnemy( pEnemy );
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::GatherEnemyConditions( CBaseEntity *pEnemy )
{
	BaseClass::GatherEnemyConditions(pEnemy);

	// If we can't see the enemy for a few seconds, consider him unreachable
	if ( !HasCondition(COND_SEE_ENEMY) )
	{
		if ( gpGlobals->curtime - GetEnemyLastTimeSeen() >= 3.0f )
		{
			MarkEnemyAsEluded();
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: Tells us whether or not we're targetting an incoming missile
//-----------------------------------------------------------------------------
bool CNPC_CombineGunship::IsTargettingMissile( void )
{
	if ( GetEnemy() == NULL )
		return false;

	if ( FClassnameIs( GetEnemy(), "rpg_missile" ) == false )
		return false;

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::InputBecomeInvulnerable( inputdata_t &input )
{
	m_bInvulnerable = true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_CombineGunship::InputBecomeVulnerable( inputdata_t &input )
{
	m_bInvulnerable = false;
}

AI_BEGIN_CUSTOM_NPC( npc_combinegunship, CNPC_CombineGunship )

//	DECLARE_TASK(  )

	DECLARE_ACTIVITY( ACT_GUNSHIP_PATROL );
	DECLARE_ACTIVITY( ACT_GUNSHIP_HOVER );
	DECLARE_ACTIVITY( ACT_GUNSHIP_CRASH );

	//DECLARE_CONDITION( COND_ )

	//=========================================================
//	DEFINE_SCHEDULE
//	(
//		SCHED_DUMMY,
//
//		"	Tasks"
//		"		TASK_FACE_ENEMY			0"
//		"	"
//		"	Interrupts"
//	)


AI_END_CUSTOM_NPC()

