//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Large vehicle what delivers combine troops.
//
// $NoKeywords: $
//=============================================================================//

#include "cbase.h"
#include "ai_default.h"
#include "ai_basenpc.h"
#include "soundenvelope.h"
#include "cbasehelicopter.h"
#include "ai_schedule.h"
#include "engine/IEngineSound.h"
#include "smoke_trail.h"
#include "IEffects.h"
#include "props.h"
#include "TemplateEntities.h"
#include "baseanimating.h"
#include "ai_senses.h"
#include "entitylist.h"
#include "ammodef.h"
#include "ndebugoverlay.h"
#include "npc_combines.h"
#include "soundent.h"
#include "mapentities.h"
#include "npc_rollermine.h"
#include "scripted.h"
#include "explode.h"
#include "gib.h"
#include "EntityFlame.h"
#include "entityblocker.h"
#include "eventqueue.h"

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

// Spawnflags
#define SF_DROPSHIP_WAIT_FOR_DROPOFF_INPUT		( 1 << 15 )	

#define DROPSHIP_ACCEL_RATE				300

// Timers
#define DROPSHIP_LANDING_HOVER_TIME		5		// Time to spend on the ground if we have no troops to drop
#define DROPSHIP_TIME_BETWEEN_MINES		0.5f

// Special actions
#define DROPSHIP_DEFAULT_SOLDIERS		4
#define DROPSHIP_MAX_SOLDIERS			6

// Movement
#define DROPSHIP_BUFF_TIME				0.3f
#define DROPSHIP_MAX_LAND_TILT			2.5f
#define DROPSHIP_CONTAINER_HEIGHT		130.0f
#define DROPSHIP_MAX_SPEED				(60 * 17.6) // 120 miles per hour.

// Pathing data
#define	DROPSHIP_LEAD_DISTANCE			800.0f
#define	DROPSHIP_MIN_CHASE_DIST_DIFF	128.0f	// Distance threshold used to determine when a target has moved enough to update our navigation to it
#define	DROPSHIP_AVOID_DIST				256.0f
#define DROPSHIP_ARRIVE_DIST			128.0f

#define	CRATE_BBOX_MIN		(Vector( -100, -80, -60 ))
#define CRATE_BBOX_MAX		(Vector( 100, 80, 80 ))

// Size
// With crate
#define DROPSHIP_BBOX_CRATE_MIN		(-Vector(40,40,60))
#define DROPSHIP_BBOX_CRATE_MAX		(Vector(40,40,40))
// Without crate
#define DROPSHIP_BBOX_MIN			(-Vector(40,40,0))
#define DROPSHIP_BBOX_MAX			(Vector(40,40,40))

// Container gun
#define DROPSHIP_GUN_SPEED			10		// Rotation speed

#define DROPSHIP_CRATE_ROCKET_HITS	4

enum DROP_STATES 
{
	DROP_IDLE = 0,
	DROP_NEXT,
};

enum CRATE_TYPES 
{
	CRATE_JEEP = -3,
	CRATE_APC = -2,
	CRATE_STRIDER = -1,
	CRATE_ROLLER_HOPPER,
	CRATE_SOLDIER,
	CRATE_NONE,
};

ConVar	g_debug_dropship( "g_debug_dropship", "0" );
ConVar  sk_dropship_container_health( "sk_dropship_container_health", "750" );
ConVar	sk_npc_dmg_dropship( "sk_npc_dmg_dropship","5", FCVAR_NONE, "Dropship container cannon damage." );

//=====================================
// Animation Events
//=====================================
#define AE_DROPSHIP_RAMP_OPEN	1		// the tailgate is open.

//=====================================
// Custom activities
//=====================================
// Without Cargo
Activity ACT_DROPSHIP_FLY_IDLE;			// Flying. Vertical aspect 
Activity ACT_DROPSHIP_FLY_IDLE_EXAGG;	// Exaggerated version of the flying idle
// With Cargo
Activity ACT_DROPSHIP_FLY_IDLE_CARGO;	// Flying. Vertical aspect 
Activity ACT_DROPSHIP_DESCEND_IDLE;		// waiting to touchdown
Activity ACT_DROPSHIP_DEPLOY_IDLE;		// idle on the ground with door open. Troops are leaving.
Activity ACT_DROPSHIP_LIFTOFF;			// transition back to FLY IDLE

enum LandingState_t
{
	LANDING_NO = 0,

	// Dropoff
	LANDING_LEVEL_OUT,		// Heading to a point above the dropoff point
	LANDING_DESCEND,		// Descending from to the dropoff point
	LANDING_TOUCHDOWN,
	LANDING_UNLOADING,
	LANDING_UNLOADED,
	LANDING_LIFTOFF,

	// Pickup
	LANDING_SWOOPING,		// Swooping down to the target

	// Hovering, which we're saying is a type of landing since there's so much landing code to leverage
	LANDING_START_HOVER,
	LANDING_HOVER_LEVEL_OUT,
	LANDING_HOVER_DESCEND,
	LANDING_HOVER_TOUCHDOWN,
	LANDING_END_HOVER,
};


#define DROPSHIP_NEAR_SOUND_MIN_DISTANCE 1000
#define DROPSHIP_NEAR_SOUND_MAX_DISTANCE 2500
#define DROPSHIP_GROUND_WASH_MIN_ALTITUDE 100.0f
#define DROPSHIP_GROUND_WASH_MAX_ALTITUDE 750.0f



//=============================================================================
// The combine dropship container
//=============================================================================
#define DROPSHIP_CONTAINER_MODEL "models/combine_dropship_container.mdl"

#define DROPSHIP_CONTAINER_MAX_CHUNKS	3
static const char *s_pChunkModelName[DROPSHIP_CONTAINER_MAX_CHUNKS] = 
{
	"models/gibs/helicopter_brokenpiece_01.mdl",
	"models/gibs/helicopter_brokenpiece_02.mdl",
	"models/gibs/helicopter_brokenpiece_03.mdl",
};

#define DROPSHIP_CONTAINER_MAX_GIBS	1
static const char *s_pGibModelName[DROPSHIP_CONTAINER_MAX_GIBS] = 
{
	"models/combine_dropship_container.mdl",
};

class CCombineDropshipContainer : public CPhysicsProp
{
	DECLARE_CLASS( CCombineDropshipContainer, CPhysicsProp );
	DECLARE_DATADESC();

public:
	void Precache();
	virtual void Spawn();
	virtual bool OverridePropdata( void );
	virtual int OnTakeDamage( const CTakeDamageInfo &info );
	virtual void Event_Killed( const CTakeDamageInfo &info );

private:
	enum
	{
		MAX_SMOKE_TRAILS = 4,
		MAX_EXPLOSIONS = 4,
	};

	// Should we trigger a damage effect?
	bool ShouldTriggerDamageEffect( int nPrevHealth, int nEffectCount ) const;

	// Add a smoke trail since we've taken more damage
	void AddSmokeTrail( const Vector &vecPos );

	// Pow!
	void ThrowFlamingGib();

	// Create a corpse
	void CreateCorpse();

private:
	int m_nSmokeTrailCount;
	EHANDLE m_hLastInflictor;
	float m_flLastHitTime;
};

//=============================================================================
// The combine dropship
//=============================================================================
class CNPC_CombineDropship : public CBaseHelicopter
{
	DECLARE_CLASS( CNPC_CombineDropship, CBaseHelicopter );

public:
	~CNPC_CombineDropship();

	// Setup
	void	Spawn( void );
	void	Precache( void );

	void	Activate( void );

	// Thinking/init
	void	InitializeRotorSound( void );
	void	StopLoopingSounds();
	void	PrescheduleThink( void );

	// Flight/sound
	void	Hunt( void );
	void	Flight( void );
	float	GetAltitude( void );
	void	DoRotorWash( void );
	void	UpdateRotorSoundPitch( int iPitch );
	void	UpdatePickupNavigation( void );
	void	UpdateLandTargetNavigation( void );
	void	CalculateSoldierCount( int iSoldiers );

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

	// Combat
	void	GatherEnemyConditions( CBaseEntity *pEnemy );
	void	DoCombatStuff( void );
	void	SpawnTroop( void );
	void	DropMine( void );
	void	UpdateContainerGunFacing( Vector &vecMuzzle, Vector &vecToTarget, Vector &vecAimDir, float *flTargetRange );
	bool	FireCannonRound( void );
	void	DoImpactEffect( trace_t &tr, int nDamageType );
	void	StartCannon( void );
	void	StopCannon( void );
	void	MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType );
	int		OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo );

	// Input handlers.
	void	InputLandLeave( inputdata_t &inputdata );
	void	InputLandTake( inputdata_t &inputdata );
	void	InputSetLandTarget( inputdata_t &inputdata );
	void	InputDropMines( inputdata_t &inputdata );
	void	InputDropStrider( inputdata_t &inputdata );
	void	InputDropAPC( inputdata_t &inputdata );

	void	InputPickup( inputdata_t &inputdata );
	void	InputSetGunRange( inputdata_t &inputdata );
	void	InputNPCFinishDustoff( inputdata_t &inputdata );
	void	InputStopWaitingForDropoff( inputdata_t &inputdata );

	void	InputHover( inputdata_t &inputdata );

	// From AI_TrackPather
	virtual void InputFlyToPathTrack( inputdata_t &inputdata );

	Vector	GetDropoffFinishPosition( Vector vecOrigin, CAI_BaseNPC *pNPC, Vector vecMins, Vector vecMaxs );
	void	LandCommon( bool bHover = false );

	Class_T Classify( void ) { return CLASS_COMBINE_GUNSHIP; }

	// Drop the soldier container
	void	DropSoldierContainer( );

	// Sounds
	virtual void UpdateRotorWashVolume();

private:
	void SetLandingState( LandingState_t landingState );
	LandingState_t GetLandingState() const { return (LandingState_t)m_iLandState; }
	bool IsHovering();
	void UpdateGroundRotorWashSound( float flAltitude );
	void UpdateRotorWashVolume( CSoundPatch *pRotorSound, float flVolume, float flDeltaTime );

private:
	// Timers
	float	m_flTimeTakeOff;
	float	m_flNextTroopSpawnAttempt;
	float	m_flDropDelay;			// delta between each mine
	float	m_flTimeNextAttack;
	float	m_flLastTime;
	
	// States and counters
	int		m_iMineCount;		// index for current mine # being deployed
	int		m_totalMinesToDrop;	// total # of mines to drop as a group (based upon triggered input)
	int		m_soldiersToDrop;
	int		m_iDropState;
	int		m_iLandState; 
	float	m_engineThrust;		// for tracking sound volume/pitch
	float	m_existPitch;
	float	m_existRoll;
	bool	m_bDropMines;		// signal to drop mines
	bool	m_bIsFiring;
	int		m_iBurstRounds;
	bool	m_leaveCrate;
	bool	m_bHasDroppedOff;
	int		m_iCrateType;
	float	m_flLandingSpeed;
	float	m_flGunRange;
	bool	m_bInvulnerable;

	QAngle	m_vecAngAcceleration;
	
	// Misc Vars
	CHandle<CBaseAnimating>	m_hContainer;
	EHANDLE		m_hPickupTarget;
	int			m_iContainerMoveType;
	bool		m_bWaitForDropoffInput;

	DECLARE_DATADESC();
	DEFINE_CUSTOM_AI;

	EHANDLE		m_hLandTarget;
	string_t	m_iszLandTarget;

	string_t	m_iszAPCVehicleName;

	// Templates for soldier's dropped off
	string_t	m_sNPCTemplate[ DROPSHIP_MAX_SOLDIERS ];
	string_t	m_sNPCTemplateData[ DROPSHIP_MAX_SOLDIERS ];	
	string_t	m_sDustoffPoints[ DROPSHIP_MAX_SOLDIERS ];	
	int			m_iCurrentTroopExiting;
	EHANDLE		m_hLastTroopToLeave;

	// Template for rollermines dropped by this dropship
	string_t	m_sRollermineTemplate;
	string_t	m_sRollermineTemplateData;

	// Cached attachment points
	int			m_iMuzzleAttachment;
	int			m_iMachineGunBaseAttachment;
	int			m_iMachineGunRefAttachment;
	int			m_iAttachmentTroopDeploy;
	int			m_iAttachmentDeployStart;

	// Sounds
	CSoundPatch		*m_pCannonSound;
	CSoundPatch		*m_pRotorOnGroundSound;
	CSoundPatch		*m_pDescendingWarningSound;
	CSoundPatch		*m_pNearRotorSound;

	// Outputs
	COutputEvent	m_OnFinishedDropoff;
	COutputEvent	m_OnFinishedPickup;

	COutputFloat	m_OnContainerShotDownBeforeDropoff;
	COutputEvent	m_OnContainerShotDownAfterDropoff;

protected:
	// Because the combine dropship is a leaf class, we can use
	// static variables to store this information, and save some memory.
	// Should the dropship end up having inheritors, their activate may
	// stomp these numbers, in which case you should make these ordinary members
	// again.
	static int m_poseBody_Accel, m_poseBody_Sway, m_poseCargo_Body_Accel, m_poseCargo_Body_Sway, 
		m_poseWeapon_Pitch, m_poseWeapon_Yaw;
	static bool m_sbStaticPoseParamsLoaded;
	virtual void	PopulatePoseParameters( void );
};

bool CNPC_CombineDropship::m_sbStaticPoseParamsLoaded = false;

int CNPC_CombineDropship::m_poseBody_Accel = 0;
int CNPC_CombineDropship::m_poseBody_Sway = 0;
int CNPC_CombineDropship::m_poseCargo_Body_Accel = 0;
int CNPC_CombineDropship::m_poseCargo_Body_Sway = 0;
int CNPC_CombineDropship::m_poseWeapon_Pitch = 0;
int CNPC_CombineDropship::m_poseWeapon_Yaw = 0;

//-----------------------------------------------------------------------------
// Purpose: Cache whatever pose parameters we intend to use
//-----------------------------------------------------------------------------
void	CNPC_CombineDropship::PopulatePoseParameters( void )
{
	if (!m_sbStaticPoseParamsLoaded)
	{
		m_poseBody_Accel		= LookupPoseParameter( "body_accel");
		m_poseBody_Sway			= LookupPoseParameter( "body_sway" );
		m_poseCargo_Body_Accel  = LookupPoseParameter( "cargo_body_accel" );
		m_poseCargo_Body_Sway   = LookupPoseParameter( "cargo_body_sway" );
		m_poseWeapon_Pitch		= LookupPoseParameter( "weapon_pitch" );
		m_poseWeapon_Yaw		= LookupPoseParameter( "weapon_yaw" );

		m_sbStaticPoseParamsLoaded = true;
	}

	BaseClass::PopulatePoseParameters();
}

//------------------------------------------------------------------------------
//
// Combine Dropship Container implementation:
//
//------------------------------------------------------------------------------
LINK_ENTITY_TO_CLASS( prop_dropship_container, CCombineDropshipContainer )

BEGIN_DATADESC( CCombineDropshipContainer )

	DEFINE_FIELD( m_nSmokeTrailCount,	FIELD_INTEGER ),
	DEFINE_FIELD( m_hLastInflictor,		FIELD_EHANDLE ),
	DEFINE_FIELD( m_flLastHitTime,		FIELD_TIME ),

END_DATADESC()

//-----------------------------------------------------------------------------
// Precache 
//-----------------------------------------------------------------------------
void CCombineDropshipContainer::Precache()
{
	PrecacheModel( DROPSHIP_CONTAINER_MODEL );

	// Set this here to quiet base prop warnings
	SetModel( DROPSHIP_CONTAINER_MODEL );

	BaseClass::Precache();

	int i;
	for ( i = 0; i < DROPSHIP_CONTAINER_MAX_CHUNKS; ++i )
	{
		PrecacheModel( s_pChunkModelName[i] );
	}

	for ( i = 0; i < DROPSHIP_CONTAINER_MAX_GIBS; ++i )
	{
		PrecacheModel( s_pGibModelName[i] );
	}

	PropBreakablePrecacheAll( GetModelName() );
}


//-----------------------------------------------------------------------------
// Spawn 
//-----------------------------------------------------------------------------
void CCombineDropshipContainer::Spawn()
{
	// NOTE: Model must be set before spawn
	SetModel( DROPSHIP_CONTAINER_MODEL );
	SetSolid( SOLID_VPHYSICS );

	BaseClass::Spawn();

#ifdef _XBOX
	AddEffects( EF_NOSHADOW );
#endif //_XBOX

	m_iHealth = m_iMaxHealth = sk_dropship_container_health.GetFloat();
}


//-----------------------------------------------------------------------------
// Allows us to use vphysics
//-----------------------------------------------------------------------------
bool CCombineDropshipContainer::OverridePropdata( void )
{
	return true;
}


//-----------------------------------------------------------------------------
// Should we trigger a damage effect?
//-----------------------------------------------------------------------------
inline bool CCombineDropshipContainer::ShouldTriggerDamageEffect( int nPrevHealth, int nEffectCount ) const
{
	int nPrevRange = (int)( ((float)nPrevHealth / (float)GetMaxHealth()) * nEffectCount );
	int nRange = (int)( ((float)GetHealth() / (float)GetMaxHealth()) * nEffectCount );
	return ( nRange != nPrevRange );
}


//-----------------------------------------------------------------------------
// Character killed (only fired once)
//-----------------------------------------------------------------------------
void CCombineDropshipContainer::CreateCorpse()
{
	m_lifeState = LIFE_DEAD;

	Vector vecNormalizedMins, vecNormalizedMaxs;
	Vector vecAbsMins, vecAbsMaxs;
	CollisionProp()->WorldSpaceAABB( &vecAbsMins, &vecAbsMaxs );
	CollisionProp()->WorldToNormalizedSpace( vecAbsMins, &vecNormalizedMins );
	CollisionProp()->WorldToNormalizedSpace( vecAbsMaxs, &vecNormalizedMaxs );

	// Explode
	Vector vecAbsPoint;
	CPASFilter filter( GetAbsOrigin() );
	CollisionProp()->RandomPointInBounds( vecNormalizedMins, vecNormalizedMaxs, &vecAbsPoint);
	te->Explosion( filter, 0.0f, &vecAbsPoint, g_sModelIndexFireball, 
		random->RandomInt( 4, 10 ), random->RandomInt( 8, 15 ), TE_EXPLFLAG_NOPARTICLES, 100, 0 );

	// Break into chunks
	Vector angVelocity;
	QAngleToAngularImpulse( GetLocalAngularVelocity(), angVelocity );
	PropBreakableCreateAll( GetModelIndex(), VPhysicsGetObject(), GetAbsOrigin(), GetAbsAngles(), GetAbsVelocity(), angVelocity, 1.0, 250, COLLISION_GROUP_NPC, this );

	// Create flaming gibs
	int iChunks = random->RandomInt( 4, 6 );
	for ( int i = 0; i < iChunks; i++ )
	{
		ThrowFlamingGib();
	}

	AddSolidFlags( FSOLID_NOT_SOLID );
	AddEffects( EF_NODRAW );
	UTIL_Remove( this );
}


//-----------------------------------------------------------------------------
// Character killed (only fired once)
//-----------------------------------------------------------------------------
void CCombineDropshipContainer::ThrowFlamingGib( void )
{
	Vector vecAbsMins, vecAbsMaxs;
	CollisionProp()->WorldSpaceAABB( &vecAbsMins, &vecAbsMaxs );

	Vector vecNormalizedMins, vecNormalizedMaxs;
	CollisionProp()->WorldToNormalizedSpace( vecAbsMins, &vecNormalizedMins );
	CollisionProp()->WorldToNormalizedSpace( vecAbsMaxs, &vecNormalizedMaxs );

	Vector vecAbsPoint;
	CPASFilter filter( GetAbsOrigin() );
	CollisionProp()->RandomPointInBounds( vecNormalizedMins, vecNormalizedMaxs, &vecAbsPoint);

	// Throw a flaming, smoking chunk.
	CGib *pChunk = CREATE_ENTITY( CGib, "gib" );
	pChunk->Spawn( "models/gibs/hgibs.mdl" );
	pChunk->SetBloodColor( DONT_BLEED );

	QAngle vecSpawnAngles;
	vecSpawnAngles.Random( -90, 90 );
	pChunk->SetAbsOrigin( vecAbsPoint );
	pChunk->SetAbsAngles( vecSpawnAngles );

	int nGib = random->RandomInt( 0, DROPSHIP_CONTAINER_MAX_CHUNKS - 1 );
	pChunk->Spawn( s_pChunkModelName[nGib] );
	pChunk->SetOwnerEntity( this );
	pChunk->m_lifeTime = random->RandomFloat( 6.0f, 8.0f );
	pChunk->SetCollisionGroup( COLLISION_GROUP_DEBRIS );
	IPhysicsObject *pPhysicsObject = pChunk->VPhysicsInitNormal( SOLID_VPHYSICS, pChunk->GetSolidFlags(), false );
	
	// Set the velocity
	if ( pPhysicsObject )
	{
		pPhysicsObject->EnableMotion( true );
		Vector vecVelocity;

		QAngle angles;
		angles.x = random->RandomFloat( -20, 20 );
		angles.y = random->RandomFloat( 0, 360 );
		angles.z = 0.0f;
		AngleVectors( angles, &vecVelocity );
		
		vecVelocity *= random->RandomFloat( 300, 900 );
		vecVelocity += GetAbsVelocity();

		AngularImpulse angImpulse;
		angImpulse = RandomAngularImpulse( -180, 180 );

		pChunk->SetAbsVelocity( vecVelocity );
		pPhysicsObject->SetVelocity(&vecVelocity, &angImpulse );
	}

	CEntityFlame *pFlame = CEntityFlame::Create( pChunk, false );
	if ( pFlame != NULL )
	{
		pFlame->SetLifetime( pChunk->m_lifeTime );
	}

	SmokeTrail *pSmokeTrail =  SmokeTrail::CreateSmokeTrail();
	if( pSmokeTrail )
	{
		pSmokeTrail->m_SpawnRate = 80;
		pSmokeTrail->m_ParticleLifetime = 0.8f;
		pSmokeTrail->m_StartColor.Init(0.3, 0.3, 0.3);
		pSmokeTrail->m_EndColor.Init(0.5, 0.5, 0.5);
		pSmokeTrail->m_StartSize = 10;
		pSmokeTrail->m_EndSize = 40;
		pSmokeTrail->m_SpawnRadius = 5;
		pSmokeTrail->m_Opacity = 0.4;
		pSmokeTrail->m_MinSpeed = 15;
		pSmokeTrail->m_MaxSpeed = 25;
		pSmokeTrail->SetLifetime( pChunk->m_lifeTime );
		pSmokeTrail->SetParent( pChunk, 0 );
		pSmokeTrail->SetLocalOrigin( vec3_origin );
		pSmokeTrail->SetMoveType( MOVETYPE_NONE );
	}
}


//-----------------------------------------------------------------------------
// Character killed (only fired once)
//-----------------------------------------------------------------------------
void CCombineDropshipContainer::Event_Killed( const CTakeDamageInfo &info )
{
	if ( GetOwnerEntity() )
	{
		CNPC_CombineDropship *pDropship = assert_cast<CNPC_CombineDropship *>(GetOwnerEntity() );
		pDropship->DropSoldierContainer();
	}

	CreateCorpse();
}


//-----------------------------------------------------------------------------
// Damage effects 
//-----------------------------------------------------------------------------
int CCombineDropshipContainer::OnTakeDamage( const CTakeDamageInfo &info )
{
	if ( m_iHealth == 0 )
		return 0;

	// Airboat guns + explosive damage is all that can hurt it
	if (( info.GetDamageType() & (DMG_BLAST | DMG_AIRBOAT) ) == 0 )
		return 0;

	CTakeDamageInfo dmgInfo = info;

	int nPrevHealth = GetHealth();

	if ( info.GetDamageType() & DMG_BLAST )
	{
		// This check is necessary to prevent double-counting of rocket damage
		// from the blast hitting both the dropship + the container
		if ( (info.GetInflictor() != m_hLastInflictor) || (gpGlobals->curtime != m_flLastHitTime) )
		{
			m_iHealth -= (m_iMaxHealth / DROPSHIP_CRATE_ROCKET_HITS) + 1;
			m_hLastInflictor = info.GetInflictor();
			m_flLastHitTime = gpGlobals->curtime; 
		}
	}
	else
	{
		m_iHealth -= dmgInfo.GetDamage();
	}

	if ( m_iHealth <= 0 )
	{
		m_iHealth = 0;
		Event_Killed( dmgInfo );
		return 0;
	}

	// Spawn damage effects
	if ( nPrevHealth != GetHealth() )
	{
		if ( ShouldTriggerDamageEffect( nPrevHealth, MAX_SMOKE_TRAILS ) )
		{
			AddSmokeTrail( dmgInfo.GetDamagePosition() );
		}

		if ( ShouldTriggerDamageEffect( nPrevHealth, MAX_EXPLOSIONS ) )
		{
			ExplosionCreate( dmgInfo.GetDamagePosition(), vec3_angle, this, 1000, 500.0f, 
			SF_ENVEXPLOSION_NODAMAGE | SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE, 0 );
			UTIL_ScreenShake( dmgInfo.GetDamagePosition(), 25.0, 150.0, 1.0, 750.0f, SHAKE_START );

			ThrowFlamingGib();
		}
	}

	return 1;
}


//-----------------------------------------------------------------------------
// Add a smoke trail since we've taken more damage
//-----------------------------------------------------------------------------
void CCombineDropshipContainer::AddSmokeTrail( const Vector &vecPos )
{
	// Start this trail out with a bang!
	ExplosionCreate( vecPos, vec3_angle, this, 1000, 500.0f, SF_ENVEXPLOSION_NODAMAGE | 
		SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE, 0 );
	UTIL_ScreenShake( vecPos, 25.0, 150.0, 1.0, 750.0f, SHAKE_START );

	if ( m_nSmokeTrailCount == MAX_SMOKE_TRAILS )
		return;

	SmokeTrail *pSmokeTrail =  SmokeTrail::CreateSmokeTrail();
	if( !pSmokeTrail )
		return;

	// See if there's an attachment for this smoke trail
	char buf[32];
	Q_snprintf( buf, 32, "damage%d", m_nSmokeTrailCount );
	int nAttachment = LookupAttachment( buf );

	++m_nSmokeTrailCount;

	pSmokeTrail->m_SpawnRate = 20;
	pSmokeTrail->m_ParticleLifetime = 4.0f;
	pSmokeTrail->m_StartColor.Init( 0.7f, 0.7f, 0.7f );
	pSmokeTrail->m_EndColor.Init( 0.6, 0.6, 0.6 );
	pSmokeTrail->m_StartSize = 15;
	pSmokeTrail->m_EndSize = 50;
	pSmokeTrail->m_SpawnRadius = 15;
	pSmokeTrail->m_Opacity = 0.75f;
	pSmokeTrail->m_MinSpeed = 10;
	pSmokeTrail->m_MaxSpeed = 20;
	pSmokeTrail->m_MinDirectedSpeed	= 100.0f;
	pSmokeTrail->m_MaxDirectedSpeed	= 120.0f;
	pSmokeTrail->SetLifetime( 5 );
	pSmokeTrail->SetParent( this, nAttachment );
	if ( nAttachment == 0 )
	{
		pSmokeTrail->SetAbsOrigin( vecPos );
	}
	else
	{
		pSmokeTrail->SetLocalOrigin( vec3_origin );
	}

	Vector vecForward( -1, 0, 0 );
	QAngle angles;
	VectorAngles( vecForward, angles );
	pSmokeTrail->SetAbsAngles( angles );
	pSmokeTrail->SetMoveType( MOVETYPE_NONE );
}


//------------------------------------------------------------------------------
//
// Combine Dropship implementation:
//
//------------------------------------------------------------------------------
LINK_ENTITY_TO_CLASS( npc_combinedropship, CNPC_CombineDropship );

BEGIN_DATADESC( CNPC_CombineDropship )

	DEFINE_FIELD( m_flTimeTakeOff, FIELD_TIME ),
	DEFINE_FIELD( m_flNextTroopSpawnAttempt, FIELD_TIME ),
	DEFINE_FIELD( m_flDropDelay, FIELD_TIME ),
	DEFINE_FIELD( m_flTimeNextAttack, FIELD_TIME ),
	DEFINE_FIELD( m_flLastTime, FIELD_TIME ),
	DEFINE_FIELD( m_iMineCount, FIELD_INTEGER ),
	DEFINE_FIELD( m_totalMinesToDrop, FIELD_INTEGER ),
	DEFINE_FIELD( m_soldiersToDrop, FIELD_INTEGER ),
	DEFINE_FIELD( m_iDropState, FIELD_INTEGER ),
	DEFINE_FIELD( m_bDropMines, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_iLandState, FIELD_INTEGER ),
	DEFINE_FIELD( m_engineThrust, FIELD_FLOAT ),
	DEFINE_FIELD( m_bIsFiring, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_iBurstRounds, FIELD_INTEGER ),
	DEFINE_FIELD( m_existPitch, FIELD_FLOAT ),
	DEFINE_FIELD( m_existRoll, FIELD_FLOAT ),
	DEFINE_FIELD( m_leaveCrate, FIELD_BOOLEAN ),
	DEFINE_KEYFIELD( m_iCrateType, FIELD_INTEGER, "CrateType" ),
	DEFINE_FIELD( m_flLandingSpeed, FIELD_FLOAT ),
	DEFINE_KEYFIELD( m_flGunRange, FIELD_FLOAT, "GunRange" ),
	DEFINE_FIELD( m_vecAngAcceleration,FIELD_VECTOR ),
	DEFINE_FIELD( m_hContainer, FIELD_EHANDLE ),
	DEFINE_FIELD( m_hPickupTarget, FIELD_EHANDLE ),
	DEFINE_FIELD( m_iContainerMoveType, FIELD_INTEGER ),
	DEFINE_FIELD( m_bWaitForDropoffInput, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_hLandTarget, FIELD_EHANDLE ),
	DEFINE_FIELD( m_bHasDroppedOff, FIELD_BOOLEAN ),
	DEFINE_KEYFIELD( m_bInvulnerable, FIELD_BOOLEAN, "Invulnerable" ),
	DEFINE_KEYFIELD( m_iszLandTarget, FIELD_STRING,	"LandTarget" ),
	DEFINE_SOUNDPATCH( m_pRotorOnGroundSound ),
	DEFINE_SOUNDPATCH( m_pDescendingWarningSound ),
	DEFINE_SOUNDPATCH( m_pNearRotorSound ),

	DEFINE_KEYFIELD( m_iszAPCVehicleName, FIELD_STRING,	"APCVehicleName" ),

	DEFINE_KEYFIELD( m_sRollermineTemplate, FIELD_STRING,	"RollermineTemplate" ),
	DEFINE_FIELD( m_sRollermineTemplateData, FIELD_STRING ),

	DEFINE_ARRAY( m_sNPCTemplateData, FIELD_STRING, DROPSHIP_MAX_SOLDIERS ),
	DEFINE_KEYFIELD( m_sNPCTemplate[0], FIELD_STRING,	"NPCTemplate" ),
	DEFINE_KEYFIELD( m_sNPCTemplate[1], FIELD_STRING,	"NPCTemplate2" ),
	DEFINE_KEYFIELD( m_sNPCTemplate[2], FIELD_STRING,	"NPCTemplate3" ),
	DEFINE_KEYFIELD( m_sNPCTemplate[3], FIELD_STRING,	"NPCTemplate4" ),
	DEFINE_KEYFIELD( m_sNPCTemplate[4], FIELD_STRING,	"NPCTemplate5" ),
	DEFINE_KEYFIELD( m_sNPCTemplate[5], FIELD_STRING,	"NPCTemplate6" ),
	// Here to shut classcheck up
	//DEFINE_ARRAY( m_sNPCTemplate, FIELD_STRING,  DROPSHIP_MAX_SOLDIERS  ),
	//DEFINE_ARRAY( m_sDustoffPoints, FIELD_STRING,  DROPSHIP_MAX_SOLDIERS  ),
	DEFINE_KEYFIELD( m_sDustoffPoints[0], FIELD_STRING,	"Dustoff1" ),
	DEFINE_KEYFIELD( m_sDustoffPoints[1], FIELD_STRING,	"Dustoff2" ),
	DEFINE_KEYFIELD( m_sDustoffPoints[2], FIELD_STRING,	"Dustoff3" ),
	DEFINE_KEYFIELD( m_sDustoffPoints[3], FIELD_STRING,	"Dustoff4" ),
	DEFINE_KEYFIELD( m_sDustoffPoints[4], FIELD_STRING,	"Dustoff5" ),
	DEFINE_KEYFIELD( m_sDustoffPoints[5], FIELD_STRING,	"Dustoff6" ),
	DEFINE_FIELD( m_iCurrentTroopExiting, FIELD_INTEGER ),
	DEFINE_FIELD( m_hLastTroopToLeave, FIELD_EHANDLE ),

	DEFINE_FIELD( m_iMuzzleAttachment, FIELD_INTEGER ),
	DEFINE_FIELD( m_iMachineGunBaseAttachment, FIELD_INTEGER ),
	DEFINE_FIELD( m_iMachineGunRefAttachment, FIELD_INTEGER ),
	DEFINE_FIELD( m_iAttachmentTroopDeploy, FIELD_INTEGER ),
	DEFINE_FIELD( m_iAttachmentDeployStart , FIELD_INTEGER ),

	DEFINE_SOUNDPATCH( m_pCannonSound ),
	
	DEFINE_INPUTFUNC( FIELD_INTEGER, "LandLeaveCrate", InputLandLeave ),
	DEFINE_INPUTFUNC( FIELD_INTEGER, "LandTakeCrate", InputLandTake ),
	DEFINE_INPUTFUNC( FIELD_STRING, "SetLandTarget", InputSetLandTarget ),
	DEFINE_INPUTFUNC( FIELD_INTEGER, "DropMines", InputDropMines ),
	DEFINE_INPUTFUNC( FIELD_VOID, "DropStrider", InputDropStrider ),
	DEFINE_INPUTFUNC( FIELD_VOID, "DropAPC", InputDropAPC ),
	DEFINE_INPUTFUNC( FIELD_STRING, "Pickup", InputPickup ),
	DEFINE_INPUTFUNC( FIELD_FLOAT, "SetGunRange", InputSetGunRange ),
	DEFINE_INPUTFUNC( FIELD_STRING, "NPCFinishDustoff", InputNPCFinishDustoff ),
	DEFINE_INPUTFUNC( FIELD_VOID, "StopWaitingForDropoff", InputStopWaitingForDropoff ),
	DEFINE_INPUTFUNC( FIELD_STRING, "Hover", InputHover ),
	DEFINE_INPUTFUNC( FIELD_STRING, "FlyToPathTrack", InputFlyToPathTrack ),
	
	DEFINE_OUTPUT( m_OnFinishedDropoff, "OnFinishedDropoff" ),
	DEFINE_OUTPUT( m_OnFinishedPickup, "OnFinishedPickup" ),

	DEFINE_OUTPUT( m_OnContainerShotDownBeforeDropoff, "OnCrateShotDownBeforeDropoff" ),
	DEFINE_OUTPUT( m_OnContainerShotDownAfterDropoff, "OnCrateShotDownAfterDropoff" ),

END_DATADESC()


//------------------------------------------------------------------------------
// Purpose : Destructor
//------------------------------------------------------------------------------
CNPC_CombineDropship::~CNPC_CombineDropship(void)
{
	if ( m_hContainer )
	{
		UTIL_Remove( m_hContainer );		// get rid of container
	}
}

//------------------------------------------------------------------------------
// Purpose :
// Input   :
// Output  :
//------------------------------------------------------------------------------
void CNPC_CombineDropship::Spawn( void )
{
	Precache( );
	SetModel( "models/combine_dropship.mdl" );

#ifdef _XBOX
	AddEffects( EF_NOSHADOW );
#endif //_XBOX

	InitPathingData( DROPSHIP_ARRIVE_DIST, DROPSHIP_MIN_CHASE_DIST_DIFF, DROPSHIP_AVOID_DIST );

	m_iContainerMoveType = MOVETYPE_NONE;
	m_iCurrentTroopExiting = 0;
	m_bHasDroppedOff = false;
	m_iMuzzleAttachment = -1;
	m_iMachineGunBaseAttachment = -1;
	m_iMachineGunRefAttachment = -1;
	m_iAttachmentTroopDeploy = -1;
	m_iAttachmentDeployStart = -1;

	// create the correct bin for the ship to carry
	switch ( m_iCrateType )
	{
	case CRATE_ROLLER_HOPPER:
		break;

	case CRATE_SOLDIER:
		m_hContainer = (CBaseAnimating*)CreateEntityByName( "prop_dropship_container" );
		if ( m_hContainer )
		{
			m_hContainer->SetName( AllocPooledString("dropship_container") );
			m_hContainer->SetAbsOrigin( GetAbsOrigin() );
			m_hContainer->SetAbsAngles( GetAbsAngles() );
			m_hContainer->SetParent(this, 0);
			m_hContainer->SetOwnerEntity(this);
			m_hContainer->Spawn();

			IPhysicsObject *pPhysicsObject = m_hContainer->VPhysicsGetObject();
			if ( pPhysicsObject )
			{
				pPhysicsObject->SetShadow( 1e4, 1e4, false, false );
				pPhysicsObject->UpdateShadow( m_hContainer->GetAbsOrigin(), m_hContainer->GetAbsAngles(), false, 0 );
			}

			m_hContainer->SetMoveType( MOVETYPE_PUSH );
			m_hContainer->SetGroundEntity( NULL );

			// Cache off container's attachment points
			m_iAttachmentTroopDeploy = m_hContainer->LookupAttachment( "deploy_landpoint" );
			m_iAttachmentDeployStart = m_hContainer->LookupAttachment( "Deploy_Start" );
			m_iMuzzleAttachment = m_hContainer->LookupAttachment( "muzzle" );
			m_iMachineGunBaseAttachment = m_hContainer->LookupAttachment( "gun_base" );
			// NOTE: gun_ref must have the same position as gun_base, but rotates with the gun
			m_iMachineGunRefAttachment = m_hContainer->LookupAttachment( "gun_ref" );
		}
		break;

	case CRATE_STRIDER:
		m_hContainer = (CBaseAnimating*)CreateEntityByName( "npc_strider" );
		m_hContainer->SetAbsOrigin( GetAbsOrigin() - Vector( 0, 0 , 100 ) );
		m_hContainer->SetAbsAngles( GetAbsAngles() );
		m_hContainer->SetParent(this, 0);
		m_hContainer->SetOwnerEntity(this);
		m_hContainer->Spawn();
		m_hContainer->SetAbsOrigin( GetAbsOrigin() - Vector( 0, 0 , 100 ) );
		break;

	case CRATE_APC:
		{
			m_soldiersToDrop = 0;
			m_hContainer = (CBaseAnimating*)gEntList.FindEntityByName( NULL, m_iszAPCVehicleName );
			if ( !m_hContainer )
			{
				Warning("Unable to find APC %s\n", STRING( m_iszAPCVehicleName ) ); 		
				break;
			}

			Vector apcPosition = GetAbsOrigin() - Vector( 0, 0 , 25 );
			QAngle apcAngles = GetAbsAngles();
			VMatrix mat, rot, result;
			MatrixFromAngles( apcAngles, mat );
			MatrixBuildRotateZ( rot, -90 );
			MatrixMultiply( mat, rot, result );
			MatrixToAngles( result, apcAngles );

			m_hContainer->Teleport( &apcPosition, &apcAngles, NULL );

			m_iContainerMoveType = m_hContainer->GetMoveType();

			IPhysicsObject *pPhysicsObject = m_hContainer->VPhysicsGetObject();
			if ( pPhysicsObject )
			{
				pPhysicsObject->SetShadow( 1e4, 1e4, false, false );
			}

			m_hContainer->SetParent(this, 0);
			m_hContainer->SetOwnerEntity(this);
			m_hContainer->SetMoveType( MOVETYPE_PUSH );
			m_hContainer->SetGroundEntity( NULL );
			m_hContainer->UpdatePhysicsShadowToCurrentPosition(0);
		}
		break;

	case CRATE_JEEP:
		m_hContainer = (CBaseAnimating*)CreateEntityByName( "prop_dynamic_override" );
		if ( m_hContainer )
		{
			m_hContainer->SetModel( "models/buggy.mdl" );
			m_hContainer->SetName( AllocPooledString("dropship_jeep") );

			m_hContainer->SetAbsOrigin( GetAbsOrigin() );//- Vector( 0, 0 , 25 ) );
			QAngle angles = GetAbsAngles();
			VMatrix mat, rot, result;
			MatrixFromAngles( angles, mat );
			MatrixBuildRotateZ( rot, -90 );
			MatrixMultiply( mat, rot, result );
			MatrixToAngles( result, angles );
			m_hContainer->SetAbsAngles( angles );

			m_hContainer->SetParent(this, 0);
			m_hContainer->SetOwnerEntity(this);
			m_hContainer->SetSolid( SOLID_VPHYSICS );
			m_hContainer->Spawn();
		}
		break;

	case CRATE_NONE:
	default:
		break;
	}

	// Setup our bbox
	if ( m_hContainer )
	{
		UTIL_SetSize( this, DROPSHIP_BBOX_CRATE_MIN, DROPSHIP_BBOX_CRATE_MAX );
		SetIdealActivity( (Activity)ACT_DROPSHIP_FLY_IDLE_CARGO );
	}
	else
	{
		UTIL_SetSize( this, DROPSHIP_BBOX_MIN, DROPSHIP_BBOX_MAX );
		SetIdealActivity( (Activity)ACT_DROPSHIP_FLY_IDLE_EXAGG );
	}

	m_cullBoxMins = WorldAlignMins() - Vector(300,300,200);
	m_cullBoxMaxs = WorldAlignMaxs() + Vector(300,300,200);
	BaseClass::Spawn();

	// Dropship ignores all damage, but can deal it to its carried container
	m_takedamage = m_bInvulnerable ? DAMAGE_NO : DAMAGE_YES;
	if ( m_bInvulnerable && m_hContainer )
	{
		m_hContainer->m_takedamage = DAMAGE_NO;
	}

	m_iHealth = 100;
	m_flFieldOfView = 0.5; // 60 degrees
	m_iBurstRounds = 15;

	InitBoneControllers();
	InitCustomSchedules();

	m_flMaxSpeed = DROPSHIP_MAX_SPEED;
	m_flMaxSpeedFiring = BASECHOPPER_MAX_FIRING_SPEED;
	m_hPickupTarget = NULL;
	m_hLandTarget = NULL;

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

	SetLandingState( LANDING_NO );

	if ( HasSpawnFlags( SF_DROPSHIP_WAIT_FOR_DROPOFF_INPUT ) )
	{
		m_bWaitForDropoffInput = true;
	}
	else
	{
		m_bWaitForDropoffInput = false;
	}
}

//-----------------------------------------------------------------------------
// Purpose: Called after spawning on map load or on a load from save game.
//-----------------------------------------------------------------------------
void CNPC_CombineDropship::Activate( void )
{
	BaseClass::Activate();

	if ( !m_sRollermineTemplateData )
	{
		m_sRollermineTemplateData = NULL_STRING;
		if ( m_sRollermineTemplate != NULL_STRING )
		{
			// This must be the first time we're activated, not a load from save game.
			// Look up the template in the template database.
			m_sRollermineTemplateData = Templates_FindByTargetName(STRING(m_sRollermineTemplate));
			if ( m_sRollermineTemplateData == NULL_STRING )
			{
				Warning( "npc_combinedropship %s: Rollermine Template %s not found!\n", STRING(GetEntityName()), STRING(m_sRollermineTemplate) );
			}
		}
	}
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void CNPC_CombineDropship::Precache( void )
{
	// Models
	PrecacheModel("models/combine_dropship.mdl");
	switch ( m_iCrateType )
	{
	case CRATE_SOLDIER:
		UTIL_PrecacheOther( "prop_dropship_container" );

		//
		// Precache the all templates that we are configured to spawn
		//
		for ( int i = 0; i < DROPSHIP_MAX_SOLDIERS; i++ )
		{
			if ( m_sNPCTemplate[i] != NULL_STRING )
			{
				if ( m_sNPCTemplateData[i] == NULL_STRING )
				{
					m_sNPCTemplateData[i] = Templates_FindByTargetName(STRING(m_sNPCTemplate[i]));
				}
				if ( m_sNPCTemplateData[i] != NULL_STRING )
				{
					CBaseEntity *pEntity = NULL;
					MapEntity_ParseEntity( pEntity, STRING(m_sNPCTemplateData[i]), NULL );
					if ( pEntity != NULL )
					{
						pEntity->Precache();
						UTIL_RemoveImmediate( pEntity );
					}
				}
				else
				{
					Warning( "npc_combinedropship %s: Template NPC %s not found!\n", STRING(GetEntityName()), STRING(m_sNPCTemplate[i]) );

					// Use the first template we've got
					m_sNPCTemplateData[i] = m_sNPCTemplateData[0];
				}

				// Make sure we've got a dustoff point for it
				if ( m_sDustoffPoints[i] == NULL_STRING )
				{
					Warning( "npc_combinedropship %s: Has no dustoff point for NPC %d!\n", STRING(GetEntityName()), i );
				}
			}
			else
			{
				m_sNPCTemplateData[i] = NULL_STRING;
			}
		}
		break;

	case CRATE_JEEP:
		PrecacheModel("models/buggy.mdl");
		break;

	default:
		break;
	}

	PrecacheScriptSound( "NPC_CombineDropship.RotorLoop" );
	PrecacheScriptSound( "NPC_CombineDropship.FireLoop" );
	PrecacheScriptSound( "NPC_CombineDropship.NearRotorLoop" );
	PrecacheScriptSound( "NPC_CombineDropship.OnGroundRotorLoop" );
	PrecacheScriptSound( "NPC_CombineDropship.DescendingWarningLoop" );
	PrecacheScriptSound( "NPC_CombineDropship.NearRotorLoop" );

	if ( m_sRollermineTemplate != NULL_STRING )
	{
		UTIL_PrecacheOther( "npc_rollermine" );
	}

	BaseClass::Precache();

}

//------------------------------------------------------------------------------
// Purpose : 
// Input   :
// Output  :
//------------------------------------------------------------------------------
void CNPC_CombineDropship::Flight( void )
{
	// Only run the flight model in some flight states
	bool bRunFlight = ( GetLandingState() == LANDING_NO || 
							GetLandingState() == LANDING_LEVEL_OUT || 
							GetLandingState() == LANDING_LIFTOFF ||
							GetLandingState() == LANDING_SWOOPING ||
							GetLandingState() == LANDING_DESCEND ||
							GetLandingState() == LANDING_HOVER_LEVEL_OUT ||
							GetLandingState() == LANDING_HOVER_DESCEND );

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

	float finspeed = 0;
	float swayspeed = 0;
	Vector vecImpulse = vec3_origin;

	//Adrian: Slowly lerp the orientation and position of the cargo into place...
	//We assume CRATE_NONE means the dropship just picked up some random phys object.
	if ( m_hContainer != NULL && ( m_iCrateType == CRATE_SOLDIER || m_iCrateType == CRATE_NONE ) )
	{
		if ( m_hContainer->GetLocalOrigin() != vec3_origin )
		{
			Vector vCurrentLocalOrigin = m_hContainer->GetLocalOrigin();
			Vector vLocalOrigin;
	
			VectorLerp( vCurrentLocalOrigin, vec3_origin, 0.05f, vLocalOrigin );
	
			m_hContainer->SetLocalOrigin( vLocalOrigin );
		}

		if ( m_hContainer->GetLocalAngles() != vec3_angle )
		{
			QAngle vCurrentLocalAngles = m_hContainer->GetLocalAngles();
			QAngle vLocalAngles;

			vLocalAngles = Lerp( 0.05f, vCurrentLocalAngles, vec3_angle );

			m_hContainer->SetLocalAngles( vLocalAngles );
		}
	}

	if ( bRunFlight )
	{
		if( GetFlags() & FL_ONGROUND )
		{
			// This would be really bad.
			SetGroundEntity( NULL );
		}

		// calc desired acceleration
		float dt = 1.0f;

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

		if ( m_lifeState == LIFE_DYING )
		{
			accelRate *= 5.0;
			maxSpeed *= 5.0;
		}

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

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

		//NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + deltaPos, 0, 255, 0, true, 0.1f );

		// 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 ( IsOnPathTrack() && GetLandingState() == LANDING_NO )
		{
			// 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 > 200 )
			{
				// 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 - 200) / 200.0f;
				flAmount = clamp( flAmount, 0, 1 );
				VectorMA( accel, flAmount * 200.0f, vecDelta, accel );
			}
		}

		// don't fall faster than 0.2G or climb faster than 2G
		accel.z = clamp( accel.z, 384 * 0.2, 384 * 2.0 );

		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 simulate 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;

		vecImpulse = m_flForce * up;
		
		if ( m_lifeState == LIFE_DYING )
		{
			vecImpulse.z = -38.4;  // 64ft/sec
		}
		else
		{
			vecImpulse.z -= 38.4;  // 32ft/sec
		}

		// Find our current velocity
		Vector vecVelDir = GetAbsVelocity();

		VectorNormalize( vecVelDir );

		if ( flDistFromPath > 100 )
		{
			// 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
		finspeed = m_flForce * DotProduct( vecVelDir, vecAccelDir );
		swayspeed = m_flForce * DotProduct( vecVelDir, right );
	}

	// Use the correct pose params for the state of our container
	int poseBodyAccel;
	int poseBodySway;
	if ( m_hContainer || GetLandingState() == LANDING_SWOOPING )
	{
		poseBodyAccel = m_poseCargo_Body_Accel;
		poseBodySway = m_poseCargo_Body_Sway;
		SetPoseParameter( m_poseBody_Accel, 0 );
		SetPoseParameter( m_poseBody_Sway, 0 );
	}
	else
	{
		poseBodyAccel = m_poseBody_Accel;
		poseBodySway = m_poseBody_Sway;
		SetPoseParameter( m_poseCargo_Body_Accel, 0 );
		SetPoseParameter( m_poseCargo_Body_Sway, 0 );
	}

	// If we're landing, deliberately tuck in the back end
	if ( GetLandingState() == LANDING_DESCEND || GetLandingState() == LANDING_TOUCHDOWN || 
		 GetLandingState() == LANDING_UNLOADING || GetLandingState() == LANDING_UNLOADED || IsHovering() )
	{
		finspeed = -60;
	}

	// Apply the acceleration blend to the fins
	float finAccelBlend = SimpleSplineRemapVal( finspeed, -60, 60, -1, 1 );
	float curFinAccel = GetPoseParameter( poseBodyAccel );
	curFinAccel = UTIL_Approach( finAccelBlend, curFinAccel, 0.1f );
	SetPoseParameter( poseBodyAccel, EdgeLimitPoseParameter( poseBodyAccel, curFinAccel ) );

	// Apply the spin sway to the fins
	float finSwayBlend = SimpleSplineRemapVal( swayspeed, -60, 60, -1, 1 );
	float curFinSway = GetPoseParameter( poseBodySway );
	curFinSway = UTIL_Approach( finSwayBlend, curFinSway, 0.1f );
	SetPoseParameter( poseBodySway, EdgeLimitPoseParameter( poseBodySway, curFinSway ) );

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

	//DevMsg("curFinAccel: %f, curFinSway: %f\n", curFinAccel, curFinSway );
}


//------------------------------------------------------------------------------
// Deals damage to what's behing carried
//------------------------------------------------------------------------------
int CNPC_CombineDropship::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo ) 
{
	// FIXME: To make this work for CRATE_STRIDER or CRATE_APC, we need to
	// add code to the strider + apc to make them not take double-damage from rockets
	// (owing to the blast hitting the crate + the dropship). See the dropship container
	// code above to see how to do it.
	if ( m_hContainer && !m_bInvulnerable )
	{
		if ( (inputInfo.GetDamageType() & DMG_AIRBOAT) || (m_iCrateType == CRATE_SOLDIER) )
		{
			m_hContainer->TakeDamage( inputInfo );
		}
	}

	// don't die
	return 0; 
}

//------------------------------------------------------------------------------
// Updates the facing direction
//------------------------------------------------------------------------------
void CNPC_CombineDropship::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 : 
// Input   :
// Output  :
//------------------------------------------------------------------------------
void CNPC_CombineDropship::InitializeRotorSound( void )
{
	CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();

	CPASAttenuationFilter filter( this );
	m_pRotorSound = controller.SoundCreate( filter, entindex(), "NPC_CombineDropship.RotorLoop" );
	m_pNearRotorSound = controller.SoundCreate( filter, entindex(), "NPC_CombineDropship.NearRotorLoop" );
	m_pRotorOnGroundSound = controller.SoundCreate( filter, entindex(), "NPC_CombineDropship.OnGroundRotorLoop" );
	m_pDescendingWarningSound = controller.SoundCreate( filter, entindex(), "NPC_CombineDropship.DescendingWarningLoop" );
	m_pCannonSound = controller.SoundCreate( filter, entindex(), "NPC_CombineDropship.FireLoop"  );

	// NOTE: m_pRotorSound is started up by the base class
	if ( m_pCannonSound )
	{
		controller.Play( m_pCannonSound, 0.0, 100 );
	}

	if ( m_pDescendingWarningSound )
	{
		controller.Play( m_pDescendingWarningSound, 0.0, 100 );
	}

	if ( m_pRotorOnGroundSound )
	{
		controller.Play( m_pRotorOnGroundSound, 0.0, 100 );
	}
	
	if ( m_pNearRotorSound )
	{
		controller.Play( m_pNearRotorSound, 0.0, 100 );
	}

	m_engineThrust = 1.0f;

	BaseClass::InitializeRotorSound();
}


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

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

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

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

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

	BaseClass::StopLoopingSounds();
}


//------------------------------------------------------------------------------
// Updates the rotor wash volume
//------------------------------------------------------------------------------
void CNPC_CombineDropship::UpdateRotorWashVolume( CSoundPatch *pRotorSound, float flVolume, float flDeltaTime )
{
	if ( !pRotorSound )
		return;

	CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
	float flVolDelta = flVolume - controller.SoundGetVolume( pRotorSound );
	if ( flVolDelta )
	{
		// We can change from 0 to 1 in 3 seconds. 
		// Figure out how many seconds flVolDelta will take.
		float flRampTime = fabs( flVolDelta ) * flDeltaTime; 
		controller.SoundChangeVolume( pRotorSound, flVolume, flRampTime );
	}
}


//------------------------------------------------------------------------------
// Updates the rotor wash volume
//------------------------------------------------------------------------------
void CNPC_CombineDropship::UpdateRotorWashVolume()
{
	float flNearFactor = 0.0f; 
	CBaseEntity *pPlayer = UTIL_PlayerByIndex( 1 );
	if (pPlayer)
	{
		float flDist = pPlayer->GetAbsOrigin().DistTo( GetAbsOrigin() );
		flDist = clamp( flDist, DROPSHIP_NEAR_SOUND_MIN_DISTANCE, DROPSHIP_NEAR_SOUND_MAX_DISTANCE );
		flNearFactor = RemapVal( flDist, DROPSHIP_NEAR_SOUND_MIN_DISTANCE, DROPSHIP_NEAR_SOUND_MAX_DISTANCE, 1.0f, 0.0f );
	}

	if ( m_pRotorSound )
	{
		UpdateRotorWashVolume( m_pRotorSound, m_engineThrust * GetRotorVolume() * (1.0f - flNearFactor), 3.0f );
	}

	if ( m_pNearRotorSound )
	{
		UpdateRotorWashVolume( m_pNearRotorSound, m_engineThrust * GetRotorVolume() * flNearFactor, 3.0f );
	}
}


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

	float rotorPitch = 0.2 + m_engineThrust * 0.8;
	if ( m_pRotorSound )
	{
		controller.SoundChangePitch( m_pRotorSound, iPitch + rotorPitch, 0.1 );
	}

	if ( m_pNearRotorSound )
	{
		controller.SoundChangePitch( m_pNearRotorSound, iPitch + rotorPitch, 0.1 );
	}

	if (m_pRotorOnGroundSound)
	{
		controller.SoundChangePitch( m_pRotorOnGroundSound, iPitch + rotorPitch, 0.1 );
	}

	UpdateRotorWashVolume();
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : iSoldiers - 
//-----------------------------------------------------------------------------
void CNPC_CombineDropship::CalculateSoldierCount( int iSoldiers )
{
	if ( m_iCrateType >= 0 ) 
	{
		m_soldiersToDrop = clamp( iSoldiers, 0, DROPSHIP_MAX_SOLDIERS );
	}
	else
	{
		m_soldiersToDrop = 0;
	}
}

//------------------------------------------------------------------------------
// Purpose : Leave crate being carried
// Input   :
// Output  :
//------------------------------------------------------------------------------
void CNPC_CombineDropship::InputLandLeave( inputdata_t &inputdata )
{
	CalculateSoldierCount( inputdata.value.Int() );
	m_leaveCrate = true;
	LandCommon();
}

//------------------------------------------------------------------------------
// Purpose : Take crate being carried to next point
// Input   :
// Output  :
//------------------------------------------------------------------------------
void CNPC_CombineDropship::InputLandTake( inputdata_t &inputdata )
{
	CalculateSoldierCount( inputdata.value.Int() );
	m_leaveCrate = false;
	LandCommon();
}

//------------------------------------------------------------------------------
// Purpose : 
// Input   : bHover - If true, means we're landing on a hover point, not the ground
// Output  :
//------------------------------------------------------------------------------
void CNPC_CombineDropship::LandCommon( bool bHover )
{
	// If we don't have a crate, we're not able to land
	if ( !m_hContainer && !bHover )
		return;

	//DevMsg( "Landing\n" );

	if( bHover )
	{
		SetLandingState( LANDING_HOVER_LEVEL_OUT );
	}
	else
	{
		SetLandingState( LANDING_LEVEL_OUT );
	}

	SetLocalAngularVelocity( vec3_angle );

	// Do we have a land target?
	if ( m_iszLandTarget != NULL_STRING )
	{
		CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, m_iszLandTarget );
		if ( !pTarget )
		{
			Warning("npc_combinedropship %s couldn't find land target named %s\n", STRING(GetEntityName()), STRING(m_iszLandTarget) );
			return;
		}

		// Start heading to the point
		m_hLandTarget = pTarget;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_CombineDropship::InputSetLandTarget( inputdata_t &inputdata )
{
	m_iszLandTarget = inputdata.value.StringID();
}

//------------------------------------------------------------------------------
// Purpose : Drop mine inputs... done this way so generic path_corners can be used
// Input   :
// Output  :
//------------------------------------------------------------------------------
void CNPC_CombineDropship::InputDropMines( inputdata_t &inputdata )
{
	m_totalMinesToDrop = inputdata.value.Int();
	if ( m_totalMinesToDrop >= 1 )	// catch bogus values being passed in
	{
		m_bDropMines = true;
	}
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_CombineDropship::InputDropStrider( inputdata_t &inputdata )
{
	if ( !m_hContainer || !FClassnameIs( m_hContainer, "npc_strider" ) )
	{
		Warning("npc_combinedropship %s was told to drop Strider, but isn't carrying one!\n", STRING(GetEntityName()) );
		return;
	}

	QAngle angles = GetAbsAngles();

	angles.x = 0.0;
	angles.z = 0.0;

	m_hContainer->SetParent(NULL, 0);
	m_hContainer->SetOwnerEntity(NULL);
	m_hContainer->SetAbsAngles( angles );
	m_hContainer->SetAbsVelocity( vec3_origin );

	m_hContainer = NULL;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_CombineDropship::InputDropAPC( inputdata_t &inputdata )
{
	if ( !m_hContainer || !FClassnameIs( m_hContainer, "prop_vehicle_apc" ) )
	{
		Warning("npc_combinedropship %s was told to drop APC, but isn't carrying one!\n", STRING(GetEntityName()) );
		return;
	}

	m_hContainer->SetParent(NULL, 0);
//	m_hContainer->SetOwnerEntity(NULL);

	Vector vecAbsVelocity = GetAbsVelocity();
	if ( vecAbsVelocity.z > 0 )
	{
		vecAbsVelocity.z = 0.0f;
	}
	if ( m_hContainer->GetHealth() > 0 )
	{
		vecAbsVelocity = vec3_origin;
	}

	m_hContainer->SetAbsVelocity( vecAbsVelocity );
	m_hContainer->SetMoveType( (MoveType_t)m_iContainerMoveType );

	// If the container has a physics object, remove it's shadow
	IPhysicsObject *pPhysicsObject = m_hContainer->VPhysicsGetObject();
	if ( pPhysicsObject )
	{
		pPhysicsObject->RemoveShadowController();
	}

	UTIL_SetSize( this, DROPSHIP_BBOX_MIN, DROPSHIP_BBOX_MAX );

	m_hContainer = NULL;
	m_OnFinishedDropoff.FireOutput( this, this );
	SetLandingState( LANDING_NO );
	m_hLandTarget = NULL;
}


//-----------------------------------------------------------------------------
// Drop the soldier container
//-----------------------------------------------------------------------------
void CNPC_CombineDropship::DropSoldierContainer( )
{
	m_hContainer->SetParent(NULL, 0);
//	m_hContainer->SetOwnerEntity(NULL);

	Vector vecAbsVelocity = GetAbsVelocity();
	if ( vecAbsVelocity.z > 0 )
	{
		vecAbsVelocity.z = 0.0f;
	}

	m_hContainer->SetAbsVelocity( vecAbsVelocity );
	m_hContainer->SetMoveType( MOVETYPE_VPHYSICS );

	// If we have a troop in the process of exiting, kill him.
	// We do this to avoid having to solve the AI problems resulting from it.
	if ( m_hLastTroopToLeave )
	{
		CTakeDamageInfo dmgInfo( this, this, vec3_origin, m_hContainer->GetAbsOrigin(), m_hLastTroopToLeave->GetMaxHealth(), DMG_GENERIC );
		m_hLastTroopToLeave->TakeDamage( dmgInfo );
	}

	// If the container has a physics object, remove it's shadow
	IPhysicsObject *pPhysicsObject = m_hContainer->VPhysicsGetObject();
	if ( pPhysicsObject )
	{
		pPhysicsObject->RemoveShadowController();
		pPhysicsObject->SetVelocity( &vecAbsVelocity, &vec3_origin );
	}

	UTIL_SetSize( this, DROPSHIP_BBOX_MIN, DROPSHIP_BBOX_MAX );

	m_hContainer = NULL;
	SetLandingState( LANDING_NO );
	m_hLandTarget = NULL;

	if ( m_bHasDroppedOff )
	{
		m_OnContainerShotDownAfterDropoff.FireOutput( this, this );
	}
	else
	{
		int iTroopsNotUnloaded = (m_soldiersToDrop - m_iCurrentTroopExiting);
		if ( g_debug_dropship.GetInt() )
		{
			Msg("Dropship died, troops not unloaded: %d\n", iTroopsNotUnloaded );
		}

		m_OnContainerShotDownBeforeDropoff.Set( iTroopsNotUnloaded, this, this );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Pick up a specified object
// Input  : &inputdata - 
//-----------------------------------------------------------------------------
void CNPC_CombineDropship::InputPickup( inputdata_t &inputdata )
{
	// Can't pickup if we're already carrying something
	if ( m_hContainer )
	{
		Warning("npc_combinedropship %s was told to pickup, but is already carrying something.\n", STRING(GetEntityName()) );
		return;
	}

	string_t iszTargetName = inputdata.value.StringID();
	if ( iszTargetName == NULL_STRING )
	{
		Warning("npc_combinedropship %s tried to pickup with no specified pickup target.\n", STRING(GetEntityName()) );
		return;
	}
	CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, iszTargetName );
	if ( !pTarget )
	{
		Warning("npc_combinedropship %s couldn't find pickup target named %s\n", STRING(GetEntityName()), STRING(iszTargetName) );
		return;
	}

	// Start heading to the point
	m_hPickupTarget = pTarget;

	m_bHasDroppedOff = false;

	// Disable collisions to my target
	m_hPickupTarget->SetOwnerEntity(this);
	if ( m_NPCState == NPC_STATE_IDLE )
	{
		SetState( NPC_STATE_ALERT );
	}
	SetLandingState( LANDING_SWOOPING );
	m_flLandingSpeed = GetAbsVelocity().Length();

	UpdatePickupNavigation();
}

//-----------------------------------------------------------------------------
// Purpose: Set the range of the container's gun
// Input  : &inputdata - 
//-----------------------------------------------------------------------------
void CNPC_CombineDropship::InputSetGunRange( inputdata_t &inputdata )
{
	m_flGunRange = inputdata.value.Float();
}


//------------------------------------------------------------------------------
// Set the landing state
//------------------------------------------------------------------------------
void CNPC_CombineDropship::SetLandingState( LandingState_t landingState )
{
	if ( landingState == m_iLandState )
		return;

	if ( m_pDescendingWarningSound )
	{
		CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
		if ( ( landingState == LANDING_DESCEND ) || ( landingState == LANDING_TOUCHDOWN ) || ( landingState == LANDING_UNLOADING ) || ( landingState == LANDING_UNLOADED ) || ( landingState == LANDING_HOVER_DESCEND ) )
		{
			controller.SoundChangeVolume( m_pDescendingWarningSound, m_bSuppressSound ? 0.0f : 1.0f, 0.3f );
		}
		else
		{
			controller.SoundChangeVolume( m_pDescendingWarningSound, 0.0f, 0.0f );
		}
	}
	
	m_iLandState = landingState;
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
bool CNPC_CombineDropship::IsHovering()
{
	bool bIsHovering = false;

	if( GetLandingState() > LANDING_START_HOVER && GetLandingState() < LANDING_END_HOVER )
	{
		bIsHovering = true;
	}

	return bIsHovering;
}

//------------------------------------------------------------------------------
// Update the ground rotorwash volume
//------------------------------------------------------------------------------
void CNPC_CombineDropship::UpdateGroundRotorWashSound( float flAltitude )
{
	float flVolume = RemapValClamped( flAltitude, DROPSHIP_GROUND_WASH_MIN_ALTITUDE, DROPSHIP_GROUND_WASH_MAX_ALTITUDE, 1.0f, 0.0f );
	UpdateRotorWashVolume( m_pRotorOnGroundSound, flVolume * GetRotorVolume(), 0.5f );
}


//------------------------------------------------------------------------------
// Purpose : 
// Input   :
// Output  :
//------------------------------------------------------------------------------
void CNPC_CombineDropship::PrescheduleThink( void )
{	
	BaseClass::PrescheduleThink();

	// "npc_kill" destroys our container
	if (m_debugOverlays & OVERLAY_NPC_KILL_BIT)
	{
		if ( m_hContainer )
		{
			CTakeDamageInfo dmgInfo( this, this, vec3_origin, vec3_origin, 1000, DMG_BLAST );
			m_hContainer->TakeDamage( dmgInfo );
		}
	}

	// Update the ground rotorwash volume
	float flAltitude = GetAltitude();
	UpdateGroundRotorWashSound( flAltitude );

	// keep track of think time deltas for burn calc below
	float dt = gpGlobals->curtime - m_flLastTime;
	m_flLastTime = gpGlobals->curtime;

	switch( GetLandingState() )
	{
	case LANDING_NO:
		{
			if ( IsActivityFinished() && (GetActivity() != ACT_DROPSHIP_FLY_IDLE_EXAGG && GetActivity() != ACT_DROPSHIP_FLY_IDLE_CARGO) )
			{
				if ( m_hContainer )
				{
					SetIdealActivity( (Activity)ACT_DROPSHIP_FLY_IDLE_CARGO );
				}
				else
				{
					SetIdealActivity( (Activity)ACT_DROPSHIP_FLY_IDLE_EXAGG );
				}
			}

			DoRotorWash();
		}
		break;

	case LANDING_LEVEL_OUT:
	case LANDING_HOVER_LEVEL_OUT:
		{
			// Approach the drop point
			Vector vecToTarget = (GetDesiredPosition() - GetAbsOrigin());
			float flDistance = vecToTarget.Length();

			// Are we there yet?
			float flSpeed = GetAbsVelocity().Length();
			if ( flDistance < 70 && flSpeed < 100 )
			{
				m_flLandingSpeed = flSpeed;

				if( IsHovering() )
				{
					SetLandingState( LANDING_HOVER_DESCEND );
				}
				else
				{
					SetLandingState( LANDING_DESCEND );
				}

				// save off current angles so we can work them out over time
				QAngle angles = GetLocalAngles();
				m_existPitch = angles.x;
				m_existRoll = angles.z;
			}

			DoRotorWash();
		}
		break;

	case LANDING_DESCEND:
	case LANDING_HOVER_DESCEND:
		{
			/*
			if ( IsActivityFinished() && GetActivity() != ACT_DROPSHIP_DESCEND_IDLE )
			{
				SetActivity( (Activity)ACT_DROPSHIP_DESCEND_IDLE );
			}
			*/

			if( IsHovering() && m_hLandTarget != NULL )
			{
				// We're trying to hover above an arbitrary point, not above the ground. 
				// Recompute flAltitude to indicate the vertical distance from the land 
				// target so that touchdown is correctly detected.
				flAltitude = GetAbsOrigin().z - m_hLandTarget->GetAbsOrigin().z;
			}

			// Orient myself to the desired direction
			bool bStillOrienting = false;
			Vector targetDir;
			if ( m_hLandTarget )
			{
				// We've got a land target, so match it's orientation
				AngleVectors( m_hLandTarget->GetAbsAngles(), &targetDir );
			}
			else
			{
				// No land target. 
				targetDir = GetDesiredPosition() - GetAbsOrigin();
			}

			// Don't unload until we're facing the way the dropoff point specifies
			float flTargetYaw = UTIL_VecToYaw( targetDir );
			float flDeltaYaw = UTIL_AngleDiff( flTargetYaw, GetAbsAngles().y );
			if ( fabs(flDeltaYaw) > 5 )
			{
				bStillOrienting = true;
			}

			// Ensure we land on the drop point. Stop dropping if we're still turning.
			Vector vecToTarget = (GetDesiredPosition() - GetAbsOrigin());
			float flDistance = vecToTarget.Length();
			float flRampedSpeed = m_flLandingSpeed * (flDistance / 70);
			Vector vecVelocity = (flRampedSpeed / flDistance) * vecToTarget;
			
#define MAX_LAND_VEL	-300.0f
#define MIN_LAND_VEL	-75.0f
#define ALTITUDE_CAP	512.0f

			float flFactor = MIN( 1.0,  flAltitude / ALTITUDE_CAP );
			float flDescendVelocity = MIN( -75, MAX_LAND_VEL * flFactor );

			vecVelocity.z = flDescendVelocity;

			SetAbsVelocity( vecVelocity );

			if ( flAltitude < 72 )
			{
				QAngle angles = GetLocalAngles();

				// Level out quickly.
				angles.x = UTIL_Approach( 0.0, angles.x, 0.2 );
				angles.z = UTIL_Approach( 0.0, angles.z, 0.2 );

				SetLocalAngles( angles );
			}
			else
			{
				// randomly move as if buffeted by ground effects
				// gently flatten ship from starting pitch/yaw
				m_existPitch = UTIL_Approach( 0.0, m_existPitch, 1 );
				m_existRoll = UTIL_Approach( 0.0, m_existRoll, 1 );

				QAngle angles = GetLocalAngles();
				angles.x = m_existPitch + ( sin( gpGlobals->curtime * 3.5f ) * DROPSHIP_MAX_LAND_TILT );
				angles.z = m_existRoll + ( sin( gpGlobals->curtime * 3.75f ) * DROPSHIP_MAX_LAND_TILT );
				SetLocalAngles( angles );
			}

			DoRotorWash();

			// place danger sounds 1 foot above ground to get troops to scatter if they are below dropship
			Vector vecBottom = GetAbsOrigin();
			vecBottom.z += WorldAlignMins().z;
			Vector vecSpot = vecBottom + Vector(0, 0, -1) * (flAltitude - 12 );
			CSoundEnt::InsertSound( SOUND_DANGER, vecSpot, 400, 0.1, this, 0 );
			CSoundEnt::InsertSound( SOUND_PHYSICS_DANGER, vecSpot, 400, 0.1, this, 1 );
//			NDebugOverlay::Cross3D( vecSpot, -Vector(4,4,4), Vector(4,4,4), 255, 0, 255, false, 10.0f );

			// now check to see if player is below us, if so, cause heat damage to them (i.e. get them to move)
			trace_t tr;
			Vector vecBBoxMin = CRATE_BBOX_MIN;		// use flat box for check
			vecBBoxMin.z = -5;
			Vector vecBBoxMax = CRATE_BBOX_MAX;
			vecBBoxMax.z = 5;
			Vector pEndPoint = vecBottom + Vector(0, 0, -1) * ( flAltitude - 12 );
			AI_TraceHull( vecBottom, pEndPoint, vecBBoxMin, vecBBoxMax, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );

			if ( tr.fraction < 1.0f )
			{
				// Damage anything that's blocking me
				if ( tr.m_pEnt && tr.m_pEnt->m_takedamage != DAMAGE_NO )
				{
					CTakeDamageInfo info( this, this, 20 * dt, DMG_BURN );
					tr.m_pEnt->TakeDamage( info );
				}
			}

			if ( !bStillOrienting && ((flAltitude <= 0.5f) || (m_iCrateType == CRATE_APC)) )
			{
				if( IsHovering() )
				{
					SetAbsVelocity( vec3_origin );
					SetLandingState( LANDING_HOVER_TOUCHDOWN );
				}
				else
				{
					SetLandingState( LANDING_TOUCHDOWN );
				}

				// upon landing, make sure ship is flat
				QAngle angles = GetLocalAngles();
				angles.x = 0;
				angles.z = 0;
				SetLocalAngles( angles );

				// TODO: Release cargo anim
				//SetActivity( (Activity)ACT_DROPSHIP_DESCEND_IDLE );
				return;
			}
		}
		break;

	case LANDING_TOUCHDOWN:
	case LANDING_HOVER_TOUCHDOWN:
		{
			/*
			if ( IsActivityFinished() && ( GetActivity() != ACT_DROPSHIP_DESCEND_IDLE ) )
			{
				SetActivity( (Activity)ACT_DROPSHIP_DESCEND_IDLE );
			}
			*/

			// Wait here if we're supposed to wait for the dropoff input
			if ( m_bWaitForDropoffInput )
				return;

			// Wait here till designer tells us to get moving again.
			if ( IsHovering() )
				return;

			SetLandingState( LANDING_UNLOADING );

			// If we're dropping off troops, we'll wait for them to be done. 
			// Otherwise, just pause on the ground for a few seconds and then leave.
			if ( m_soldiersToDrop && m_hContainer)
			{
				m_flTimeTakeOff = 0;
				m_flNextTroopSpawnAttempt = 0;

				// Open our container
				m_hContainer->SetSequence( m_hContainer->LookupSequence("open_idle") );

				// Start unloading troops
				m_iCurrentTroopExiting = 0;
				SpawnTroop();
			}
			else
			{
				float flHoverTime = ( m_iCrateType >= 0 ) ? DROPSHIP_LANDING_HOVER_TIME : 0.5f;
				m_flTimeTakeOff = gpGlobals->curtime + flHoverTime;
			}
		}
		break;

	case LANDING_UNLOADING:
		{
			// If we've got no specified takeoff time, we're still waiting for troops to exit. Idle.
			if ( !m_flTimeTakeOff )
			{
				float idleVolume = 0.2f;
				m_engineThrust = UTIL_Approach( idleVolume, m_engineThrust, 0.04f );
				if ( m_engineThrust > idleVolume ) 
				{
					// Make sure we're kicking up dust/water as long as engine thrust is up
					DoRotorWash();				
				}

				// If we've lost the last troop who was leaving, he probably got killed during dustoff.
				if ( !m_hLastTroopToLeave || !m_hLastTroopToLeave->IsAlive() )
				{
					// If we still have troops onboard, spawn the next one
					if ( m_iCurrentTroopExiting < m_soldiersToDrop )
					{
						SpawnTroop();
					}
					else
					{
						// We're out of troops, time to leave
						m_flTimeTakeOff = gpGlobals->curtime + 0.5;
					}
				}
			}
			else
			{
				if( gpGlobals->curtime > m_flTimeTakeOff )
				{
					SetLandingState( LANDING_LIFTOFF );
					SetActivity( (Activity)ACT_DROPSHIP_LIFTOFF );
					m_engineThrust = 1.0f;			// ensure max volume once we're airborne
					if ( m_bIsFiring )
					{
						StopCannon();				// kill cannon sounds if they are on
					}

					// detach container from ship
					if ( m_hContainer && m_leaveCrate )
					{
						m_hContainer->SetParent(NULL);
						m_hContainer->SetMoveType( (MoveType_t)m_iContainerMoveType );

						Vector vecAbsVelocity( 0, 0, GetAbsVelocity().z );
						if ( vecAbsVelocity.z > 0 )
						{
							vecAbsVelocity.z = 0.0f;
						}

						m_hContainer->SetAbsVelocity( vecAbsVelocity );

						// If the container has a physics object, remove it's shadow
						IPhysicsObject *pPhysicsObject = m_hContainer->VPhysicsGetObject();
						if ( pPhysicsObject )
						{
							pPhysicsObject->RemoveShadowController();
							pPhysicsObject->SetVelocity( &vecAbsVelocity, &vec3_origin );
						}

						m_hContainer = NULL;
						UTIL_SetSize( this, DROPSHIP_BBOX_MIN, DROPSHIP_BBOX_MAX );
					}
				}
				else if ( (m_flTimeTakeOff - gpGlobals->curtime) < 0.5f )
				{
					// Manage engine wash and volume
					m_engineThrust = UTIL_Approach( 1.0f, m_engineThrust, 0.1f );
					DoRotorWash();
				}
			}
		}
		break;

	case LANDING_LIFTOFF:
		{
			// Once we're off the ground, start flying again
			if ( flAltitude > 120 )		
			{
				SetLandingState( LANDING_NO );
				m_hLandTarget = NULL;
				m_bHasDroppedOff = true;
				m_OnFinishedDropoff.FireOutput( this, this );
			}

			if ( m_hContainer )
			{
				m_hContainer->SetSequence( m_hContainer->LookupSequence("close_idle") );
			}
		}
		break;

	case LANDING_SWOOPING:
		{
			// Did we lose our pickup target?
			if ( !m_hPickupTarget )
			{
				SetLandingState( LANDING_NO );
			}
			else
			{
				// Decrease altitude and speed to hit the target point.
				Vector vecToTarget = (GetDesiredPosition() - GetAbsOrigin());
				float flDistance = vecToTarget.Length();

				// Start cheating when we get near it
				if ( flDistance < 50 )
				{
					/*
					if ( flDistance > 10 )
					{
						// Cheat and ensure we touch the target
						float flSpeed = GetAbsVelocity().Length();
						Vector vecVelocity = vecToTarget;
						VectorNormalize( vecVelocity );
						SetAbsVelocity( vecVelocity * min(flSpeed,flDistance) );
					}
					else
					*/
					{
						// Grab the target
						m_hContainer = m_hPickupTarget;
						m_hPickupTarget = NULL;
						m_iContainerMoveType = m_hContainer->GetMoveType();
						if ( m_bInvulnerable && m_hContainer )
						{
							m_hContainer->m_takedamage = DAMAGE_NO;
						}

						// If the container has a physics object, move it to shadow
						IPhysicsObject *pPhysicsObject = m_hContainer->VPhysicsGetObject();
						if ( pPhysicsObject )
						{
							pPhysicsObject->EnableMotion( true );
							pPhysicsObject->SetShadow( 1e4, 1e4, false, false );
							pPhysicsObject->UpdateShadow( GetAbsOrigin(), GetAbsAngles(), false, 0 );
						}

						m_hContainer->SetParent(this, 0);
						m_hContainer->SetMoveType( MOVETYPE_PUSH );
						m_hContainer->SetGroundEntity( NULL );

						m_OnFinishedPickup.FireOutput( this, this );
						SetLandingState( LANDING_NO );
					}
				}
			}

			DoRotorWash();
		}
		break;
	}

	if ( !(CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI) )
	{
		DoCombatStuff();
	}

	if ( GetActivity() != GetIdealActivity() )
	{
		//DevMsg( "setactivity" );
		SetActivity( GetIdealActivity() );
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
#define DROPSHIP_WASH_ALTITUDE 1024.0

void CNPC_CombineDropship::DoRotorWash( void )
{
	Vector	vecForward;
	GetVectors( &vecForward, NULL, NULL );

	Vector vecRotorHub = GetAbsOrigin() + vecForward * -64;

	DrawRotorWash( DROPSHIP_WASH_ALTITUDE, vecRotorHub );
}


//------------------------------------------------------------------------------
// Purpose : Spawn the next NPC in our template list
//------------------------------------------------------------------------------
void CNPC_CombineDropship::SpawnTroop( void )
{
	if ( !m_hContainer )
	{
		// We're done, take off.
		m_flTimeTakeOff = gpGlobals->curtime + 0.5;
		return;
	}

	// Are we fully unloaded? If so, take off. Otherwise, tell the next troop to exit.
	if ( m_iCurrentTroopExiting >= m_soldiersToDrop || m_sNPCTemplateData[m_iCurrentTroopExiting] == NULL_STRING )
	{
		// We're done, take off.
		m_flTimeTakeOff = gpGlobals->curtime + 0.5;
		return;
	}

	m_hLastTroopToLeave = NULL;

	// Not time to try again yet?
	if ( m_flNextTroopSpawnAttempt > gpGlobals->curtime )
		return;

	// HACK: This is a nasty piece of work. We want to make sure the deploy end is clear, and has enough
	// room with our deploying NPC, but we don't want to create the NPC unless it's clear, and we don't
	// know how much room he needs without spawning him. 
	// So, because we know that we only ever spawn combine soldiers at the moment, we'll just use their hull.
	// HACK: Add some bloat because the endpoint isn't perfectly aligned with NPC end origin
	Vector vecNPCMins = NAI_Hull::Mins( HULL_HUMAN ) - Vector(4,4,4);
	Vector vecNPCMaxs = NAI_Hull::Maxs( HULL_HUMAN ) + Vector(4,4,4);

	// Scare NPCs away from our deploy endpoint to keep them away
	Vector vecDeployEndPoint;
	QAngle vecDeployEndAngles;
	m_hContainer->GetAttachment( m_iAttachmentTroopDeploy, vecDeployEndPoint, vecDeployEndAngles );
	vecDeployEndPoint = GetDropoffFinishPosition( vecDeployEndPoint, NULL, vecNPCMins, vecNPCMaxs );
	CSoundEnt::InsertSound( SOUND_DANGER, vecDeployEndPoint, 120.0f, 2.0f, this );

	// Make sure there are no NPCs on the spot
	trace_t tr;
	CTraceFilterOnlyNPCsAndPlayer filter( this, COLLISION_GROUP_NONE );
	AI_TraceHull( vecDeployEndPoint, vecDeployEndPoint, vecNPCMins, vecNPCMaxs, MASK_SOLID, &filter, &tr );
	if ( tr.m_pEnt )
	{
		if ( g_debug_dropship.GetInt() == 2 )
		{
			NDebugOverlay::Box( vecDeployEndPoint, vecNPCMins, vecNPCMaxs, 255,0,0, 64, 0.5 );
		}

		m_flNextTroopSpawnAttempt = gpGlobals->curtime + 1;
		return;
	}

	if ( g_debug_dropship.GetInt() == 2 )
	{
		NDebugOverlay::Box( vecDeployEndPoint, vecNPCMins, vecNPCMaxs, 0,255,0, 64, 0.5 );
	}

	// Get the spawn point inside the container
	Vector vecSpawnOrigin;
	QAngle vecSpawnAngles;
	m_hContainer->GetAttachment( m_iAttachmentDeployStart, vecSpawnOrigin, vecSpawnAngles );

	// Spawn the templated NPC
	CBaseEntity *pEntity = NULL;
	MapEntity_ParseEntity( pEntity, STRING(m_sNPCTemplateData[m_iCurrentTroopExiting]), NULL );

	// Increment troop count
	m_iCurrentTroopExiting++;

	if ( !pEntity )
	{
		Warning("Dropship could not create template NPC\n" );
		return;
	}
	CAI_BaseNPC	*pNPC = pEntity->MyNPCPointer();
	Assert( pNPC );

	// Spawn an entity blocker.
	CBaseEntity *pBlocker = CEntityBlocker::Create( vecDeployEndPoint, vecNPCMins, vecNPCMaxs, pNPC, true );
	g_EventQueue.AddEvent( pBlocker, "Kill", 2.5, this, this );
	if ( g_debug_dropship.GetInt() == 2 )
	{
		NDebugOverlay::Box( vecDeployEndPoint, vecNPCMins, vecNPCMaxs, 255, 255, 255, 64, 2.5 );
	}

	// Ensure our NPCs are standing upright
	vecSpawnAngles[PITCH] = vecSpawnAngles[ROLL] = 0;

	// Move it to the container spawnpoint
	pNPC->SetAbsOrigin( vecSpawnOrigin );
	pNPC->SetAbsAngles( vecSpawnAngles );
	DispatchSpawn( pNPC );
	pNPC->m_NPCState = NPC_STATE_IDLE;
	pNPC->Activate();

	// Spawn a scripted sequence entity to make the NPC run out of the dropship
	CAI_ScriptedSequence *pSequence = (CAI_ScriptedSequence*)CreateEntityByName( "scripted_sequence" );
	pSequence->KeyValue( "m_iszEntity", STRING(pNPC->GetEntityName()) );
	pSequence->KeyValue( "m_iszPlay", "Dropship_Deploy" );
	pSequence->KeyValue( "m_fMoveTo", "4" );	// CINE_MOVETO_TELEPORT
	pSequence->KeyValue( "OnEndSequence", UTIL_VarArgs("%s,NPCFinishDustoff,%s,0,-1", STRING(GetEntityName()), STRING(pNPC->GetEntityName())) );
	pSequence->SetAbsOrigin( vecSpawnOrigin );
	pSequence->SetAbsAngles( vecSpawnAngles );
	pSequence->AddSpawnFlags( SF_SCRIPT_NOINTERRUPT | SF_SCRIPT_HIGH_PRIORITY | SF_SCRIPT_OVERRIDESTATE );
	pSequence->Spawn();
	pSequence->Activate();
	variant_t emptyVariant;
	pSequence->AcceptInput( "BeginSequence", this, this, emptyVariant, 0 );

	m_hLastTroopToLeave = pNPC;
}

//-----------------------------------------------------------------------------
// Purpose: Returns a safe position above/below the specified origin for the NPC to finish it's dropoff on
// Input  : vecOrigin - 
//-----------------------------------------------------------------------------
Vector CNPC_CombineDropship::GetDropoffFinishPosition( Vector vecOrigin, CAI_BaseNPC *pNPC, Vector vecMins, Vector vecMaxs )
{
	// Use the NPC's if they're specified
	if ( pNPC )
	{
		vecMins = NAI_Hull::Mins( pNPC->GetHullType() );
		vecMaxs = NAI_Hull::Maxs( pNPC->GetHullType() );
	}

	trace_t tr;
	AI_TraceHull( vecOrigin + Vector(0,0,32), vecOrigin, vecMins, vecMaxs, MASK_SOLID, pNPC, COLLISION_GROUP_NONE, &tr );
	if ( tr.fraction < 1.0 )
	{
		if ( g_debug_dropship.GetInt() == 1 )
		{
			NDebugOverlay::Box( vecOrigin, vecMins, vecMaxs, 255,0,0, 8, 4.0 );
		}

		// Try and find the ground
		AI_TraceHull( vecOrigin + Vector(0,0,32), vecOrigin, vecMins, vecMaxs, MASK_SOLID, pNPC, COLLISION_GROUP_NONE, &tr );
		if ( !tr.startsolid )
			return (tr.endpos + Vector(0,0,1));
	}
	else if ( g_debug_dropship.GetInt() == 1 )
	{
		NDebugOverlay::Box( vecOrigin, vecMins, vecMaxs, 0,255,0, 8, 4.0 );
	}

	return vecOrigin;
}

//-----------------------------------------------------------------------------
// Purpose: A troop we dropped of has now finished the scripted sequence
// Input  : &inputdata - 
//-----------------------------------------------------------------------------
void CNPC_CombineDropship::InputNPCFinishDustoff( inputdata_t &inputdata )
{
	CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, inputdata.value.StringID(), NULL, inputdata.pActivator, inputdata.pCaller );
	if ( !pEnt )
		return;

	CAI_BaseNPC *pNPC = pEnt->MyNPCPointer();
	Assert( pNPC );

	Vector vecOrigin = GetDropoffFinishPosition( pNPC->GetAbsOrigin(), pNPC, vec3_origin, vec3_origin );
	pNPC->SetAbsOrigin( vecOrigin );

	// Do we have a dustoff point?
	CBaseEntity *pDustoff = NULL;
	if ( m_sDustoffPoints[m_iCurrentTroopExiting-1] != NULL_STRING )
	{
		pDustoff = gEntList.FindEntityByName( NULL, m_sDustoffPoints[m_iCurrentTroopExiting-1] );
		if ( !pDustoff )
		{
			Warning("npc_combinedropship %s couldn't find dustoff target named %s\n", STRING(GetEntityName()), STRING(m_sDustoffPoints[m_iCurrentTroopExiting-1]) );
		}
	}

	if ( !pDustoff )
	{
		// Make a move away sound to scare the combine away from this point
		CSoundEnt::InsertSound( SOUND_MOVE_AWAY | SOUND_CONTEXT_COMBINE_ONLY, pNPC->GetAbsOrigin(), 128, 0.1 );
	}
	else
	{
		if ( g_debug_dropship.GetInt() == 1 )
		{
			NDebugOverlay::Box( pDustoff->GetAbsOrigin(), -Vector(10,10,10), Vector(10,10,10), 0,255,0, 8, 5.0 );
		}

		// Tell the NPC to move to the dustoff position
		pNPC->SetState( NPC_STATE_ALERT );
		pNPC->ScheduledMoveToGoalEntity( SCHED_DROPSHIP_DUSTOFF, pDustoff, ACT_RUN );
		pNPC->GetNavigator()->SetArrivalDirection( pDustoff->GetAbsAngles() );

		// Make sure they ignore a bunch of conditions
		static int g_Conditions[] = 
		{
			COND_CAN_MELEE_ATTACK1,
			COND_CAN_MELEE_ATTACK2,
			COND_CAN_RANGE_ATTACK1,
			COND_CAN_RANGE_ATTACK2,
			COND_ENEMY_DEAD,
			COND_HEAR_BULLET_IMPACT,
			COND_HEAR_COMBAT,
			COND_HEAR_DANGER,
			COND_NEW_ENEMY,
			COND_PROVOKED,
			COND_SEE_ENEMY,
			COND_SEE_FEAR,
			COND_SMELL,
			COND_LIGHT_DAMAGE,
			COND_HEAVY_DAMAGE,
			COND_PHYSICS_DAMAGE,
			COND_REPEATED_DAMAGE,
		};
		pNPC->SetIgnoreConditions( g_Conditions, ARRAYSIZE(g_Conditions) );
	}

	// Unload the next troop
	SpawnTroop();
}

//-----------------------------------------------------------------------------
// Purpose: Tells the dropship to stop waiting and dustoff
// Input  : &inputdata - 
//-----------------------------------------------------------------------------
void CNPC_CombineDropship::InputStopWaitingForDropoff( inputdata_t &inputdata )
{
	m_bWaitForDropoffInput = false;
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void CNPC_CombineDropship::InputHover( inputdata_t &inputdata )
{
	m_iszLandTarget = inputdata.value.StringID();
	LandCommon( true );
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void CNPC_CombineDropship::InputFlyToPathTrack( inputdata_t &inputdata )
{
	if( IsHovering() )
	{
		SetLandingState( LANDING_NO );
		m_hLandTarget = NULL;
	}

	CAI_TrackPather::InputFlyToPathTrack( inputdata );
}

//------------------------------------------------------------------------------
// Purpose : 
// Input   :
// Output  :
//------------------------------------------------------------------------------
float CNPC_CombineDropship::GetAltitude( void )
{
	trace_t tr;
	Vector vecBottom = GetAbsOrigin();

	// Uneven terrain causes us problems, so trace our box down
	AI_TraceEntity( this, vecBottom, vecBottom - Vector(0,0,4096), MASK_SOLID_BRUSHONLY, &tr );

	float flAltitude = ( 4096 * tr.fraction );
	//DevMsg(" Altitude: %.3f\n", flAltitude );
	return flAltitude;
}


//-----------------------------------------------------------------------------
// Purpose: Drop rollermine from dropship
//-----------------------------------------------------------------------------
void CNPC_CombineDropship::DropMine( void )
{
	NPC_Rollermine_DropFromPoint( GetAbsOrigin(), this, STRING(m_sRollermineTemplateData) );
}

//------------------------------------------------------------------------------
// Purpose : Fly towards our pickup target
//------------------------------------------------------------------------------
void CNPC_CombineDropship::UpdatePickupNavigation( void )
{
	// Try and touch the top of the object
	Vector vecPickup = m_hPickupTarget->WorldSpaceCenter();
	vecPickup.z += (m_hPickupTarget->CollisionProp()->OBBSize().z * 0.5);
	SetDesiredPosition( vecPickup );

	//NDebugOverlay::Cross3D( GetDesiredPosition(), -Vector(32,32,32), Vector(32,32,32), 0, 255, 255, true, 0.1f );
}

//------------------------------------------------------------------------------
// Purpose : Fly towards our land target
//------------------------------------------------------------------------------
void CNPC_CombineDropship::UpdateLandTargetNavigation( void )
{
	Vector vecPickup = m_hLandTarget->WorldSpaceCenter();
	vecPickup.z += 256;
	SetDesiredPosition( vecPickup );

	//NDebugOverlay::Cross3D( GetDesiredPosition(), -Vector(32,32,32), Vector(32,32,32), 0, 255, 255, true, 0.1f );
}

//------------------------------------------------------------------------------
// Purpose :
// Input   :
// Output  :
//------------------------------------------------------------------------------
void CNPC_CombineDropship::Hunt( void )
{
	// If we have a pickup target, fly to it
	if ( m_hPickupTarget )
	{
		UpdatePickupNavigation();
	}
	else if ( m_hLandTarget )
	{
		UpdateLandTargetNavigation();
	}
	else if ( GetLandingState() == LANDING_NO )
	{
		UpdateTrackNavigation();
	}

	// don't face player ever, only face nav points
	Vector desiredDir = GetDesiredPosition() - GetAbsOrigin();
	VectorNormalize( desiredDir ); 
	// Face our desired position.
	m_vecDesiredFaceDir = desiredDir;

	if ( GetLandingState() == LANDING_DESCEND || GetLandingState() == LANDING_LEVEL_OUT || IsHovering() )
	{
		if ( m_hLandTarget )
		{
			// We've got a land target, so match it's orientation
			AngleVectors( m_hLandTarget->GetAbsAngles(), &m_vecDesiredFaceDir );
		}
		else
		{
			// No land target. 
			m_vecDesiredFaceDir = GetDesiredPosition() - GetAbsOrigin();
		}
	}

	UpdateEnemy();
	Flight();

	UpdatePlayerDopplerShift( );

}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_CombineDropship::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: do all of the stuff related to having an enemy, attacking, etc.
//-----------------------------------------------------------------------------
void CNPC_CombineDropship::DoCombatStuff( void )
{
	// Handle mines
	if ( m_bDropMines )
	{
		switch( m_iDropState )
		{
		case DROP_IDLE:
			{
				m_iMineCount = m_totalMinesToDrop - 1;

				DropMine();
				// setup next individual drop time
				m_flDropDelay = gpGlobals->curtime + DROPSHIP_TIME_BETWEEN_MINES;
				// get ready to drop next mine, unless we're only supposed to drop 1
				if ( m_iMineCount )
				{
					m_iDropState = DROP_NEXT;
				}
				else
				{
					m_bDropMines = false;		// no more...
				}
				break;
			}
		case DROP_NEXT:
			{
				if ( gpGlobals->curtime > m_flDropDelay )		// time to drop next mine?
				{
					DropMine();
					m_flDropDelay = gpGlobals->curtime + DROPSHIP_TIME_BETWEEN_MINES;

					m_iMineCount--;
					if ( !m_iMineCount )
					{
						m_iDropState = DROP_IDLE;
						m_bDropMines = false;		// reset flag
					}
				}
				break;
			}
		}
	}

	// Handle guns
	bool bStopGun = true;
	if ( GetEnemy() )
	{
		bStopGun = !FireCannonRound();
	}

	if ( bStopGun && m_bIsFiring )
	{
		StopCannon();
	}
}

//-----------------------------------------------------------------------------
// Purpose: Update the container's gun to face the enemy. 
// Input  : &vecMuzzle - The gun's muzzle/firing point
//			&vecAimDir - The gun's current aim direction
//-----------------------------------------------------------------------------
void CNPC_CombineDropship::UpdateContainerGunFacing( Vector &vecMuzzle, Vector &vecToTarget, Vector &vecAimDir, float *flTargetRange )
{
	Assert( m_hContainer );

	// Get the desired aim vector
	vecToTarget = GetEnemy()->WorldSpaceCenter( );

	Vector vecBarrelPos, vecWorldBarrelPos;
	QAngle worldBarrelAngle, vecAngles;
	matrix3x4_t matRefToWorld;
	m_hContainer->GetAttachment( m_iMuzzleAttachment, vecMuzzle, vecAngles );
	vecWorldBarrelPos = vecMuzzle;
	worldBarrelAngle = vecAngles;
	m_hContainer->GetAttachment( m_iMachineGunRefAttachment, matRefToWorld );
	VectorITransform( vecWorldBarrelPos, matRefToWorld, vecBarrelPos );

	EntityMatrix parentMatrix;
	parentMatrix.InitFromEntity( m_hContainer, m_iMachineGunBaseAttachment );
	Vector target = parentMatrix.WorldToLocal( vecToTarget ); 

	float quadTarget = target.LengthSqr();
	float quadTargetXY = target.x*target.x + target.y*target.y;

	// Target is too close!  Can't aim at it
	if ( quadTarget > vecBarrelPos.LengthSqr() )
	{
		// We're trying to aim the offset barrel at an arbitrary point.
		// To calculate this, I think of the target as being on a sphere with 
		// it's center at the origin of the gun.
		// The rotation we need is the opposite of the rotation that moves the target 
		// along the surface of that sphere to intersect with the gun's shooting direction
		// To calculate that rotation, we simply calculate the intersection of the ray 
		// coming out of the barrel with the target sphere (that's the new target position)
		// and use atan2() to get angles

		// angles from target pos to center
		float targetToCenterYaw = atan2( target.y, target.x );
		float centerToGunYaw = atan2( vecBarrelPos.y, sqrt( quadTarget - (vecBarrelPos.y*vecBarrelPos.y) ) );

		float targetToCenterPitch = atan2( target.z, sqrt( quadTargetXY ) );
		float centerToGunPitch = atan2( -vecBarrelPos.z, sqrt( quadTarget - (vecBarrelPos.z*vecBarrelPos.z) ) );

		QAngle angles;
		angles.Init( RAD2DEG(targetToCenterPitch+centerToGunPitch), RAD2DEG( targetToCenterYaw + centerToGunYaw ), 0 );

		float flNewAngle = AngleNormalize( UTIL_ApproachAngle( angles.x, m_hContainer->GetPoseParameter(m_poseWeapon_Pitch), DROPSHIP_GUN_SPEED));
		m_hContainer->SetPoseParameter( m_poseWeapon_Pitch, flNewAngle );

		flNewAngle = AngleNormalize( UTIL_ApproachAngle( angles.y, m_hContainer->GetPoseParameter(m_poseWeapon_Yaw), DROPSHIP_GUN_SPEED));
		m_hContainer->SetPoseParameter( m_poseWeapon_Yaw, flNewAngle );
		m_hContainer->StudioFrameAdvance();
	}

	vecToTarget -= vecMuzzle;
	*flTargetRange = VectorNormalize( vecToTarget );
	AngleVectors( vecAngles, &vecAimDir );
}


//------------------------------------------------------------------------------
// Purpose: Fire a round from the cannon
// Notes:	Only call this if you have an enemy.
//			Returns true if the cannon round was actually fired
//------------------------------------------------------------------------------
bool CNPC_CombineDropship::FireCannonRound( void )
{
	// Try and aim my cannon at the enemy, if I have a container
	if ( !m_hContainer || (m_iCrateType < 0) )
		return false;

	// Update the container gun, and get the vector to the enemy, and the gun's current aim direction
	float flRange;
	Vector vecMuzzle, vecAimDir, vecToEnemy;
	UpdateContainerGunFacing( vecMuzzle, vecToEnemy, vecAimDir, &flRange );
	
	// Out of range?
	if ( flRange > m_flGunRange )
		return false;

	// Only fire if the target's close enough to our aim direction
	float flCosAngle = DotProduct( vecToEnemy, vecAimDir );
	if ( flCosAngle < DOT_15DEGREE )
	{
		m_flTimeNextAttack = gpGlobals->curtime + 0.1;
		return false;
	}

	// If we're out of rounds, reload
	if ( m_iBurstRounds <= 0 )
	{
		m_iBurstRounds = RandomInt( 10, 20 );
		m_flTimeNextAttack = gpGlobals->curtime + (m_iBurstRounds * 0.1);
		return false;
	}

	// HACK: Return true so the fire sound isn't stopped
	if ( m_flTimeNextAttack > gpGlobals->curtime )
		return true;

	m_iBurstRounds--;

	// If we're not currently firing, start it up
	if ( !m_bIsFiring )
	{
		StartCannon();
	}

	// Add a muzzle flash
	QAngle vecAimAngles;
	VectorAngles( vecAimDir, vecAimAngles );
	g_pEffects->MuzzleFlash( vecMuzzle, vecAimAngles, random->RandomFloat( 5.0f, 7.0f ), MUZZLEFLASH_TYPE_GUNSHIP );
	m_flTimeNextAttack = gpGlobals->curtime + 0.05;

	// Clamp to account for inaccuracy in aiming w/ pose parameters
	vecAimDir = vecToEnemy;

	// Fire the bullet
	int ammoType = GetAmmoDef()->Index("CombineCannon"); 
	FireBullets( 1, vecMuzzle, vecAimDir, VECTOR_CONE_2DEGREES, 8192, ammoType, 1, -1, -1, sk_npc_dmg_dropship.GetInt() );

	return true;
}

//------------------------------------------------------------------------------
// Scare AIs in the area where bullets are impacting
//------------------------------------------------------------------------------
void CNPC_CombineDropship::DoImpactEffect( trace_t &tr, int nDamageType )
{
	CSoundEnt::InsertSound( SOUND_DANGER | SOUND_CONTEXT_REACT_TO_SOURCE, tr.endpos, 120.0f, 0.3f, this );

	BaseClass::DoImpactEffect( tr, nDamageType );
}

//------------------------------------------------------------------------------
// Purpose : The proper way to begin the gunship cannon firing at the enemy.
// Input   : 
//		   :
// Output  :
//------------------------------------------------------------------------------
void CNPC_CombineDropship::StartCannon( void )
{
	m_bIsFiring = true;

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

}

//------------------------------------------------------------------------------
// Purpose : The proper way to cease the gunship cannon firing. 
// Input   : 
//		   :
// Output  :
//------------------------------------------------------------------------------
void CNPC_CombineDropship::StopCannon( void )
{
	m_bIsFiring = false;

	// Stop the cannon sound.
	if ( m_pCannonSound )
	{
		CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
		controller.SoundChangeVolume(m_pCannonSound, 0.0, 0.1);
	}
}

//-----------------------------------------------------------------------------
// Purpose: Used the gunship's tracer for now
//-----------------------------------------------------------------------------
void CNPC_CombineDropship::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, 16000, true, "GunshipTracer" );
		}
		break;

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

AI_BEGIN_CUSTOM_NPC( npc_combinedropship, CNPC_CombineDropship )

	DECLARE_ACTIVITY( ACT_DROPSHIP_FLY_IDLE );
	DECLARE_ACTIVITY( ACT_DROPSHIP_FLY_IDLE_EXAGG );
	DECLARE_ACTIVITY( ACT_DROPSHIP_DESCEND_IDLE );
	DECLARE_ACTIVITY( ACT_DROPSHIP_DEPLOY_IDLE );
	DECLARE_ACTIVITY( ACT_DROPSHIP_LIFTOFF );

	DECLARE_ACTIVITY( ACT_DROPSHIP_FLY_IDLE_CARGO );

AI_END_CUSTOM_NPC()