//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Small, fast version of the strider. Goes where striders cannot, such
// as into buildings. Best killed with physics objects and explosives.
//
//=============================================================================

#include "cbase.h"
#include "npc_strider.h"
#include "npc_hunter.h"
#include "ai_behavior_follow.h"
#include "ai_moveprobe.h"
#include "ai_senses.h"
#include "ai_speech.h"
#include "ai_task.h"
#include "ai_default.h"
#include "ai_schedule.h"
#include "ai_hull.h"
#include "ai_baseactor.h"
#include "ai_waypoint.h"
#include "ai_link.h"
#include "ai_hint.h"
#include "ai_squadslot.h"
#include "ai_squad.h"
#include "ai_tacticalservices.h"
#include "beam_shared.h"
#include "datacache/imdlcache.h"
#include "eventqueue.h"
#include "gib.h"
#include "globalstate.h"
#include "hierarchy.h"
#include "movevars_shared.h"
#include "npcevent.h"
#include "saverestore_utlvector.h"
#include "particle_parse.h"
#include "te_particlesystem.h"
#include "sceneentity.h"
#include "shake.h"
#include "soundenvelope.h"
#include "soundent.h"
#include "SpriteTrail.h"
#include "IEffects.h"
#include "engine/IEngineSound.h"
#include "bone_setup.h"
#include "studio.h"
#include "ai_route.h"
#include "ammodef.h"
#include "npc_bullseye.h"
#include "physobj.h"
#include "ai_memory.h"
#include "collisionutils.h"
#include "shot_manipulator.h"
#include "steamjet.h"
#include "physics_prop_ragdoll.h"
#include "vehicle_base.h"
#include "coordsize.h"
#include "hl2_shareddefs.h"
#include "te_effect_dispatch.h"
#include "beam_flags.h"
#include "prop_combine_ball.h"
#include "explode.h"
#include "weapon_physcannon.h"
#include "weapon_striderbuster.h"
#include "monstermaker.h"
#include "weapon_rpg.h"

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

class CNPC_Hunter;


static const char *HUNTER_FLECHETTE_MODEL = "models/weapons/hunter_flechette.mdl";

// Think contexts
static const char *HUNTER_BLEED_THINK = "HunterBleed";
static const char *HUNTER_ZAP_THINK = "HunterZap";
static const char *HUNTER_JOSTLE_VEHICLE_THINK = "HunterJostle";


ConVar sk_hunter_health( "sk_hunter_health", "210" );

// Melee attacks
ConVar sk_hunter_dmg_one_slash( "sk_hunter_dmg_one_slash", "20" );
ConVar sk_hunter_dmg_charge( "sk_hunter_dmg_charge", "20" );

// Flechette volley attack
ConVar hunter_flechette_max_range( "hunter_flechette_max_range", "1200" );
ConVar hunter_flechette_min_range( "hunter_flechette_min_range", "100" );
ConVar hunter_flechette_volley_size( "hunter_flechette_volley_size", "8" );
ConVar hunter_flechette_speed( "hunter_flechette_speed", "2000" );
ConVar sk_hunter_dmg_flechette( "sk_hunter_dmg_flechette", "4.0" );
ConVar sk_hunter_flechette_explode_dmg( "sk_hunter_flechette_explode_dmg", "12.0" );
ConVar sk_hunter_flechette_explode_radius( "sk_hunter_flechette_explode_radius", "128.0" );
ConVar hunter_flechette_explode_delay( "hunter_flechette_explode_delay", "2.5" );
ConVar hunter_flechette_delay( "hunter_flechette_delay", "0.1" );
ConVar hunter_first_flechette_delay( "hunter_first_flechette_delay", "0.5" );
ConVar hunter_flechette_max_concurrent_volleys( "hunter_flechette_max_concurrent_volleys", "2" );
ConVar hunter_flechette_volley_start_min_delay( "hunter_flechette_volley_start_min_delay", ".25" );
ConVar hunter_flechette_volley_start_max_delay( "hunter_flechette_volley_start_max_delay", ".95" );
ConVar hunter_flechette_volley_end_min_delay( "hunter_flechette_volley_end_min_delay", "1" );
ConVar hunter_flechette_volley_end_max_delay( "hunter_flechette_volley_end_max_delay", "2" );
ConVar hunter_flechette_test( "hunter_flechette_test", "0" );
ConVar hunter_clamp_shots( "hunter_clamp_shots", "1" );
ConVar hunter_cheap_explosions( "hunter_cheap_explosions", "1" );

// Damage received
ConVar sk_hunter_bullet_damage_scale( "sk_hunter_bullet_damage_scale", "0.6" );
ConVar sk_hunter_charge_damage_scale( "sk_hunter_charge_damage_scale", "2.0" );
ConVar sk_hunter_buckshot_damage_scale( "sk_hunter_buckshot_damage_scale", "0.5" );
ConVar sk_hunter_vehicle_damage_scale( "sk_hunter_vehicle_damage_scale", "2.2" );
ConVar sk_hunter_dmg_from_striderbuster( "sk_hunter_dmg_from_striderbuster", "150" );
ConVar sk_hunter_citizen_damage_scale( "sk_hunter_citizen_damage_scale", "0.3" );

ConVar hunter_allow_dissolve( "hunter_allow_dissolve", "1" );
ConVar hunter_random_expressions( "hunter_random_expressions", "0" );
ConVar hunter_show_weapon_los_z( "hunter_show_weapon_los_z", "0" );
ConVar hunter_show_weapon_los_condition( "hunter_show_weapon_los_condition", "0" );

ConVar hunter_melee_delay( "hunter_melee_delay", "2.0" );

// Bullrush charge.
ConVar hunter_charge( "hunter_charge", "1" );
ConVar hunter_charge_min_delay( "hunter_charge_min_delay", "10.0" );
ConVar hunter_charge_pct( "hunter_charge_pct", "25" );
ConVar hunter_charge_test( "hunter_charge_test", "0" );

// Vehicle dodging.
ConVar hunter_dodge_warning( "hunter_dodge_warning", "1.1" );
ConVar hunter_dodge_warning_width( "hunter_dodge_warning_width", "180" );
ConVar hunter_dodge_warning_cone( "hunter_dodge_warning_cone", ".5" );
ConVar hunter_dodge_debug( "hunter_dodge_debug", "0" );

// Jostle vehicles when hit by them
ConVar hunter_jostle_car_min_speed( "hunter_jostle_car_min_speed", "100" ); // If hit by a car going at least this fast, jostle the car
ConVar hunter_jostle_car_max_speed( "hunter_jostle_car_max_speed", "600" ); // Used for determining jostle scale

ConVar hunter_free_knowledge( "hunter_free_knowledge", "10.0" );
ConVar hunter_plant_adjust_z( "hunter_plant_adjust_z", "12" );

ConVar hunter_disable_patrol( "hunter_disable_patrol", "0" );

// Dealing with striderbusters
ConVar hunter_hate_held_striderbusters( "hunter_hate_held_striderbusters", "1" );
ConVar hunter_hate_thrown_striderbusters( "hunter_hate_thrown_striderbusters", "1" );
ConVar hunter_hate_attached_striderbusters( "hunter_hate_attached_striderbusters", "1" );
ConVar hunter_hate_held_striderbusters_delay( "hunter_hate_held_striderbusters_delay", "0.5" );
ConVar hunter_hate_held_striderbusters_tolerance( "hunter_hate_held_striderbusters_tolerance", "2000.0" );
ConVar hunter_hate_thrown_striderbusters_tolerance( "hunter_hate_thrown_striderbusters_tolerance", "300.0" );
ConVar hunter_seek_thrown_striderbusters_tolerance( "hunter_seek_thrown_striderbusters_tolerance", "400.0" );
ConVar hunter_retreat_striderbusters( "hunter_retreat_striderbusters", "1", FCVAR_NONE, "If true, the hunter will retreat when a buster is glued to him." );

ConVar hunter_allow_nav_jump( "hunter_allow_nav_jump", "0" );
ConVar g_debug_hunter_charge( "g_debug_hunter_charge", "0" );

ConVar hunter_stand_still( "hunter_stand_still", "0" ); // used for debugging, keeps them rooted in place

ConVar hunter_siege_frequency( "hunter_siege_frequency", "12" );

#define HUNTER_FOV_DOT					0.0		// 180 degree field of view
#define HUNTER_CHARGE_MIN				256
#define HUNTER_CHARGE_MAX				1024
#define HUNTER_FACE_ENEMY_DIST			512.0f
#define HUNTER_MELEE_REACH				80
#define HUNTER_BLOOD_LEFT_FOOT			0
#define HUNTER_IGNORE_ENEMY_TIME		5		// How long the hunter will ignore another enemy when distracted by the player.

#define HUNTER_FACING_DOT				0.8		// The angle within which we start shooting
#define HUNTER_SHOOT_MAX_YAW_DEG		60.0f	// Once shooting, clamp to +/- these degrees of yaw deflection as our target moves
#define HUNTER_SHOOT_MAX_YAW_COS		0.5f	// The cosine of the above angle

#define HUNTER_FLECHETTE_WARN_TIME		1.0f

#define HUNTER_SEE_ENEMY_TIME_INVALID	-1

#define NUM_FLECHETTE_VOLLEY_ON_FOLLOW 4

#define HUNTER_SIEGE_MAX_DIST_MODIFIER 2.0f

//-----------------------------------------------------------------------------
// Animation events
//-----------------------------------------------------------------------------
int AE_HUNTER_FOOTSTEP_LEFT;
int AE_HUNTER_FOOTSTEP_RIGHT;
int AE_HUNTER_FOOTSTEP_BACK;
int AE_HUNTER_MELEE_ANNOUNCE;
int AE_HUNTER_MELEE_ATTACK_LEFT;
int AE_HUNTER_MELEE_ATTACK_RIGHT;
int AE_HUNTER_DIE;
int AE_HUNTER_SPRAY_BLOOD;
int AE_HUNTER_START_EXPRESSION;
int AE_HUNTER_END_EXPRESSION;


//-----------------------------------------------------------------------------
// Interactions.
//-----------------------------------------------------------------------------
int g_interactionHunterFoundEnemy = 0;


//-----------------------------------------------------------------------------
// Local stuff.
//-----------------------------------------------------------------------------
static string_t s_iszStriderClassname;
static string_t s_iszStriderBusterClassname;
static string_t s_iszMagnadeClassname;
static string_t s_iszPhysPropClassname;
static string_t s_iszHuntersToRunOver;


//-----------------------------------------------------------------------------
// Custom Activities
//-----------------------------------------------------------------------------
Activity ACT_HUNTER_DEPLOYRA2;
Activity ACT_HUNTER_DODGER;
Activity ACT_HUNTER_DODGEL;
Activity ACT_HUNTER_GESTURE_SHOOT;
Activity ACT_HUNTER_FLINCH_STICKYBOMB;
Activity ACT_HUNTER_STAGGER;
Activity ACT_HUNTER_MELEE_ATTACK1_VS_PLAYER;
Activity ACT_DI_HUNTER_MELEE;
Activity ACT_DI_HUNTER_THROW;
Activity ACT_HUNTER_ANGRY;
Activity ACT_HUNTER_WALK_ANGRY;
Activity ACT_HUNTER_FOUND_ENEMY;
Activity ACT_HUNTER_FOUND_ENEMY_ACK;
Activity ACT_HUNTER_CHARGE_START;
Activity ACT_HUNTER_CHARGE_RUN;
Activity ACT_HUNTER_CHARGE_STOP;
Activity ACT_HUNTER_CHARGE_CRASH;
Activity ACT_HUNTER_CHARGE_HIT;
Activity ACT_HUNTER_RANGE_ATTACK2_UNPLANTED;
Activity ACT_HUNTER_IDLE_PLANTED;
Activity ACT_HUNTER_FLINCH_N;
Activity ACT_HUNTER_FLINCH_S;
Activity ACT_HUNTER_FLINCH_E;
Activity ACT_HUNTER_FLINCH_W;


//-----------------------------------------------------------------------------
//	Squad slots
//-----------------------------------------------------------------------------
enum SquadSlot_t
{	
	SQUAD_SLOT_HUNTER_CHARGE = LAST_SHARED_SQUADSLOT,
	SQUAD_SLOT_HUNTER_FLANK_FIRST,
	SQUAD_SLOT_HUNTER_FLANK_LAST = SQUAD_SLOT_HUNTER_FLANK_FIRST,
	SQUAD_SLOT_RUN_SHOOT,
};

#define	HUNTER_FOLLOW_DISTANCE	2000.0f
#define	HUNTER_FOLLOW_DISTANCE_SQR	(HUNTER_FOLLOW_DISTANCE * HUNTER_FOLLOW_DISTANCE)

#define HUNTER_RUNDOWN_SQUADDATA 0


//-----------------------------------------------------------------------------
// We're doing this quite a lot, so this makes the check a lot faster since
// we don't have to compare strings.
//-----------------------------------------------------------------------------
bool IsStriderBuster( CBaseEntity *pEntity )
{
	if ( !pEntity )
		return false;

	if( pEntity->m_iClassname == s_iszStriderBusterClassname || 
		pEntity->m_iClassname == s_iszMagnadeClassname)
		return true;

	return false;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool HateThisStriderBuster( CBaseEntity *pTarget )
{
	if ( StriderBuster_WasKnockedOffStrider(pTarget) )
		return false;

	if ( pTarget->VPhysicsGetObject() )
	{
		if ( hunter_hate_held_striderbusters.GetBool() || 
			hunter_hate_thrown_striderbusters.GetBool() ||
			hunter_hate_attached_striderbusters.GetBool() )
		{
			if ( ( pTarget->VPhysicsGetObject()->GetGameFlags() & ( FVPHYSICS_PLAYER_HELD | FVPHYSICS_WAS_THROWN ) ) )
			{
				return true;
			}

			if ( StriderBuster_IsAttachedStriderBuster( pTarget ) )
			{
				return true;
			}
		}
	}

	return false;
}


//-----------------------------------------------------------------------------
// The hunter can fire a volley of explosive flechettes.
//-----------------------------------------------------------------------------
static const char *s_szHunterFlechetteBubbles = "HunterFlechetteBubbles";
static const char *s_szHunterFlechetteSeekThink = "HunterFlechetteSeekThink";
static const char *s_szHunterFlechetteDangerSoundThink = "HunterFlechetteDangerSoundThink";
static const char *s_szHunterFlechetteSpriteTrail = "sprites/bluelaser1.vmt";
static int s_nHunterFlechetteImpact = -2;
static int s_nFlechetteFuseAttach = -1;

#define FLECHETTE_AIR_VELOCITY	2500

class CHunterFlechette : public CPhysicsProp, public IParentPropInteraction
{
	DECLARE_CLASS( CHunterFlechette, CPhysicsProp );

public:

	CHunterFlechette();
	~CHunterFlechette();

	Class_T Classify() { return CLASS_NONE; }
	
	bool WasThrownBack()
	{
		return m_bThrownBack;
	}

public:

	void Spawn();
	void Activate();
	void Precache();
	void Shoot( Vector &vecVelocity, bool bBright );
	void SetSeekTarget( CBaseEntity *pTargetEntity );
	void Explode();

	bool CreateVPhysics();

	unsigned int PhysicsSolidMaskForEntity() const;
	static CHunterFlechette *FlechetteCreate( const Vector &vecOrigin, const QAngle &angAngles, CBaseEntity *pentOwner = NULL );

	// IParentPropInteraction
	void OnParentCollisionInteraction( parentCollisionInteraction_t eType, int index, gamevcollisionevent_t *pEvent );
	void OnParentPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason );

protected:

	void SetupGlobalModelData();

	void StickTo( CBaseEntity *pOther, trace_t &tr );

	void BubbleThink();
	void DangerSoundThink();
	void ExplodeThink();
	void DopplerThink();
	void SeekThink();

	bool CreateSprites( bool bBright );

	void FlechetteTouch( CBaseEntity *pOther );

	Vector m_vecShootPosition;
	EHANDLE m_hSeekTarget;
	bool m_bThrownBack;

	DECLARE_DATADESC();
	//DECLARE_SERVERCLASS();
};

LINK_ENTITY_TO_CLASS( hunter_flechette, CHunterFlechette );

BEGIN_DATADESC( CHunterFlechette )

	DEFINE_THINKFUNC( BubbleThink ),
	DEFINE_THINKFUNC( DangerSoundThink ),
	DEFINE_THINKFUNC( ExplodeThink ),
	DEFINE_THINKFUNC( DopplerThink ),
	DEFINE_THINKFUNC( SeekThink ),
	
	DEFINE_ENTITYFUNC( FlechetteTouch ),

	DEFINE_FIELD( m_vecShootPosition, FIELD_POSITION_VECTOR ),
	DEFINE_FIELD( m_hSeekTarget, FIELD_EHANDLE ),
	DEFINE_FIELD( m_bThrownBack, FIELD_BOOLEAN ),

END_DATADESC()

//IMPLEMENT_SERVERCLASS_ST( CHunterFlechette, DT_HunterFlechette )
//END_SEND_TABLE()


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CHunterFlechette *CHunterFlechette::FlechetteCreate( const Vector &vecOrigin, const QAngle &angAngles, CBaseEntity *pentOwner )
{
	// Create a new entity with CHunterFlechette private data
	CHunterFlechette *pFlechette = (CHunterFlechette *)CreateEntityByName( "hunter_flechette" );
	UTIL_SetOrigin( pFlechette, vecOrigin );
	pFlechette->SetAbsAngles( angAngles );
	pFlechette->Spawn();
	pFlechette->Activate();
	pFlechette->SetOwnerEntity( pentOwner );

	return pFlechette;
}


//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void CC_Hunter_Shoot_Flechette( const CCommand& args )
{
	MDLCACHE_CRITICAL_SECTION();

	bool allowPrecache = CBaseEntity::IsPrecacheAllowed();
	CBaseEntity::SetAllowPrecache( true );

	CBasePlayer *pPlayer = UTIL_GetCommandClient();

	QAngle angEye = pPlayer->EyeAngles();
	CHunterFlechette *entity = CHunterFlechette::FlechetteCreate( pPlayer->EyePosition(), angEye, pPlayer );
	if ( entity )
	{
		entity->Precache();
		DispatchSpawn( entity );

		// Shoot the flechette.		
		Vector forward;
		pPlayer->EyeVectors( &forward );
		forward *= 2000.0f;
		entity->Shoot( forward, false );
	}

	CBaseEntity::SetAllowPrecache( allowPrecache );
}

static ConCommand ent_create("hunter_shoot_flechette", CC_Hunter_Shoot_Flechette, "Fires a hunter flechette where the player is looking.", FCVAR_GAMEDLL | FCVAR_CHEAT);


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CHunterFlechette::CHunterFlechette()
{
	UseClientSideAnimation();
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CHunterFlechette::~CHunterFlechette()
{
}


//-----------------------------------------------------------------------------
// If set, the flechette will seek unerringly toward the target as it flies.
//-----------------------------------------------------------------------------
void CHunterFlechette::SetSeekTarget( CBaseEntity *pTargetEntity )
{
	if ( pTargetEntity )
	{
		m_hSeekTarget = pTargetEntity;
		SetContextThink( &CHunterFlechette::SeekThink, gpGlobals->curtime, s_szHunterFlechetteSeekThink );
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CHunterFlechette::CreateVPhysics()
{
	// Create the object in the physics system
	VPhysicsInitNormal( SOLID_BBOX, FSOLID_NOT_STANDABLE, false );

	return true;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
unsigned int CHunterFlechette::PhysicsSolidMaskForEntity() const
{
	return ( BaseClass::PhysicsSolidMaskForEntity() | CONTENTS_HITBOX ) & ~CONTENTS_GRATE;
}


//-----------------------------------------------------------------------------
// Called from CPropPhysics code when we're attached to a physics object.
//-----------------------------------------------------------------------------
void CHunterFlechette::OnParentCollisionInteraction( parentCollisionInteraction_t eType, int index, gamevcollisionevent_t *pEvent )
{
	if ( eType == COLLISIONINTER_PARENT_FIRST_IMPACT )
	{
		m_bThrownBack = true;
		Explode();
	}
}

void CHunterFlechette::OnParentPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason )
{
	m_bThrownBack = true;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CHunterFlechette::CreateSprites( bool bBright )
{
	if ( bBright )
	{
		DispatchParticleEffect( "hunter_flechette_trail_striderbuster", PATTACH_ABSORIGIN_FOLLOW, this );
	}
	else
	{
		DispatchParticleEffect( "hunter_flechette_trail", PATTACH_ABSORIGIN_FOLLOW, this );
	}
	
	return true;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHunterFlechette::Spawn()
{
	Precache( );

	SetModel( HUNTER_FLECHETTE_MODEL );
	SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_CUSTOM );
	UTIL_SetSize( this, -Vector(1,1,1), Vector(1,1,1) );
	SetSolid( SOLID_BBOX );
	SetGravity( 0.05f );
	SetCollisionGroup( COLLISION_GROUP_PROJECTILE );
	
	// Make sure we're updated if we're underwater
	UpdateWaterState();

	SetTouch( &CHunterFlechette::FlechetteTouch );

	// Make us glow until we've hit the wall
	m_nSkin = 1;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHunterFlechette::Activate()
{
	BaseClass::Activate();
	SetupGlobalModelData();
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHunterFlechette::SetupGlobalModelData()
{
	if ( s_nHunterFlechetteImpact == -2 )
	{
		s_nHunterFlechetteImpact = LookupSequence( "impact" );
		s_nFlechetteFuseAttach = LookupAttachment( "attach_fuse" );
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHunterFlechette::Precache()
{
	PrecacheModel( HUNTER_FLECHETTE_MODEL );
	PrecacheModel( "sprites/light_glow02_noz.vmt" );

	PrecacheScriptSound( "NPC_Hunter.FlechetteNearmiss" );
	PrecacheScriptSound( "NPC_Hunter.FlechetteHitBody" );
	PrecacheScriptSound( "NPC_Hunter.FlechetteHitWorld" );
	PrecacheScriptSound( "NPC_Hunter.FlechettePreExplode" );
	PrecacheScriptSound( "NPC_Hunter.FlechetteExplode" );

	PrecacheParticleSystem( "hunter_flechette_trail_striderbuster" );
	PrecacheParticleSystem( "hunter_flechette_trail" );
	PrecacheParticleSystem( "hunter_projectile_explosion_1" );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHunterFlechette::StickTo( CBaseEntity *pOther, trace_t &tr )
{
	EmitSound( "NPC_Hunter.FlechetteHitWorld" );

	SetMoveType( MOVETYPE_NONE );
	
	if ( !pOther->IsWorld() )
	{
		SetParent( pOther );
		SetSolid( SOLID_NONE );
		SetSolidFlags( FSOLID_NOT_SOLID );
	}

	// Do an impact effect.
	//Vector vecDir = GetAbsVelocity();
	//float speed = VectorNormalize( vecDir );

	//Vector vForward;
	//AngleVectors( GetAbsAngles(), &vForward );
	//VectorNormalize ( vForward );

	//CEffectData	data;
	//data.m_vOrigin = tr.endpos;
	//data.m_vNormal = vForward;
	//data.m_nEntIndex = 0;
	//DispatchEffect( "BoltImpact", data );
	
	Vector vecVelocity = GetAbsVelocity();
	bool bAttachedToBuster = StriderBuster_OnFlechetteAttach( pOther, vecVelocity );

	SetTouch( NULL );

	// We're no longer flying. Stop checking for water volumes.
	SetContextThink( NULL, 0, s_szHunterFlechetteBubbles );

	// Stop seeking.
	m_hSeekTarget = NULL;
	SetContextThink( NULL, 0, s_szHunterFlechetteSeekThink );

	// Get ready to explode.
	if ( !bAttachedToBuster )
	{
		SetThink( &CHunterFlechette::DangerSoundThink );
		SetNextThink( gpGlobals->curtime + (hunter_flechette_explode_delay.GetFloat() - HUNTER_FLECHETTE_WARN_TIME) );
	}
	else
	{
		DangerSoundThink();
	}

	// Play our impact animation.
	ResetSequence( s_nHunterFlechetteImpact );

	static int s_nImpactCount = 0;
	s_nImpactCount++;
	if ( s_nImpactCount & 0x01 )
	{
		UTIL_ImpactTrace( &tr, DMG_BULLET );
		
		// Shoot some sparks
		if ( UTIL_PointContents( GetAbsOrigin() ) != CONTENTS_WATER)
		{
			g_pEffects->Sparks( GetAbsOrigin() );
		}
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHunterFlechette::FlechetteTouch( CBaseEntity *pOther )
{
	if ( pOther->IsSolidFlagSet(FSOLID_VOLUME_CONTENTS | FSOLID_TRIGGER) )
	{
		// Some NPCs are triggers that can take damage (like antlion grubs). We should hit them.
		if ( ( pOther->m_takedamage == DAMAGE_NO ) || ( pOther->m_takedamage == DAMAGE_EVENTS_ONLY ) )
			return;
	}

	if ( FClassnameIs( pOther, "hunter_flechette" ) )
		return;

	trace_t	tr;
	tr = BaseClass::GetTouchTrace();

	if ( pOther->m_takedamage != DAMAGE_NO )
	{
		Vector	vecNormalizedVel = GetAbsVelocity();

		ClearMultiDamage();
		VectorNormalize( vecNormalizedVel );

		float flDamage = sk_hunter_dmg_flechette.GetFloat();
		CBreakable *pBreak = dynamic_cast <CBreakable *>(pOther);
		if ( pBreak && ( pBreak->GetMaterialType() == matGlass ) )
		{
			flDamage = MAX( pOther->GetHealth(), flDamage );
		}

		CTakeDamageInfo	dmgInfo( this, GetOwnerEntity(), flDamage, DMG_DISSOLVE | DMG_NEVERGIB );
		CalculateMeleeDamageForce( &dmgInfo, vecNormalizedVel, tr.endpos, 0.7f );
		dmgInfo.SetDamagePosition( tr.endpos );
		pOther->DispatchTraceAttack( dmgInfo, vecNormalizedVel, &tr );

		ApplyMultiDamage();

		// Keep going through breakable glass.
		if ( pOther->GetCollisionGroup() == COLLISION_GROUP_BREAKABLE_GLASS )
			 return;
			 
		SetAbsVelocity( Vector( 0, 0, 0 ) );

		// play body "thwack" sound
		EmitSound( "NPC_Hunter.FlechetteHitBody" );

		StopParticleEffects( this );

		Vector vForward;
		AngleVectors( GetAbsAngles(), &vForward );
		VectorNormalize ( vForward );

		trace_t	tr2;
		UTIL_TraceLine( GetAbsOrigin(),	GetAbsOrigin() + vForward * 128, MASK_BLOCKLOS, pOther, COLLISION_GROUP_NONE, &tr2 );

		if ( tr2.fraction != 1.0f )
		{
			//NDebugOverlay::Box( tr2.endpos, Vector( -16, -16, -16 ), Vector( 16, 16, 16 ), 0, 255, 0, 0, 10 );
			//NDebugOverlay::Box( GetAbsOrigin(), Vector( -16, -16, -16 ), Vector( 16, 16, 16 ), 0, 0, 255, 0, 10 );

			if ( tr2.m_pEnt == NULL || ( tr2.m_pEnt && tr2.m_pEnt->GetMoveType() == MOVETYPE_NONE ) )
			{
				CEffectData	data;

				data.m_vOrigin = tr2.endpos;
				data.m_vNormal = vForward;
				data.m_nEntIndex = tr2.fraction != 1.0f;
			
				//DispatchEffect( "BoltImpact", data );
			}
		}

		if ( ( ( pOther->GetMoveType() == MOVETYPE_VPHYSICS ) || ( pOther->GetMoveType() == MOVETYPE_PUSH ) ) && ( ( pOther->GetHealth() > 0 ) || ( pOther->m_takedamage == DAMAGE_EVENTS_ONLY ) ) )
		{
			CPhysicsProp *pProp = dynamic_cast<CPhysicsProp *>( pOther );
			if ( pProp )
			{
				pProp->SetInteraction( PROPINTER_PHYSGUN_NOTIFY_CHILDREN );
			}
		
			// We hit a physics object that survived the impact. Stick to it.
			StickTo( pOther, tr );
		}
		else
		{
			SetTouch( NULL );
			SetThink( NULL );
			SetContextThink( NULL, 0, s_szHunterFlechetteBubbles );

			UTIL_Remove( this );
		}
	}
	else
	{
		// See if we struck the world
		if ( pOther->GetMoveType() == MOVETYPE_NONE && !( tr.surface.flags & SURF_SKY ) )
		{
			// We hit a physics object that survived the impact. Stick to it.
			StickTo( pOther, tr );
		}
		else if( pOther->GetMoveType() == MOVETYPE_PUSH && FClassnameIs(pOther, "func_breakable") )
		{
			// We hit a func_breakable, stick to it.
			// The MOVETYPE_PUSH is a micro-optimization to cut down on the classname checks.
			StickTo( pOther, tr );
		}
		else
		{
			// Put a mark unless we've hit the sky
			if ( ( tr.surface.flags & SURF_SKY ) == false )
			{
				UTIL_ImpactTrace( &tr, DMG_BULLET );
			}

			UTIL_Remove( this );
		}
	}
}


//-----------------------------------------------------------------------------
// Fixup flechette position when seeking towards a striderbuster.
//-----------------------------------------------------------------------------
void CHunterFlechette::SeekThink()
{
	if ( m_hSeekTarget )
	{
		Vector vecBodyTarget = m_hSeekTarget->BodyTarget( GetAbsOrigin() );

		Vector vecClosest;
		CalcClosestPointOnLineSegment( GetAbsOrigin(), m_vecShootPosition, vecBodyTarget, vecClosest, NULL );

		Vector vecDelta = vecBodyTarget - m_vecShootPosition;
		VectorNormalize( vecDelta );

		QAngle angShoot;
		VectorAngles( vecDelta, angShoot );

		float flSpeed = hunter_flechette_speed.GetFloat();
		if ( !flSpeed )
		{
			flSpeed = 2500.0f;
		}

		Vector vecVelocity = vecDelta * flSpeed;
		Teleport( &vecClosest, &angShoot, &vecVelocity );

		SetNextThink( gpGlobals->curtime, s_szHunterFlechetteSeekThink );
	}
}


//-----------------------------------------------------------------------------
// Play a near miss sound as we travel past the player.
//-----------------------------------------------------------------------------
void CHunterFlechette::DopplerThink()
{
	CBasePlayer *pPlayer = AI_GetSinglePlayer();
	if ( !pPlayer )
		return;

	Vector vecVelocity = GetAbsVelocity();
	VectorNormalize( vecVelocity );
	
	float flMyDot = DotProduct( vecVelocity, GetAbsOrigin() );
	float flPlayerDot = DotProduct( vecVelocity, pPlayer->GetAbsOrigin() );

	if ( flPlayerDot <= flMyDot )
	{
		EmitSound( "NPC_Hunter.FlechetteNearMiss" );
		
		// We've played the near miss sound and we're not seeking. Stop thinking.
		SetThink( NULL );
	}
	else
	{
		SetNextThink( gpGlobals->curtime );
	}
}


//-----------------------------------------------------------------------------
// Think every 0.1 seconds to make bubbles if we're flying through water.
//-----------------------------------------------------------------------------
void CHunterFlechette::BubbleThink()
{
	SetNextThink( gpGlobals->curtime + 0.1f, s_szHunterFlechetteBubbles );

	if ( GetWaterLevel()  == 0 )
		return;

	UTIL_BubbleTrail( GetAbsOrigin() - GetAbsVelocity() * 0.1f, GetAbsOrigin(), 5 );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHunterFlechette::Shoot( Vector &vecVelocity, bool bBrightFX )
{
	CreateSprites( bBrightFX );

	m_vecShootPosition = GetAbsOrigin();

	SetAbsVelocity( vecVelocity );

	SetThink( &CHunterFlechette::DopplerThink );
	SetNextThink( gpGlobals->curtime );

	SetContextThink( &CHunterFlechette::BubbleThink, gpGlobals->curtime + 0.1, s_szHunterFlechetteBubbles );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHunterFlechette::DangerSoundThink()
{
	EmitSound( "NPC_Hunter.FlechettePreExplode" );

	CSoundEnt::InsertSound( SOUND_DANGER|SOUND_CONTEXT_EXCLUDE_COMBINE, GetAbsOrigin(), 150.0f, 0.5, this );
	SetThink( &CHunterFlechette::ExplodeThink );
	SetNextThink( gpGlobals->curtime + HUNTER_FLECHETTE_WARN_TIME );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHunterFlechette::ExplodeThink()
{
	Explode();
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHunterFlechette::Explode()
{
	SetSolid( SOLID_NONE );

	// Don't catch self in own explosion!
	m_takedamage = DAMAGE_NO;

	EmitSound( "NPC_Hunter.FlechetteExplode" );
	
	// Move the explosion effect to the tip to reduce intersection with the world.
	Vector vecFuse;
	GetAttachment( s_nFlechetteFuseAttach, vecFuse );
	DispatchParticleEffect( "hunter_projectile_explosion_1", vecFuse, GetAbsAngles(), NULL );

	int nDamageType = DMG_DISSOLVE;

	// Perf optimization - only every other explosion makes a physics force. This is
	// hardly noticeable since flechettes usually explode in clumps.
	static int s_nExplosionCount = 0;
	s_nExplosionCount++;
	if ( ( s_nExplosionCount & 0x01 ) && hunter_cheap_explosions.GetBool() )
	{
		nDamageType |= DMG_PREVENT_PHYSICS_FORCE;
	}

	RadiusDamage( CTakeDamageInfo( this, GetOwnerEntity(), sk_hunter_flechette_explode_dmg.GetFloat(), nDamageType ), GetAbsOrigin(), sk_hunter_flechette_explode_radius.GetFloat(), CLASS_NONE, NULL );
		
    AddEffects( EF_NODRAW );

	SetThink( &CBaseEntity::SUB_Remove );
	SetNextThink( gpGlobals->curtime + 0.1f );
}


//-----------------------------------------------------------------------------
// Calculate & apply damage & force for a charge to a target.
// Done outside of the hunter because we need to do this inside a trace filter.
//-----------------------------------------------------------------------------
void Hunter_ApplyChargeDamage( CBaseEntity *pHunter, CBaseEntity *pTarget, float flDamage )
{
	Vector attackDir = ( pTarget->WorldSpaceCenter() - pHunter->WorldSpaceCenter() );
	VectorNormalize( attackDir );
	Vector offset = RandomVector( -32, 32 ) + pTarget->WorldSpaceCenter();

	// Generate enough force to make a 75kg guy move away at 700 in/sec
	Vector vecForce = attackDir * ImpulseScale( 75, 700 );

	// Deal the damage
	CTakeDamageInfo	info( pHunter, pHunter, vecForce, offset, flDamage, DMG_CLUB );
	pTarget->TakeDamage( info );
}


//-----------------------------------------------------------------------------
// A simple trace filter class to skip small moveable physics objects
//-----------------------------------------------------------------------------
class CHunterTraceFilterSkipPhysics : public CTraceFilter
{
public:
	// It does have a base, but we'll never network anything below here..
	DECLARE_CLASS_NOBASE( CHunterTraceFilterSkipPhysics );
	
	CHunterTraceFilterSkipPhysics( const IHandleEntity *passentity, int collisionGroup, float minMass )
		: m_pPassEnt(passentity), m_collisionGroup(collisionGroup), m_minMass(minMass)
	{
	}
	virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
	{
		if ( !StandardFilterRules( pHandleEntity, contentsMask ) )
			return false;

		if ( !PassServerEntityFilter( pHandleEntity, m_pPassEnt ) )
			return false;

		// Don't test if the game code tells us we should ignore this collision...
		CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity );
		if ( pEntity )
		{
			if ( !pEntity->ShouldCollide( m_collisionGroup, contentsMask ) )
				return false;
			
			if ( !g_pGameRules->ShouldCollide( m_collisionGroup, pEntity->GetCollisionGroup() ) )
				return false;

			// don't test small moveable physics objects (unless it's an NPC)
			if ( !pEntity->IsNPC() && pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
			{
				float entMass = PhysGetEntityMass( pEntity ) ;
				if ( entMass < m_minMass )
				{
					if ( entMass < m_minMass * 0.666f || pEntity->CollisionProp()->BoundingRadius() < (assert_cast<const CAI_BaseNPC *>(EntityFromEntityHandle( m_pPassEnt )))->GetHullHeight() )
					{
						return false;
					}
				}
			}

			// If we hit an antlion, don't stop, but kill it
			if ( pEntity->Classify() == CLASS_ANTLION )
			{
				CBaseEntity *pHunter = (CBaseEntity *)EntityFromEntityHandle( m_pPassEnt );
				Hunter_ApplyChargeDamage( pHunter, pEntity, pEntity->GetHealth() );
				return false;
			}
		}

		return true;
	}

private:
	const IHandleEntity *m_pPassEnt;
	int m_collisionGroup;
	float m_minMass;
};


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
inline void HunterTraceHull_SkipPhysics( const Vector &vecAbsStart, const Vector &vecAbsEnd, const Vector &hullMin, 
					 const Vector &hullMax,	unsigned int mask, const CBaseEntity *ignore, 
					 int collisionGroup, trace_t *ptr, float minMass )
{
	Ray_t ray;
	ray.Init( vecAbsStart, vecAbsEnd, hullMin, hullMax );
	CHunterTraceFilterSkipPhysics traceFilter( ignore, collisionGroup, minMass );
	enginetrace->TraceRay( ray, mask, &traceFilter, ptr );
}


//-----------------------------------------------------------------------------
// Hunter follow behavior
//-----------------------------------------------------------------------------
class CAI_HunterEscortBehavior : public CAI_FollowBehavior
{	
public:
	DECLARE_CLASS( CAI_HunterEscortBehavior, CAI_FollowBehavior );

	CAI_HunterEscortBehavior() :
		BaseClass( AI_FollowParams_t( AIF_HUNTER, true ) ),
		m_flTimeEscortReturn( 0 ),
		m_bEnabled( false )
	{
	}

	CNPC_Hunter *GetOuter() { return (CNPC_Hunter *)( BaseClass::GetOuter() ); }

	void SetEscortTarget( CNPC_Strider *pLeader, bool fFinishCurSchedule = false );
	CNPC_Strider * GetEscortTarget() { return (CNPC_Strider *)GetFollowTarget(); }

	bool FarFromFollowTarget()
	{ 
		return ( GetFollowTarget() && (GetAbsOrigin() - GetFollowTarget()->GetAbsOrigin()).LengthSqr() > HUNTER_FOLLOW_DISTANCE_SQR ); 
	}

	void DrawDebugGeometryOverlays();
	bool ShouldFollow();
	void BuildScheduleTestBits();

	void BeginScheduleSelection();

	void GatherConditions();
	void GatherConditionsNotActive();
	int SelectSchedule();
	int	FollowCallBaseSelectSchedule();
	void StartTask( const Task_t *pTask );
	void RunTask( const Task_t *pTask );

	void CheckBreakEscort();

	void OnDamage( const CTakeDamageInfo &info );
	static void DistributeFreeHunters();
	static void FindFreeHunters( CUtlVector<CNPC_Hunter *> *pFreeHunters );

	float			m_flTimeEscortReturn;
	CSimpleSimTimer	m_FollowAttackTimer;
	bool			m_bEnabled;

	static float	gm_flLastDefendSound; // not saved and loaded, it's okay to yell again after a load

	//---------------------------------

	DECLARE_DATADESC();
};


BEGIN_DATADESC( CAI_HunterEscortBehavior )
	DEFINE_FIELD( m_flTimeEscortReturn, FIELD_TIME ),
	DEFINE_EMBEDDED( m_FollowAttackTimer ),
	DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ),
END_DATADESC();

float CAI_HunterEscortBehavior::gm_flLastDefendSound;

//-----------------------------------------------------------------------------
// Hunter PHYSICS DAMAGE TABLE
//-----------------------------------------------------------------------------
#define HUNTER_MIN_PHYSICS_DAMAGE 10

static impactentry_t s_HunterLinearTable[] =
{
	{ 150*150, 75 },
	{ 350*350, 105 },
	{ 1000*1000, 300 },
};

static impactentry_t s_HunterAngularTable[] =
{
	{ 100*100, 75 },
	{ 200*200, 105 },
	{ 300*300, 300 },
};

impactdamagetable_t s_HunterImpactDamageTable =
{
	s_HunterLinearTable,
	s_HunterAngularTable,
	
	ARRAYSIZE(s_HunterLinearTable),
	ARRAYSIZE(s_HunterAngularTable),

	24*24,		// minimum linear speed squared
	360*360,	// minimum angular speed squared (360 deg/s to cause spin/slice damage)
	5,			// can't take damage from anything under 5kg

	10,			// anything less than 10kg is "small"
	HUNTER_MIN_PHYSICS_DAMAGE,			// never take more than 10 pts of damage from anything under 10kg
	36*36,		// <10kg objects must go faster than 36 in/s to do damage

	VPHYSICS_LARGE_OBJECT_MASS,		// large mass in kg 
	4,			// large mass scale (anything over 500kg does 4X as much energy to read from damage table)
	5,			// large mass falling scale (emphasize falling/crushing damage over sideways impacts since the stress will kill you anyway)
	0.0f,		// min vel
};


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
class CNPC_Hunter : public CAI_BaseActor
{
	DECLARE_CLASS( CNPC_Hunter, CAI_BaseActor );

public:
	CNPC_Hunter();
	~CNPC_Hunter();

	//---------------------------------

	void			Precache();
	void			Spawn();
	void			PostNPCInit();
	void			Activate();
	void			UpdateOnRemove();
	void			OnRestore();
	bool			CreateBehaviors();
	void			IdleSound();
	bool			ShouldPlayIdleSound();
	bool			CanBecomeRagdoll();
	Activity		GetDeathActivity();
	void			StopLoopingSounds();

	const impactdamagetable_t &GetPhysicsImpactDamageTable();

	Class_T			Classify();
	Vector			BodyTarget( const Vector &posSrc, bool bNoisy /*= true*/ );

	int				DrawDebugTextOverlays();
	void			DrawDebugGeometryOverlays();

	void			UpdateEfficiency( bool bInPVS );

	//---------------------------------

	virtual Vector	GetNodeViewOffset()					{ return BaseClass::GetDefaultEyeOffset();		}

	int				GetSoundInterests();

	bool			IsInLargeOutdoorMap();

	//---------------------------------
	// CAI_BaseActor
	//---------------------------------
	const char *SelectRandomExpressionForState( NPC_STATE state );
	void PlayExpressionForState( NPC_STATE state );

	//---------------------------------
	// CBaseAnimating
	//---------------------------------
	float			GetIdealAccel() const { return GetIdealSpeed(); }

	//---------------------------------
	// Behavior
	//---------------------------------
	void			NPCThink();
	void			PrescheduleThink();
	void			GatherConditions();
	void			CollectSiegeTargets();
	void			ManageSiegeTargets();
	void			KillCurrentSiegeTarget();
	bool			QueryHearSound( CSound *pSound );
	void			OnSeeEntity( CBaseEntity *pEntity );
	void			CheckFlinches() {} // Hunter handles on own
	void			BuildScheduleTestBits();
	NPC_STATE		SelectIdealState();
	int				SelectSchedule();
	int				SelectCombatSchedule();
	int				SelectSiegeSchedule();
	int				TranslateSchedule( int scheduleType );
	void			StartTask( const Task_t *pTask );
	void			RunTask( const Task_t *pTask );
	Activity		NPC_TranslateActivity( Activity baseAct );
	void			OnChangeActivity( Activity eNewActivity );
	
	void			HandleAnimEvent( animevent_t *pEvent );
	bool			HandleInteraction(int interactionType, void *data, CBaseCombatCharacter *pSourceEnt);

	void			PlayerHasIlluminatedNPC( CBasePlayer *pPlayer, float flDot );

	void			AddEntityRelationship( CBaseEntity *pEntity, Disposition_t nDisposition, int nPriority );
	float			EnemyDistTolerance() { return 100.0f; }

	bool			ScheduledMoveToGoalEntity( int scheduleType, CBaseEntity *pGoalEntity, Activity movementActivity );

	void			OnChangeHintGroup( string_t oldGroup, string_t newGroup );

	bool			IsUsingSiegeTargets()	{ return m_iszSiegeTargetName != NULL_STRING; }
	
	//---------------------------------
	// Inputs
	//---------------------------------
	void			InputDodge( inputdata_t &inputdata );
	void			InputFlankEnemy( inputdata_t &inputdata );
	void			InputDisableShooting( inputdata_t &inputdata );
	void			InputEnableShooting( inputdata_t &inputdata );
	void			InputFollowStrider( inputdata_t &inputdata );
	void			InputUseSiegeTargets( inputdata_t &inputdata );
	void			InputEnableSquadShootDelay( inputdata_t &inputdata );
	void			InputDisableSquadShootDelay( inputdata_t &inputdata );
	void			InputEnableUnplantedShooting( inputdata_t &inputdata );
	void			InputDisableUnplantedShooting( inputdata_t &inputdata );

	//---------------------------------
	// Combat
	//---------------------------------
	bool			FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL );
	bool			IsValidEnemy( CBaseEntity *pEnemy );
	
	Disposition_t	IRelationType( CBaseEntity *pTarget );
	int				IRelationPriority( CBaseEntity *pTarget );

	void			SetSquad( CAI_Squad *pSquad );

	bool			UpdateEnemyMemory( CBaseEntity *pEnemy, const Vector &position, CBaseEntity *pInformer = NULL );

	int				RangeAttack1Conditions( float flDot, float flDist );
	int				RangeAttack2Conditions( float flDot, float flDist );

	int				MeleeAttack1Conditions ( float flDot, float flDist );
	int				MeleeAttack1ConditionsVsEnemyInVehicle( CBaseCombatCharacter *pEnemy, float flDot );

	int				MeleeAttack2Conditions( float flDot, float flDist );

	bool			WeaponLOSCondition(const Vector &ownerPos, const Vector &targetPos, bool bSetConditions);
	bool			TestShootPosition(const Vector &vecShootPos, const Vector &targetPos );

	Vector			Weapon_ShootPosition();

	CBaseEntity *	MeleeAttack( float flDist, int iDamage, QAngle &qaViewPunch, Vector &vecVelocityPunch, int BloodOrigin );

	void			MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType );
	void			DoMuzzleFlash( int nAttachment );

	bool			CanShootThrough( const trace_t &tr, const Vector &vecTarget );

	int				CountRangedAttackers();
	void 			DelayRangedAttackers( float minDelay, float maxDelay, bool bForced = false );

	//---------------------------------
	//	Sounds & speech
	//---------------------------------
	void			AlertSound();
	void			PainSound( const CTakeDamageInfo &info );
	void			DeathSound( const CTakeDamageInfo &info );

	//---------------------------------
	// Damage handling
	//---------------------------------
	void			TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
	bool			IsHeavyDamage( const CTakeDamageInfo &info );
	int				OnTakeDamage( const CTakeDamageInfo &info );
	int				OnTakeDamage_Alive( const CTakeDamageInfo &info );
	void			Event_Killed( const CTakeDamageInfo &info );

	void			StartBleeding();
	inline bool		IsBleeding() { return m_bIsBleeding; }
	void			Explode();

	void			SetupGlobalModelData();
	
	//---------------------------------
	// Navigation & Movement
	//---------------------------------
	bool			OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval );
	float			MaxYawSpeed();
	bool			IsJumpLegal(const Vector &startPos, const Vector &apex, const Vector &endPos) const;
	float			GetJumpGravity() const		{ return 3.0f; }
	bool			ShouldProbeCollideAgainstEntity( CBaseEntity *pEntity );
	void            TaskFail( AI_TaskFailureCode_t code );
	void			TaskFail( const char *pszGeneralFailText )	{ TaskFail( MakeFailCode( pszGeneralFailText ) ); }

	CAI_BaseNPC *	GetEntity() { return this; }

	//---------------------------------
	// Magnade
	//---------------------------------	
	void	StriderBusterAttached( CBaseEntity *pAttached );
	void	StriderBusterDetached( CBaseEntity *pAttached );

private:

	void ConsiderFlinching( const CTakeDamageInfo &info );

	void TaskFindDodgeActivity();

	void GatherChargeConditions();
	void GatherIndoorOutdoorConditions();

	// Charge attack.
	bool ShouldCharge( const Vector &startPos, const Vector &endPos, bool useTime, bool bCheckForCancel );
	void ChargeLookAhead();
	float ChargeSteer();
	bool EnemyIsRightInFrontOfMe( CBaseEntity **pEntity );
	void ChargeDamage( CBaseEntity *pTarget );
	bool HandleChargeImpact( Vector vecImpact, CBaseEntity *pEntity );

	void BeginVolley( int nNum, float flStartTime );
	bool ShootFlechette( CBaseEntity *pTargetEntity, bool bSingleShot );
	bool ShouldSeekTarget( CBaseEntity *pTargetEntity, bool bStriderBuster );
	void GetShootDir( Vector &vecDir, const Vector &vecSrc, CBaseEntity *pTargetEntity, bool bStriderbuster, int nShotNum, bool bSingleShot );
	bool ClampShootDir( Vector &vecDir );

	void SetAim( const Vector &aimDir, float flInterval );
	void RelaxAim( float flInterval );
	void UpdateAim();
	void UpdateEyes();
	void LockBothEyes( float flDuration );
	void UnlockBothEyes( float flDuration );

	void TeslaThink();
	void BleedThink();
	void JostleVehicleThink();

	void FollowStrider( const char *szStrider );
	void FollowStrider( CNPC_Strider * pStrider );
	int NumHuntersInMySquad();

	bool CanPlantHere( const Vector &vecPos );

	//---------------------------------
	// Foot handling
	//---------------------------------
	Vector LeftFootHit( float eventtime );
	Vector RightFootHit( float eventtime );
	Vector BackFootHit( float eventtime );

	void FootFX( const Vector &origin );

	CBaseEntity *GetEnemyVehicle();
	bool IsCorporealEnemy( CBaseEntity *pEnemy );

	void PhysicsDamageEffect( const Vector &vecPos, const Vector &vecDir );
	bool PlayerFlashlightOnMyEyes( CBasePlayer *pPlayer );

	//-----------------------------------------------------
	// Conditions, Schedules, Tasks
	//-----------------------------------------------------
	enum 
	{
		SCHED_HUNTER_RANGE_ATTACK1 = BaseClass::NEXT_SCHEDULE,
		SCHED_HUNTER_RANGE_ATTACK2,
		SCHED_HUNTER_MELEE_ATTACK1,
		SCHED_HUNTER_DODGE,
		SCHED_HUNTER_CHASE_ENEMY,
		SCHED_HUNTER_CHASE_ENEMY_MELEE,
		SCHED_HUNTER_COMBAT_FACE,
		SCHED_HUNTER_FLANK_ENEMY,
		SCHED_HUNTER_CHANGE_POSITION,
		SCHED_HUNTER_CHANGE_POSITION_FINISH,
		SCHED_HUNTER_SIDESTEP,
		SCHED_HUNTER_PATROL,
		SCHED_HUNTER_FLINCH_STICKYBOMB,
		SCHED_HUNTER_STAGGER,
		SCHED_HUNTER_PATROL_RUN,
		SCHED_HUNTER_TAKE_COVER_FROM_ENEMY,
		SCHED_HUNTER_HIDE_UNDER_COVER,
		SCHED_HUNTER_FAIL_IMMEDIATE, // instant fail without waiting
		SCHED_HUNTER_CHARGE_ENEMY,
		SCHED_HUNTER_FAIL_CHARGE_ENEMY,
		SCHED_HUNTER_FOUND_ENEMY,
		SCHED_HUNTER_FOUND_ENEMY_ACK,
		SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER,
		SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER_LATENT,
		SCHED_HUNTER_GOTO_HINT,
		SCHED_HUNTER_CLEAR_HINTNODE,
		SCHED_HUNTER_FAIL_DODGE,
		SCHED_HUNTER_SIEGE_STAND,
		SCHED_HUNTER_CHANGE_POSITION_SIEGE,

		TASK_HUNTER_AIM = BaseClass::NEXT_TASK,
		TASK_HUNTER_FIND_DODGE_POSITION,
		TASK_HUNTER_DODGE,
		TASK_HUNTER_PRE_RANGE_ATTACK2,
		TASK_HUNTER_SHOOT_COMMIT,
		TASK_HUNTER_BEGIN_FLANK,
		TASK_HUNTER_ANNOUNCE_FLANK,
		TASK_HUNTER_STAGGER,
		TASK_HUNTER_CORNERED_TIMER,
		TASK_HUNTER_FIND_SIDESTEP_POSITION,
		TASK_HUNTER_CHARGE,
		TASK_HUNTER_CHARGE_DELAY,
		TASK_HUNTER_FINISH_RANGE_ATTACK,
		TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY,

		COND_HUNTER_SHOULD_PATROL = BaseClass::NEXT_CONDITION,
		COND_HUNTER_FORCED_FLANK_ENEMY,
		COND_HUNTER_FORCED_DODGE,
		COND_HUNTER_CAN_CHARGE_ENEMY,
		COND_HUNTER_HIT_BY_STICKYBOMB,
		COND_HUNTER_STAGGERED,
		COND_HUNTER_IS_INDOORS,
		COND_HUNTER_SEE_STRIDERBUSTER,
		COND_HUNTER_INCOMING_VEHICLE,
		COND_HUNTER_NEW_HINTGROUP,
		COND_HUNTER_CANT_PLANT,
		COND_HUNTER_SQUADMATE_FOUND_ENEMY,
	};

	enum HunterEyeStates_t
	{
		HUNTER_EYE_STATE_TOP_LOCKED = 0,
		HUNTER_EYE_STATE_BOTTOM_LOCKED,
		HUNTER_EYE_STATE_BOTH_LOCKED,
		HUNTER_EYE_STATE_BOTH_UNLOCKED,
	};

	string_t		m_iszFollowTarget;		// Name of the strider we should follow.
	CSimpleStopwatch m_BeginFollowDelay;

	int				m_nKillingDamageType;
	HunterEyeStates_t m_eEyeState;

	float			m_aimYaw;
	float			m_aimPitch;

	float			m_flShootAllowInterruptTime;
	float			m_flNextChargeTime;				// Prevents us from doing our threat display too often.
	float			m_flNextDamageTime;
	float			m_flNextSideStepTime;

	CSimpleSimTimer m_HeavyDamageDelay;
	CSimpleSimTimer m_FlinchTimer;
	CSimpleSimTimer m_EyeSwitchTimer;		// Controls how often we switch which eye is focusing on our enemy.

	bool			m_bTopMuzzle;	// Used to alternate between top muzzle FX and bottom muzzle FX.
	bool			m_bEnableSquadShootDelay;
	bool			m_bIsBleeding;

	Activity		m_eDodgeActivity;
	CSimpleSimTimer m_RundownDelay;
	CSimpleSimTimer m_IgnoreVehicleTimer;

	bool m_bDisableShooting;	// Range attack disabled via an input. Used for scripting melee attacks.
	
	bool m_bFlashlightInEyes;	// The player is shining the flashlight on our eyes.
	float m_flPupilDilateTime;	// When to dilate our pupils if the flashlight is no longer on our eyes.

	Vector m_vecEnemyLastSeen;
	Vector m_vecLastCanPlantHerePos;
	Vector m_vecStaggerDir;

	bool m_bPlanted;
	bool m_bLastCanPlantHere;
	bool m_bMissLeft;
	bool m_bEnableUnplantedShooting;

	static float	gm_flMinigunDistZ;
	static Vector	gm_vecLocalRelativePositionMinigun;

	static int gm_nTopGunAttachment;
	static int gm_nBottomGunAttachment;
	static int gm_nAimYawPoseParam;
	static int gm_nAimPitchPoseParam;
	static int gm_nBodyYawPoseParam;
	static int gm_nBodyPitchPoseParam;
	static int gm_nStaggerYawPoseParam;
	static int gm_nHeadCenterAttachment;
	static int gm_nHeadBottomAttachment;
	static float gm_flHeadRadius;

	static int gm_nUnplantedNode;
	static int gm_nPlantedNode;

	CAI_HunterEscortBehavior m_EscortBehavior;

	int m_nFlechettesQueued;
	int m_nClampedShots;				// The number of consecutive shots fired at an out-of-max yaw target.

	float m_flNextRangeAttack2Time;		// Time when we can fire another volley of flechettes.
	float m_flNextFlechetteTime;		// Time to fire the next flechette in this volley.
	
	float m_flNextMeleeTime;
	float m_flTeslaStopTime;

	string_t m_iszCurrentExpression;

	// buster fu	
	CUtlVector< EHANDLE >	m_hAttachedBusters;		// List of busters attached to us
	float m_fCorneredTimer; ///< hunter was cornered when fleeing player; it won't flee again until this time

	CSimpleSimTimer m_CheckHintGroupTimer;

	DEFINE_CUSTOM_AI;

	DECLARE_DATADESC();

	friend class CAI_HunterEscortBehavior;
	friend class CHunterMaker;

	bool	m_bInLargeOutdoorMap;
	float	m_flTimeSawEnemyAgain;

	// Sounds
	//CSoundPatch	*m_pGunFiringSound;

	CUtlVector<EHANDLE> m_pSiegeTargets;
	string_t	m_iszSiegeTargetName;
	float		m_flTimeNextSiegeTargetAttack;
	EHANDLE		m_hCurrentSiegeTarget;
	
	EHANDLE		m_hHitByVehicle;
};


LINK_ENTITY_TO_CLASS( npc_hunter, CNPC_Hunter );


BEGIN_DATADESC( CNPC_Hunter )

	DEFINE_KEYFIELD( m_iszFollowTarget, FIELD_STRING, "FollowTarget" ),

	DEFINE_FIELD( m_aimYaw,				FIELD_FLOAT ),
	DEFINE_FIELD( m_aimPitch,				FIELD_FLOAT ),

	DEFINE_FIELD( m_flShootAllowInterruptTime, FIELD_TIME ),
	DEFINE_FIELD( m_flNextChargeTime, FIELD_TIME ),
	//DEFINE_FIELD( m_flNextDamageTime, FIELD_TIME ),
	DEFINE_FIELD( m_flNextSideStepTime, FIELD_TIME ),

	DEFINE_EMBEDDED( m_HeavyDamageDelay ),
	DEFINE_EMBEDDED( m_FlinchTimer ),

	DEFINE_FIELD( m_eEyeState, FIELD_INTEGER ),

	DEFINE_FIELD( m_bTopMuzzle, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_bEnableSquadShootDelay, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_bIsBleeding, FIELD_BOOLEAN ),

	DEFINE_FIELD( m_bDisableShooting, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_bFlashlightInEyes, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_flPupilDilateTime, FIELD_TIME ),

	DEFINE_FIELD( m_vecEnemyLastSeen, FIELD_POSITION_VECTOR ),
	DEFINE_FIELD( m_vecLastCanPlantHerePos, FIELD_POSITION_VECTOR ),
	DEFINE_FIELD( m_vecStaggerDir, FIELD_VECTOR ),
	
	DEFINE_FIELD( m_bPlanted, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_bLastCanPlantHere, FIELD_BOOLEAN ),
	//DEFINE_FIELD( m_bMissLeft, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_bEnableUnplantedShooting, FIELD_BOOLEAN ),

	DEFINE_FIELD( m_nKillingDamageType, FIELD_INTEGER ),
	DEFINE_FIELD( m_eDodgeActivity, FIELD_INTEGER ),
	DEFINE_EMBEDDED( m_RundownDelay ),
	DEFINE_EMBEDDED( m_IgnoreVehicleTimer ),

	DEFINE_FIELD( m_flNextMeleeTime, FIELD_TIME ),
	DEFINE_FIELD( m_flTeslaStopTime, FIELD_TIME ),

	// (auto saved by AI)
	//DEFINE_FIELD( m_EscortBehavior, FIELD_EMBEDDED ),	

	DEFINE_FIELD( m_iszCurrentExpression, FIELD_STRING ),

	DEFINE_FIELD( m_fCorneredTimer, FIELD_TIME),

	DEFINE_EMBEDDED( m_CheckHintGroupTimer ),

	// (Recomputed in Precache())
	//DEFINE_FIELD( m_bInLargeOutdoorMap, FIELD_BOOLEAN ), 
	DEFINE_FIELD( m_flTimeSawEnemyAgain, FIELD_TIME ),

	//DEFINE_SOUNDPATCH( m_pGunFiringSound ),

	//DEFINE_UTLVECTOR( m_pSiegeTarget, FIELD_EHANDLE ),
	DEFINE_FIELD( m_iszSiegeTargetName, FIELD_STRING ),
	DEFINE_FIELD( m_flTimeNextSiegeTargetAttack, FIELD_TIME ),
	DEFINE_FIELD( m_hCurrentSiegeTarget, FIELD_EHANDLE ),
	DEFINE_FIELD( m_hHitByVehicle, FIELD_EHANDLE ),

	DEFINE_EMBEDDED( m_BeginFollowDelay ),
	DEFINE_EMBEDDED( m_EyeSwitchTimer ),

	DEFINE_FIELD( m_nFlechettesQueued, FIELD_INTEGER ),
	DEFINE_FIELD( m_nClampedShots, FIELD_INTEGER ),
	DEFINE_FIELD( m_flNextRangeAttack2Time, FIELD_TIME ),
	DEFINE_FIELD( m_flNextFlechetteTime, FIELD_TIME ),
	DEFINE_UTLVECTOR( m_hAttachedBusters, FIELD_EHANDLE ),
	DEFINE_UTLVECTOR( m_pSiegeTargets, FIELD_EHANDLE ),

	// inputs
	DEFINE_INPUTFUNC( FIELD_VOID, "Dodge", InputDodge ),
	DEFINE_INPUTFUNC( FIELD_VOID, "FlankEnemy", InputFlankEnemy ),
	DEFINE_INPUTFUNC( FIELD_STRING, "DisableShooting", InputDisableShooting ),
	DEFINE_INPUTFUNC( FIELD_STRING, "EnableShooting", InputEnableShooting ),
	DEFINE_INPUTFUNC( FIELD_STRING, "FollowStrider", InputFollowStrider ),
	DEFINE_INPUTFUNC( FIELD_STRING, "UseSiegeTargets", InputUseSiegeTargets ),
	DEFINE_INPUTFUNC( FIELD_VOID, "EnableSquadShootDelay", InputEnableSquadShootDelay ),
	DEFINE_INPUTFUNC( FIELD_VOID, "DisableSquadShootDelay", InputDisableSquadShootDelay ),
	DEFINE_INPUTFUNC( FIELD_VOID, "EnableUnplantedShooting", InputEnableUnplantedShooting ),
	DEFINE_INPUTFUNC( FIELD_VOID, "DisableUnplantedShooting", InputDisableUnplantedShooting ),

	// Function Pointers
	DEFINE_THINKFUNC( TeslaThink ),
	DEFINE_THINKFUNC( BleedThink ),
	DEFINE_THINKFUNC( JostleVehicleThink ),

END_DATADESC()

//-----------------------------------------------------------------------------

int CNPC_Hunter::gm_nUnplantedNode = 0;
int CNPC_Hunter::gm_nPlantedNode = 0;

int CNPC_Hunter::gm_nAimYawPoseParam = -1;
int CNPC_Hunter::gm_nAimPitchPoseParam = -1;
int CNPC_Hunter::gm_nBodyYawPoseParam = -1;
int CNPC_Hunter::gm_nBodyPitchPoseParam = -1;
int CNPC_Hunter::gm_nStaggerYawPoseParam = -1;
int CNPC_Hunter::gm_nHeadCenterAttachment = -1;
int CNPC_Hunter::gm_nHeadBottomAttachment = -1;
float CNPC_Hunter::gm_flHeadRadius = 0;

int CNPC_Hunter::gm_nTopGunAttachment = -1;
int CNPC_Hunter::gm_nBottomGunAttachment = -1;

float CNPC_Hunter::gm_flMinigunDistZ;
Vector CNPC_Hunter::gm_vecLocalRelativePositionMinigun;

//-----------------------------------------------------------------------------

static CUtlVector<CNPC_Hunter *> g_Hunters;
float g_TimeLastDistributeFreeHunters = -1;
const float FREE_HUNTER_DISTRIBUTE_INTERVAL = 2;

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CNPC_Hunter::CNPC_Hunter()
{
	g_Hunters.AddToTail( this );
	g_TimeLastDistributeFreeHunters = -1;
	m_flTimeSawEnemyAgain = HUNTER_SEE_ENEMY_TIME_INVALID;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CNPC_Hunter::~CNPC_Hunter()
{
	g_Hunters.FindAndRemove( this );
	g_TimeLastDistributeFreeHunters = -1;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::Precache()
{
	PrecacheModel( "models/hunter.mdl" );
	PropBreakablePrecacheAll( MAKE_STRING("models/hunter.mdl") );

	PrecacheScriptSound( "NPC_Hunter.Idle" );
	PrecacheScriptSound( "NPC_Hunter.Scan" );
	PrecacheScriptSound( "NPC_Hunter.Alert" );
	PrecacheScriptSound( "NPC_Hunter.Pain" );
	PrecacheScriptSound( "NPC_Hunter.PreCharge" );
	PrecacheScriptSound( "NPC_Hunter.Angry" );
	PrecacheScriptSound( "NPC_Hunter.Death" );
	PrecacheScriptSound( "NPC_Hunter.FireMinigun" );
	PrecacheScriptSound( "NPC_Hunter.Footstep" );
	PrecacheScriptSound( "NPC_Hunter.BackFootstep" );
	PrecacheScriptSound( "NPC_Hunter.FlechetteVolleyWarn" );
	PrecacheScriptSound( "NPC_Hunter.FlechetteShoot" );
	PrecacheScriptSound( "NPC_Hunter.FlechetteShootLoop" );
	PrecacheScriptSound( "NPC_Hunter.FlankAnnounce" );
	PrecacheScriptSound( "NPC_Hunter.MeleeAnnounce" );
	PrecacheScriptSound( "NPC_Hunter.MeleeHit" );
	PrecacheScriptSound( "NPC_Hunter.TackleAnnounce" );
	PrecacheScriptSound( "NPC_Hunter.TackleHit" );
	PrecacheScriptSound( "NPC_Hunter.ChargeHitEnemy" );
	PrecacheScriptSound( "NPC_Hunter.ChargeHitWorld" );
	PrecacheScriptSound( "NPC_Hunter.FoundEnemy" );
	PrecacheScriptSound( "NPC_Hunter.FoundEnemyAck" );
	PrecacheScriptSound( "NPC_Hunter.DefendStrider" );
	PrecacheScriptSound( "NPC_Hunter.HitByVehicle" );

	PrecacheParticleSystem( "hunter_muzzle_flash" );
	PrecacheParticleSystem( "blood_impact_synth_01" );
	PrecacheParticleSystem( "blood_impact_synth_01_arc_parent" );
	PrecacheParticleSystem( "blood_spurt_synth_01" );
	PrecacheParticleSystem( "blood_drip_synth_01" );

	PrecacheInstancedScene( "scenes/npc/hunter/hunter_scan.vcd" );
	PrecacheInstancedScene( "scenes/npc/hunter/hunter_eyeclose.vcd" );
	PrecacheInstancedScene( "scenes/npc/hunter/hunter_roar.vcd" );
	PrecacheInstancedScene( "scenes/npc/hunter/hunter_pain.vcd" );
	PrecacheInstancedScene( "scenes/npc/hunter/hunter_eyedarts_top.vcd" );
	PrecacheInstancedScene( "scenes/npc/hunter/hunter_eyedarts_bottom.vcd" );
	
	PrecacheMaterial( "effects/water_highlight" );

	UTIL_PrecacheOther( "hunter_flechette" );
	UTIL_PrecacheOther( "sparktrail" );

	m_bInLargeOutdoorMap = false;
	if( !Q_strnicmp( STRING(gpGlobals->mapname), "ep2_outland_12", 14) )
	{
		m_bInLargeOutdoorMap = true;
	}

	BaseClass::Precache();
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::Spawn()
{
	Precache();

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

	//m_debugOverlays |= OVERLAY_NPC_ROUTE_BIT | OVERLAY_BBOX_BIT | OVERLAY_PIVOT_BIT;

	SetHullType( HULL_MEDIUM_TALL );
	SetHullSizeNormal();
	SetDefaultEyeOffset();
	
	SetNavType( NAV_GROUND );
	m_flGroundSpeed	= 500;
	m_NPCState = NPC_STATE_NONE;

	SetBloodColor( DONT_BLEED );
	
	m_iHealth = m_iMaxHealth = sk_hunter_health.GetInt();

	m_flFieldOfView = HUNTER_FOV_DOT;

	SetSolid( SOLID_BBOX );
	AddSolidFlags( FSOLID_NOT_STANDABLE );
	SetMoveType( MOVETYPE_STEP );

	SetupGlobalModelData();
	
	CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_SQUAD | bits_CAP_ANIMATEDFACE );
	CapabilitiesAdd( bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_INNATE_RANGE_ATTACK2 | bits_CAP_INNATE_MELEE_ATTACK1 );
	CapabilitiesAdd( bits_CAP_SKIP_NAV_GROUND_CHECK );

	if ( !hunter_allow_dissolve.GetBool() )
	{
		AddEFlags( EFL_NO_DISSOLVE );
	}

	if( hunter_allow_nav_jump.GetBool() )
	{
		CapabilitiesAdd( bits_CAP_MOVE_JUMP );
	}

	NPCInit();

	m_bEnableSquadShootDelay = true;

	m_flDistTooFar = hunter_flechette_max_range.GetFloat();

	// Discard time must be greater than free knowledge duration. Make it double.
	float freeKnowledge = hunter_free_knowledge.GetFloat();
	if ( freeKnowledge < GetEnemies()->GetEnemyDiscardTime() )
	{
		GetEnemies()->SetEnemyDiscardTime( MAX( freeKnowledge + 0.1, AI_DEF_ENEMY_DISCARD_TIME ) );
	}
	GetEnemies()->SetFreeKnowledgeDuration( freeKnowledge );

	// Find out what strider we should follow, if any.
	if ( m_iszFollowTarget != NULL_STRING )
	{
		m_BeginFollowDelay.Set( .1 ); // Allow time for strider to spawn
	}

	//if ( !m_pGunFiringSound )
	//{
	//	CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
	//	CPASAttenuationFilter filter( this );
	//
	//	m_pGunFiringSound = controller.SoundCreate( filter, entindex(), "NPC_Hunter.FlechetteShootLoop" );
	//	controller.Play( m_pGunFiringSound, 0.0, 100 );
	//}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::UpdateEfficiency( bool bInPVS )
{
	SetEfficiency( ( GetSleepState() != AISS_AWAKE ) ? AIE_DORMANT : AIE_NORMAL );
	SetMoveEfficiency( AIME_NORMAL );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Hunter::CreateBehaviors()
{
	AddBehavior( &m_EscortBehavior );

	return BaseClass::CreateBehaviors();
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::SetupGlobalModelData()
{
	if ( gm_nBodyYawPoseParam != -1 )
		return;

	gm_nAimYawPoseParam = LookupPoseParameter( "aim_yaw" );
	gm_nAimPitchPoseParam = LookupPoseParameter( "aim_pitch" );

	gm_nBodyYawPoseParam = LookupPoseParameter( "body_yaw" );
	gm_nBodyPitchPoseParam = LookupPoseParameter( "body_pitch" );

	gm_nTopGunAttachment = LookupAttachment( "top_eye" );
	gm_nBottomGunAttachment = LookupAttachment( "bottom_eye" );
	gm_nStaggerYawPoseParam = LookupAttachment( "stagger_yaw" );
	
	gm_nHeadCenterAttachment = LookupAttachment( "head_center" );
	gm_nHeadBottomAttachment = LookupAttachment( "head_radius_measure" );

	// Measure the radius of the head.	
	Vector vecHeadCenter;
	Vector vecHeadBottom;
	GetAttachment( gm_nHeadCenterAttachment, vecHeadCenter );
	GetAttachment( gm_nHeadBottomAttachment, vecHeadBottom );
	gm_flHeadRadius = ( vecHeadCenter - vecHeadBottom ).Length();

	int nSequence = SelectWeightedSequence( ACT_HUNTER_RANGE_ATTACK2_UNPLANTED );
	gm_nUnplantedNode = GetEntryNode( nSequence );

	nSequence = SelectWeightedSequence( ACT_RANGE_ATTACK2 );
	gm_nPlantedNode = GetEntryNode( nSequence );

	CollisionProp()->SetSurroundingBoundsType( USE_HITBOXES );
}


//-----------------------------------------------------------------------------
// Shuts down looping sounds when we are killed in combat or deleted.
//-----------------------------------------------------------------------------
void CNPC_Hunter::StopLoopingSounds()
{
	BaseClass::StopLoopingSounds();

	//if ( m_pGunFiringSound )
	//{
	//	CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
	//	controller.SoundDestroy( m_pGunFiringSound );
	//	m_pGunFiringSound = NULL;
	//}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::OnRestore()
{
	BaseClass::OnRestore();
	SetupGlobalModelData();
	CreateVPhysics();
	
	if ( IsBleeding() )
	{
		StartBleeding();
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::IdleSound()
{
	if ( HasCondition( COND_LOST_ENEMY ) )
	{
		EmitSound( "NPC_Hunter.Scan" );
	}
	else
	{
		EmitSound( "NPC_Hunter.Idle" );
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Hunter::ShouldPlayIdleSound()
{
	if ( random->RandomInt(0, 99) == 0 && !HasSpawnFlags( SF_NPC_GAG ) )
		return true;
	
	return false;
}


//-----------------------------------------------------------------------------
// Stay facing our enemy when close enough.
//-----------------------------------------------------------------------------
bool CNPC_Hunter::OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval )
{
	if ( GetActivity() == ACT_TRANSITION )
	{
		// No turning while in transitions.
		return true;
	}

 	bool bSideStepping = IsCurSchedule( SCHED_HUNTER_SIDESTEP, false );
 	
  	// FIXME: this will break scripted sequences that walk when they have an enemy
  	if ( GetEnemy() &&
  		( bSideStepping ||
  		( ( ( GetNavigator()->GetMovementActivity() == ACT_RUN ) || ( GetNavigator()->GetMovementActivity() == ACT_WALK ) ) &&
		  !IsCurSchedule( SCHED_HUNTER_TAKE_COVER_FROM_ENEMY, false ) ) ) )
	{
		Vector vecEnemyLKP = GetEnemyLKP();
		
		// Face my enemy if we're close enough
		if ( bSideStepping || UTIL_DistApprox( vecEnemyLKP, GetAbsOrigin() ) < HUNTER_FACE_ENEMY_DIST )
		{
			AddFacingTarget( GetEnemy(), vecEnemyLKP, 1.0, 0.2 );
		}
	}

	return BaseClass::OverrideMoveFacing( move, flInterval );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::PostNPCInit()
{
	BaseClass::PostNPCInit();

	IPhysicsObject *pPhysObject = VPhysicsGetObject();
	Assert( pPhysObject );
	if ( pPhysObject )
	{
		pPhysObject->SetMass( 600.0 );
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::Activate()
{
	BaseClass::Activate();

	s_iszStriderBusterClassname = AllocPooledString( "weapon_striderbuster" );
	s_iszStriderClassname  = AllocPooledString( "npc_strider" );
	s_iszMagnadeClassname = AllocPooledString( "npc_grenade_magna" );
	s_iszPhysPropClassname = AllocPooledString( "prop_physics" );
	s_iszHuntersToRunOver = AllocPooledString( "hunters_to_run_over" );
	
	// If no one has initialized the hunters to run over counter, just zero it out.
	if ( !GlobalEntity_IsInTable( s_iszHuntersToRunOver ) )
	{
		GlobalEntity_Add( s_iszHuntersToRunOver, gpGlobals->mapname, GLOBAL_ON );
		GlobalEntity_SetCounter( s_iszHuntersToRunOver, 0 );
	}

	CMissile::AddCustomDetonator( this, ( GetHullMaxs().AsVector2D() - GetHullMins().AsVector2D() ).Length() * 0.5, GetHullHeight() );

	SetupGlobalModelData();
	
	if ( gm_flMinigunDistZ == 0 )
	{
		// Have to create a virgin hunter to ensure proper pose
		CNPC_Hunter *pHunter = (CNPC_Hunter *)CreateEntityByName( "npc_hunter" );
		Assert(pHunter);
		pHunter->Spawn();

		pHunter->SetActivity( ACT_WALK );
		pHunter->InvalidateBoneCache();

		// Currently just using the gun for the vertical component!
		Vector defEyePos;
		pHunter->GetAttachment( "minigunbase", defEyePos );
		gm_flMinigunDistZ = defEyePos.z - pHunter->GetAbsOrigin().z;

		Vector position;
		pHunter->GetAttachment( gm_nTopGunAttachment, position );
		VectorITransform( position, pHunter->EntityToWorldTransform(), gm_vecLocalRelativePositionMinigun );
		UTIL_Remove( pHunter );
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::UpdateOnRemove()
{
	CMissile::RemoveCustomDetonator( this );
	BaseClass::UpdateOnRemove();
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
Class_T CNPC_Hunter::Classify()
{
	return CLASS_COMBINE_HUNTER;
}

//-----------------------------------------------------------------------------
// Compensate for the hunter's long legs by moving the bodytarget up to his head.
//-----------------------------------------------------------------------------
Vector CNPC_Hunter::BodyTarget( const Vector &posSrc, bool bNoisy /*= true*/ )
{ 
	Vector vecResult;
	QAngle vecAngle;
	GetAttachment( gm_nHeadCenterAttachment, vecResult, vecAngle );

	if ( bNoisy )
	{
		float rand1 = random->RandomFloat( 0, gm_flHeadRadius ) + random->RandomFloat( 0, gm_flHeadRadius );
		return vecResult + RandomVector( -rand1, rand1 );
	}

	return vecResult;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int	CNPC_Hunter::DrawDebugTextOverlays()
{
	int text_offset = BaseClass::DrawDebugTextOverlays();

	if (m_debugOverlays & OVERLAY_TEXT_BIT)
	{
		EntityText( text_offset, CFmtStr("%s", m_bPlanted ? "Planted" : "Unplanted" ), 0 );
		text_offset++;

		EntityText( text_offset, CFmtStr("Eye state: %d", m_eEyeState ), 0 );
		text_offset++;

		if( IsUsingSiegeTargets() )
		{
			EntityText( text_offset, CFmtStr("Next Siege Attempt:%f", m_flTimeNextSiegeTargetAttack - gpGlobals->curtime ), 0 );
			text_offset++;
		}
	}

	return text_offset;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::LockBothEyes( float flDuration )
{
	m_eEyeState = HUNTER_EYE_STATE_BOTH_LOCKED;
	m_EyeSwitchTimer.Set( flDuration );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::UnlockBothEyes( float flDuration )
{
	m_eEyeState = HUNTER_EYE_STATE_BOTH_UNLOCKED;
	m_EyeSwitchTimer.Set( flDuration );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::OnChangeActivity( Activity eNewActivity )
{
	m_EyeSwitchTimer.Force();
	
	BaseClass::OnChangeActivity( eNewActivity );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::UpdateEyes()
{
	// If the eyes are controlled by a script, do nothing.
	if ( GetState() == NPC_STATE_SCRIPT )
		return;

	if ( m_EyeSwitchTimer.Expired() )
	{
		RemoveActorFromScriptedScenes( this, false );
		
		if ( GetActivity() == ACT_IDLE )
		{
			// Idles have eye motion baked in.
			m_eEyeState = HUNTER_EYE_STATE_BOTH_LOCKED;
		}
		else if ( GetEnemy() == NULL )
		{
			m_eEyeState = HUNTER_EYE_STATE_BOTH_UNLOCKED;
		}
		else if ( m_eEyeState == HUNTER_EYE_STATE_BOTH_LOCKED )
		{
			if ( random->RandomInt( 0, 1 ) == 0 )
			{
				m_eEyeState = HUNTER_EYE_STATE_TOP_LOCKED;
			}
			else
			{
				m_eEyeState = HUNTER_EYE_STATE_BOTTOM_LOCKED;
			}
		}
		else if ( m_eEyeState == HUNTER_EYE_STATE_TOP_LOCKED )
		{
			m_eEyeState = HUNTER_EYE_STATE_BOTTOM_LOCKED;
		}
		else if ( m_eEyeState == HUNTER_EYE_STATE_BOTTOM_LOCKED )
		{
			m_eEyeState = HUNTER_EYE_STATE_TOP_LOCKED;
		}

		if ( ( m_eEyeState == HUNTER_EYE_STATE_BOTTOM_LOCKED ) || ( m_eEyeState == HUNTER_EYE_STATE_BOTH_UNLOCKED ) )
		{
			SetExpression( "scenes/npc/hunter/hunter_eyedarts_top.vcd" );
		}

		if ( ( m_eEyeState == HUNTER_EYE_STATE_TOP_LOCKED ) || ( m_eEyeState == HUNTER_EYE_STATE_BOTH_UNLOCKED ) )
		{
			SetExpression( "scenes/npc/hunter/hunter_eyedarts_bottom.vcd" );
		}

		m_EyeSwitchTimer.Set( random->RandomFloat( 1.0f, 3.0f ) );
	}

	/*Vector vecEyePos;
	Vector vecEyeDir;

	GetAttachment( gm_nTopGunAttachment, vecEyePos, &vecEyeDir );
	NDebugOverlay::Line( vecEyePos, vecEyePos + vecEyeDir * 36, 255, 0, 0, 0, 0.1 );

	GetAttachment( gm_nBottomGunAttachment, vecEyePos, &vecEyeDir );
	NDebugOverlay::Line( vecEyePos, vecEyePos + vecEyeDir * 36, 255, 0, 0, 0, 0.1 );*/
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::NPCThink()
{
	BaseClass::NPCThink();

	// Update our planted/unplanted state.
	m_bPlanted = ( GetEntryNode( GetSequence() ) == gm_nPlantedNode );

	UpdateAim();
	UpdateEyes();
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::PrescheduleThink()
{
	BaseClass::PrescheduleThink();
	
	if ( m_BeginFollowDelay.Expired() )
	{
		FollowStrider( STRING( m_iszFollowTarget ) );
		m_BeginFollowDelay.Stop();
	}

	m_EscortBehavior.CheckBreakEscort();

	// If we're being blinded by the flashlight, see if we should stop
	if ( m_bFlashlightInEyes )
	{
		if ( m_flPupilDilateTime < gpGlobals->curtime )
		{
 			CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
 			if ( pPlayer && !pPlayer->IsIlluminatedByFlashlight( this, NULL ) || !PlayerFlashlightOnMyEyes( pPlayer ) )
			{
				//Msg( "NOT SHINING FLASHLIGHT ON ME\n" );
			
				// Remove the actor from the flashlight scene
				RemoveActorFromScriptedScenes( this, true, false, "scenes/npc/hunter/hunter_eyeclose.vcd" );
				m_bFlashlightInEyes = false;
			}
		}
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::GatherChargeConditions()
{
	ClearCondition( COND_HUNTER_CAN_CHARGE_ENEMY );

	if ( !hunter_charge.GetBool() )
		return;
		
	if ( !GetEnemy() )
		return;

	if ( GetHintGroup() != NULL_STRING )
		return;
	
	if ( !HasCondition( COND_SEE_ENEMY ) )
		return;
	
	if ( !hunter_charge_test.GetBool() && gpGlobals->curtime < m_flNextChargeTime )
		return;

	// No charging Alyx or Barney
	if( GetEnemy()->Classify() == CLASS_PLAYER_ALLY_VITAL )
		return;

	if ( m_EscortBehavior.GetEscortTarget() && GetEnemy()->MyCombatCharacterPointer() && !GetEnemy()->MyCombatCharacterPointer()->FInViewCone( this ) )
		return;

	if ( ShouldCharge( GetAbsOrigin(), GetEnemy()->GetAbsOrigin(), true, false ) )
	{
		SetCondition( COND_HUNTER_CAN_CHARGE_ENEMY );
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::GatherConditions()
{
	GatherIndoorOutdoorConditions();
	GatherChargeConditions();

	BaseClass::GatherConditions();

	// Enemy LKP that doesn't get updated by the free knowledge code.
	// Used for shooting at where our enemy was when we can't see them.
	ClearCondition( COND_HUNTER_INCOMING_VEHICLE );
	if ( m_IgnoreVehicleTimer.Expired() && GetEnemy() && HasCondition( COND_SEE_ENEMY ) )
	{
		CBaseEntity *pVehicle = GetEnemyVehicle();
		if ( ( pVehicle ) && ( GlobalEntity_GetCounter( s_iszHuntersToRunOver ) <= 0 ) )
		{
			static float timeDrawnArrow;

			// Extrapolate the position of the vehicle and see if it's heading toward us.
			float predictTime = hunter_dodge_warning.GetFloat();
			Vector2D vecFuturePos = pVehicle->GetAbsOrigin().AsVector2D() + pVehicle->GetSmoothedVelocity().AsVector2D() * predictTime;
			if ( pVehicle->GetSmoothedVelocity().LengthSqr() > Square( 200 ) )
			{
				float t = 0;
				Vector2D vDirMovement = pVehicle->GetSmoothedVelocity().AsVector2D();
				if ( hunter_dodge_debug.GetBool() )
				{
					NDebugOverlay::Line( pVehicle->GetAbsOrigin(), pVehicle->GetAbsOrigin() + pVehicle->GetSmoothedVelocity(), 255, 255, 255, true, .1 );
				}
				vDirMovement.NormalizeInPlace();
				Vector2D vDirToHunter = GetAbsOrigin().AsVector2D() - pVehicle->GetAbsOrigin().AsVector2D();
				vDirToHunter.NormalizeInPlace();
				if ( DotProduct2D( vDirMovement, vDirToHunter ) > hunter_dodge_warning_cone.GetFloat() && 
					 CalcDistanceSqrToLine2D( GetAbsOrigin().AsVector2D(), pVehicle->GetAbsOrigin().AsVector2D(), vecFuturePos, &t ) < Square( hunter_dodge_warning_width.GetFloat() * .5 ) && 
					 t > 0.0 && t < 1.0 )
				{
					if ( fabs( predictTime - hunter_dodge_warning.GetFloat() ) < .05 || random->RandomInt( 0, 3 ) )
					{
						SetCondition( COND_HUNTER_INCOMING_VEHICLE );
					}
					else
					{
						if ( hunter_dodge_debug. GetBool() )
						{
							Msg( "Hunter %d failing dodge (ignore)\n", entindex() );
						}
					}

					if ( hunter_dodge_debug. GetBool() )
					{
						NDebugOverlay::Cross3D( EyePosition(), 100, 255, 255, 255, true, .1 );
						if ( timeDrawnArrow != gpGlobals->curtime )
						{
							timeDrawnArrow = gpGlobals->curtime;
							Vector vEndpoint( vecFuturePos.x, vecFuturePos.y, UTIL_GetLocalPlayer()->WorldSpaceCenter().z - 24 );
							NDebugOverlay::HorzArrow( UTIL_GetLocalPlayer()->WorldSpaceCenter() - Vector(0, 0, 24), vEndpoint, hunter_dodge_warning_width.GetFloat(), 255, 0, 0, 64, true, .1 );
						}
					}
				}
				else if ( hunter_dodge_debug.GetBool() )
				{
					if ( t <= 0 )
					{
						NDebugOverlay::Cross3D( EyePosition(), 100, 0, 0, 255, true, .1 );
					}
					else
					{
						NDebugOverlay::Cross3D( EyePosition(), 100, 0, 255, 255, true, .1 );
					}
				}
			}
			else if ( hunter_dodge_debug.GetBool() )
			{
				NDebugOverlay::Cross3D( EyePosition(), 100, 0, 255, 0, true, .1 );
			}
			if ( hunter_dodge_debug. GetBool() )
			{
				if ( timeDrawnArrow != gpGlobals->curtime )
				{
					timeDrawnArrow = gpGlobals->curtime;
					Vector vEndpoint( vecFuturePos.x, vecFuturePos.y, UTIL_GetLocalPlayer()->WorldSpaceCenter().z - 24 );
					NDebugOverlay::HorzArrow( UTIL_GetLocalPlayer()->WorldSpaceCenter() - Vector(0, 0, 24), vEndpoint, hunter_dodge_warning_width.GetFloat(), 127, 127, 127, 64, true, .1 );
				}
			}

		}

		m_vecEnemyLastSeen = GetEnemy()->GetAbsOrigin();
	}

	if( !HasCondition(COND_ENEMY_OCCLUDED) )
	{
		// m_flTimeSawEnemyAgain always tells us what time I first saw this
		// enemy again after some period of not seeing them. This is used to
		// compute how long the enemy has been visible to me THIS TIME. 
		// Every time I lose sight of the enemy this time is set invalid until
		// I see the enemy again and record that time.
		if( m_flTimeSawEnemyAgain == HUNTER_SEE_ENEMY_TIME_INVALID )
		{
			m_flTimeSawEnemyAgain = gpGlobals->curtime;
		}
	}
	else
	{
		m_flTimeSawEnemyAgain = HUNTER_SEE_ENEMY_TIME_INVALID;
	}

	ManageSiegeTargets();
}

//-----------------------------------------------------------------------------
// Search all entities in the map
//-----------------------------------------------------------------------------
void CNPC_Hunter::CollectSiegeTargets()
{
	CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, m_iszSiegeTargetName );

	while( pTarget != NULL )
	{
		if( pTarget->Classify() == CLASS_BULLSEYE )
		{
			m_pSiegeTargets.AddToTail( pTarget );
		}

		pTarget = gEntList.FindEntityByName( pTarget, m_iszSiegeTargetName );
	};

	if( m_pSiegeTargets.Count() < 1 )
	{
		m_iszSiegeTargetName = NULL_STRING;		// And stop trying!
	}
}

//-----------------------------------------------------------------------------
// For use when Hunters are outside and the player is inside a structure
// Create a temporary bullseye in a location that makes it seem like
// I am aware of the location of a player I cannot see. (Then fire at
// at this bullseye, thus laying 'siege' to the part of the building he 
// is in.) The locations are copied from suitable info_target entities.
// (these should be placed in exterior windows and doorways so that 
// the Hunter fires into the building through these apertures)
//-----------------------------------------------------------------------------
void CNPC_Hunter::ManageSiegeTargets()
{
	if( gpGlobals->curtime < m_flTimeNextSiegeTargetAttack )
		return;

	if( m_pSiegeTargets.Count() == 0 )
	{
		// If my list of siege targets is empty, go and cache all of them now
		// so that I don't have to search the world every time.
		CollectSiegeTargets();

		if( m_pSiegeTargets.Count() == 0 )
			return;
	}

	m_flTimeNextSiegeTargetAttack = gpGlobals->curtime + (hunter_siege_frequency.GetFloat() * RandomFloat( 0.8f, 1.2f) );
	CBasePlayer *pPlayer = AI_GetSinglePlayer();

	// Start by assuming we are not going to create a siege target
	bool bCreateSiegeTarget = false;
	if( GetEnemy() == NULL )
	{
		// If I have no enemy at all, give it a try.
		bCreateSiegeTarget = true;
	}

	if( bCreateSiegeTarget )
	{
		// We've decided that the situation calls for a siege target. So, we dig through all of my siege targets and
		// take the closest one to the player that the player can see! (Obey they bullseye's FOV)
		float flClosestDistSqr = Square( 1200.0f ); // Only use siege targets within 100 feet of player
		CBaseEntity *pSiegeTargetLocation = NULL;
		int iTraces = 0;
		for( int i = 0 ; i < m_pSiegeTargets.Count() ; i++ )
		{
			CBaseEntity *pCandidate = m_pSiegeTargets[i];
			if ( pCandidate == NULL )
				continue;

			float flDistSqr = pCandidate->GetAbsOrigin().DistToSqr(pPlayer->GetAbsOrigin());

			if( flDistSqr < flClosestDistSqr )
			{
				// CollectSiegeTargets() guarantees my list is populated only with bullseye entities.
				CNPC_Bullseye *pBullseye = dynamic_cast<CNPC_Bullseye*>(pCandidate);
				if( !pBullseye->FInViewCone(this) )
					continue;

				if( pPlayer->FVisible(pCandidate) )
				{
					iTraces++;// Only counting these as a loose perf measurement
					flClosestDistSqr = flDistSqr;
					pSiegeTargetLocation = pCandidate;
				}
			}
		}

		if( pSiegeTargetLocation != NULL )
		{
			// Ditch any leftover siege target.
			KillCurrentSiegeTarget();

			// Create a bullseye that will live for 20 seconds. If we can't attack it within 20 seconds, it's probably
			// out of reach anyone, so have it clean itself up after that long.
			CBaseEntity *pSiegeTarget = CreateCustomTarget( pSiegeTargetLocation->GetAbsOrigin(), 20.0f );
			pSiegeTarget->SetName( MAKE_STRING("siegetarget") );

			m_hCurrentSiegeTarget.Set( pSiegeTarget );

			AddEntityRelationship( pSiegeTarget, D_HT, 1 );
			GetEnemies()->UpdateMemory( GetNavigator()->GetNetwork(), pSiegeTarget, pSiegeTarget->GetAbsOrigin(), 0.0f, true );
			AI_EnemyInfo_t *pMemory = GetEnemies()->Find( pSiegeTarget );

			if( pMemory )
			{
				// Pretend we've known about this target longer than we really have so that our AI doesn't waste time running ALERT schedules.
				pMemory->timeFirstSeen = gpGlobals->curtime - 5.0f;
				pMemory->timeLastSeen = gpGlobals->curtime - 1.0f;
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Destroy the bullseye that we're using as a temporary target
//-----------------------------------------------------------------------------
void CNPC_Hunter::KillCurrentSiegeTarget()
{
	if ( m_hCurrentSiegeTarget )
	{
		GetEnemies()->ClearMemory( m_hCurrentSiegeTarget );

		UTIL_Remove( m_hCurrentSiegeTarget );
		m_hCurrentSiegeTarget.Set( NULL );
	}
}

//-----------------------------------------------------------------------------
// Return true if this NPC can hear the specified sound
//-----------------------------------------------------------------------------
bool CNPC_Hunter::QueryHearSound( CSound *pSound )
{
	if ( pSound->SoundContext() & SOUND_CONTEXT_EXCLUDE_COMBINE )
		return false;

	if ( pSound->SoundContext() & SOUND_CONTEXT_PLAYER_VEHICLE )
		return false;

	return BaseClass::QueryHearSound( pSound );
}


//-----------------------------------------------------------------------------
// This is a fairly bogus heuristic right now, but it works on 06a and 12 (sjb)
//
// Better options:	Trace infinitely and check the material we hit for sky
//					Put some leaf info in the BSP
//					Use volumes in the levels? (yucky for designers)
//-----------------------------------------------------------------------------
// TODO: use this or nuke it!
void CNPC_Hunter::GatherIndoorOutdoorConditions()
{
	// Check indoor/outdoor before calling base class, since base class calls our
	// RangeAttackConditions() functions, and we want those functions to know 
	// whether we're indoors or out.
	trace_t tr;

	UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, 40.0f * 12.0f ), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
	if( tr.fraction < 1.0f )
	{
		SetCondition( COND_HUNTER_IS_INDOORS );
	}
	else
	{
		ClearCondition( COND_HUNTER_IS_INDOORS );
	}
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::BuildScheduleTestBits()
{
	BaseClass::BuildScheduleTestBits();

	if ( m_lifeState != LIFE_ALIVE )
	{
		return;
	}

	// Our range attack is uninterruptable for the first few seconds.
	if ( IsCurSchedule( SCHED_HUNTER_RANGE_ATTACK2, false ) && ( gpGlobals->curtime < m_flShootAllowInterruptTime ) )
	{
		ClearCustomInterruptConditions();
		SetCustomInterruptCondition( COND_HEAVY_DAMAGE );
	}
	else if ( IsCurSchedule( SCHED_HUNTER_RANGE_ATTACK2, false ) && ( GetActivity() == ACT_TRANSITION ) )
	{
		// Don't stop unplanting just because we can range attack again.
		ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK1 );
		ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK2 );
	}
	else if ( !IsInLargeOutdoorMap() && IsCurSchedule( SCHED_HUNTER_FLANK_ENEMY, false ) && GetEnemy() != NULL )
	{
		if( HasCondition(COND_CAN_RANGE_ATTACK2) && m_flTimeSawEnemyAgain != HUNTER_SEE_ENEMY_TIME_INVALID )
		{
			if( (gpGlobals->curtime - m_flTimeSawEnemyAgain) >= 2.0f )
			{
				// When we're running flank behavior, wait a moment AFTER being able to see the enemy before
				// breaking my schedule to range attack. This helps assure that the hunter gets well inside
				// the room before stopping to attack. Otherwise the Hunter may stop immediately in the doorway
				// and stop the progress of any hunters behind it.
				SetCustomInterruptCondition( COND_CAN_RANGE_ATTACK2 );
			}
		}
	}

	// If our enemy is anything but a striderbuster, drop everything if we see one.
	if ( !IsStriderBuster( GetEnemy() ) )
	{
		SetCustomInterruptCondition( COND_HUNTER_SEE_STRIDERBUSTER );
	}

	// If we're not too busy, allow ourselves to ACK found enemy signals.
	if ( !GetEnemy() )
	{
		SetCustomInterruptCondition( COND_HUNTER_SQUADMATE_FOUND_ENEMY );
	}

	// Interrupt everything if we need to dodge.
	if ( !IsCurSchedule( SCHED_HUNTER_DODGE, false ) && 
		 !IsCurSchedule( SCHED_HUNTER_STAGGER, false ) &&
		 !IsCurSchedule( SCHED_ALERT_FACE_BESTSOUND, false ) )
	{
		SetCustomInterruptCondition( COND_HUNTER_INCOMING_VEHICLE );
		SetCustomInterruptCondition( COND_HEAR_PHYSICS_DANGER );
		SetCustomInterruptCondition( COND_HUNTER_FORCED_DODGE );
	}

	// Always interrupt on a flank command.	
	SetCustomInterruptCondition( COND_HUNTER_FORCED_FLANK_ENEMY );

	// Always interrupt if staggered.
	SetCustomInterruptCondition( COND_HUNTER_STAGGERED );
	
	// Always interrupt if hit by a sticky bomb.
	SetCustomInterruptCondition( COND_HUNTER_HIT_BY_STICKYBOMB );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
static bool IsMovablePhysicsObject( CBaseEntity *pEntity )
{
	return pEntity && pEntity->GetMoveType() == MOVETYPE_VPHYSICS && pEntity->VPhysicsGetObject() && pEntity->VPhysicsGetObject()->IsMoveable();
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
NPC_STATE CNPC_Hunter::SelectIdealState()
{
	switch ( m_NPCState )
	{
		case NPC_STATE_COMBAT:
		{
			if ( GetEnemy() == NULL )
			{
				if ( !HasCondition( COND_ENEMY_DEAD ) && !hunter_disable_patrol.GetBool() )
				{
					// Lost track of my enemy. Patrol.
					SetCondition( COND_HUNTER_SHOULD_PATROL );
				}

				return NPC_STATE_ALERT;
			}
			else if ( HasCondition( COND_ENEMY_DEAD ) )
			{
				// dvs: TODO: announce enemy kills?
				//AnnounceEnemyKill(GetEnemy());
			}
		}

		default:
		{
			return BaseClass::SelectIdealState();
		}
	}

	return GetIdealState();
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Hunter::ShouldCharge( const Vector &startPos, const Vector &endPos, bool useTime, bool bCheckForCancel )
{
	// Must have a target
	if ( !GetEnemy() )
		return false;

	// Don't check the distance once we start charging
	if ( !bCheckForCancel && !hunter_charge_test.GetBool() )
	{
		float distance = ( startPos.AsVector2D() - endPos.AsVector2D() ).LengthSqr();

		// Must be within our tolerance range
		if ( ( distance < Square(HUNTER_CHARGE_MIN) ) || ( distance > Square(HUNTER_CHARGE_MAX) ) )
			return false;
	}

	// FIXME: We'd like to exclude small physics objects from this check!

	// We only need to hit the endpos with the edge of our bounding box
	Vector vecDir = endPos - startPos;
	VectorNormalize( vecDir );
	float flWidth = WorldAlignSize().x * 0.5;
	Vector vecTargetPos = endPos - (vecDir * flWidth);

	// See if we can directly move there
	AIMoveTrace_t moveTrace;
	GetMoveProbe()->MoveLimit( NAV_GROUND, startPos, vecTargetPos, MASK_NPCSOLID_BRUSHONLY, GetEnemy(), &moveTrace );
	
	// Draw the probe
	if ( g_debug_hunter_charge.GetInt() == 1 )
	{
		Vector	enemyDir	= (vecTargetPos - startPos);
		float	enemyDist	= VectorNormalize( enemyDir );

		NDebugOverlay::BoxDirection( startPos, GetHullMins(), GetHullMaxs() + Vector(enemyDist,0,0), enemyDir, 0, 255, 0, 8, 1.0f );
	}

	// If we're not blocked, charge
	if ( IsMoveBlocked( moveTrace ) )
	{
		// Don't allow it if it's too close to us
		if ( UTIL_DistApprox( WorldSpaceCenter(), moveTrace.vEndPosition ) < HUNTER_CHARGE_MIN )
			return false;

		// Allow some special cases to not block us
		if ( moveTrace.pObstruction != NULL )
		{
			// If we've hit the world, see if it's a cliff
			if ( moveTrace.pObstruction == GetContainingEntity( INDEXENT(0) ) )
			{	
				// Can't be too far above/below the target
				if ( fabs( moveTrace.vEndPosition.z - vecTargetPos.z ) > StepHeight() )
					return false;

				// Allow it if we got pretty close
				if ( UTIL_DistApprox( moveTrace.vEndPosition, vecTargetPos ) < 64 )
					return true;
			}

			// Hit things that will take damage
			if ( moveTrace.pObstruction->m_takedamage != DAMAGE_NO )
				return true;

			// Hit things that will move
			if ( moveTrace.pObstruction->GetMoveType() == MOVETYPE_VPHYSICS )
				return true;
		}

		return false;
	}

	float zDelta = endPos.z - moveTrace.vEndPosition.z;
	if ( fabsf(zDelta) > GetHullHeight() * 0.7)
	{
		return false;
	}

	return true;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Hunter::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter *pSourceEnt)
{
	if ( ( pSourceEnt != this ) && ( interactionType == g_interactionHunterFoundEnemy ) )
	{
		SetCondition( COND_HUNTER_SQUADMATE_FOUND_ENEMY );
		return true;
	}

	return BaseClass::HandleInteraction( interactionType, data, pSourceEnt );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CNPC_Hunter::SelectCombatSchedule()
{
	// If we're here with no enemy, patrol and hope we find one.
	CBaseEntity *pEnemy = GetEnemy();
	if ( pEnemy == NULL )
	{
		if ( !hunter_disable_patrol.GetBool() )
			return SCHED_HUNTER_PATROL_RUN;
		else
			return SCHED_ALERT_STAND;
	}

	if ( hunter_flechette_test.GetBool() )
	{
		if ( HasCondition( COND_CAN_RANGE_ATTACK2 ) )
		{
			return SCHED_HUNTER_RANGE_ATTACK2;
		}
		return SCHED_COMBAT_FACE;
	}

	bool bStriderBuster = IsStriderBuster( pEnemy );
	if ( bStriderBuster )
	{
		if ( gpGlobals->curtime - CAI_HunterEscortBehavior::gm_flLastDefendSound > 10.0 )
		{
			EmitSound( "NPC_Hunter.DefendStrider" );
			CAI_HunterEscortBehavior::gm_flLastDefendSound = gpGlobals->curtime;
		}

		if ( HasCondition( COND_CAN_RANGE_ATTACK2 ) || HasCondition( COND_NOT_FACING_ATTACK ) )
		{
			return SCHED_HUNTER_RANGE_ATTACK2;
		}
		return SCHED_ESTABLISH_LINE_OF_FIRE;
	}

	// Certain behaviors, like flanking and melee attacks, only make sense on visible,
	// corporeal enemies (NOT bullseyes).
	bool bIsCorporealEnemy = IsCorporealEnemy( pEnemy );

	// Take a quick swipe at our enemy if able to do so.
	if ( bIsCorporealEnemy && HasCondition( COND_CAN_MELEE_ATTACK1 ) )
	{
		return SCHED_HUNTER_MELEE_ATTACK1;
	}

	// React to newly acquired enemies.
	if ( bIsCorporealEnemy && HasCondition( COND_NEW_ENEMY ) )
	{
		AI_EnemyInfo_t *pEnemyInfo = GetEnemies()->Find( pEnemy );

		if ( GetSquad() && pEnemyInfo && ( pEnemyInfo->timeFirstSeen == pEnemyInfo->timeAtFirstHand ) )
		{
			GetSquad()->BroadcastInteraction( g_interactionHunterFoundEnemy, NULL, this );

			// First contact for my squad.
			return SCHED_HUNTER_FOUND_ENEMY;
		}
	}

	if ( HasCondition( COND_HUNTER_SQUADMATE_FOUND_ENEMY ) )
	{
		// A squadmate found an enemy. Respond to their call.
		return SCHED_HUNTER_FOUND_ENEMY_ACK;
	}

	// Fire a flechette volley. Ignore squad slots if we're attacking a striderbuster.
	// See if there is an opportunity to charge.
	if ( !bStriderBuster && bIsCorporealEnemy && HasCondition( COND_HUNTER_CAN_CHARGE_ENEMY ) )
	{
		if ( hunter_charge_test.GetBool() || random->RandomInt( 1, 100 ) < hunter_charge_pct.GetInt() )
		{
			if ( hunter_charge_test.GetBool() || OccupyStrategySlot( SQUAD_SLOT_HUNTER_CHARGE ) )
			{
				return SCHED_HUNTER_CHARGE_ENEMY;
			}
		}
	}

	if ( HasCondition( COND_CAN_RANGE_ATTACK2 ) )
	{
		if ( bStriderBuster || CountRangedAttackers() < hunter_flechette_max_concurrent_volleys.GetInt() )
		{
			DelayRangedAttackers( hunter_flechette_volley_start_min_delay.GetFloat(), hunter_flechette_volley_start_max_delay.GetFloat(), true );
			return SCHED_HUNTER_RANGE_ATTACK2;
		}
	}

	if ( pEnemy->GetGroundEntity() == this )
	{
		return SCHED_HUNTER_MELEE_ATTACK1;
	}

	if ( HasCondition( COND_TOO_CLOSE_TO_ATTACK ) )
	{
		return SCHED_MOVE_AWAY_FROM_ENEMY;
	}

	// Sidestep every so often if my enemy is nearby and facing me.
/*
	if ( gpGlobals->curtime > m_flNextSideStepTime )
	{
		if ( HasCondition( COND_ENEMY_FACING_ME ) && ( UTIL_DistApprox( GetEnemy()->GetAbsOrigin(), GetAbsOrigin() ) < HUNTER_FACE_ENEMY_DIST ) )
		{
			m_flNextSideStepTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 3.0f );
			return SCHED_HUNTER_SIDESTEP;
		}
	}
*/
	if ( HasCondition( COND_HEAVY_DAMAGE ) && ( gpGlobals->curtime > m_flNextSideStepTime ) )
	{
		m_flNextSideStepTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 3.0f );
		return SCHED_HUNTER_SIDESTEP;
	}

	if ( !bStriderBuster && bIsCorporealEnemy )
	{
		if ( HasCondition( COND_HUNTER_CAN_CHARGE_ENEMY ) )
		{
			if ( OccupyStrategySlot( SQUAD_SLOT_HUNTER_CHARGE ) )
			{
				return SCHED_HUNTER_CHARGE_ENEMY;
			}
/*
			else
			{
				return SCHED_HUNTER_SIDESTEP;
			}
*/
		}

		// Try to be a flanker.
		if ( ( NumHuntersInMySquad() > 1 ) && OccupyStrategySlotRange( SQUAD_SLOT_HUNTER_FLANK_FIRST, SQUAD_SLOT_HUNTER_FLANK_LAST ) )
		{
			return SCHED_HUNTER_FLANK_ENEMY;
		}
	}
	
	// Can't see my enemy.
	if ( HasCondition( COND_ENEMY_OCCLUDED ) || HasCondition( COND_ENEMY_TOO_FAR ) || HasCondition( COND_TOO_FAR_TO_ATTACK ) || HasCondition( COND_NOT_FACING_ATTACK ) )
	{
		return SCHED_HUNTER_CHASE_ENEMY;
	}

	if ( HasCondition( COND_HUNTER_CANT_PLANT ) )
	{
		return SCHED_ESTABLISH_LINE_OF_FIRE;
	}

	//if ( HasCondition( COND_ENEMY_OCCLUDED ) && IsCurSchedule( SCHED_RANGE_ATTACK1, false ) )
	//{
	//	return SCHED_HUNTER_COMBAT_FACE;
	//}

 	return SCHED_HUNTER_CHANGE_POSITION;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CNPC_Hunter::SelectSiegeSchedule()
{
	bool bHasEnemy = (GetEnemy() != NULL);

	if( bHasEnemy )
	{
		// We have an enemy, so we should be making every effort to attack it.
		if( !HasCondition(COND_SEE_ENEMY) || !HasCondition(COND_CAN_RANGE_ATTACK2) )
			return SCHED_ESTABLISH_LINE_OF_FIRE;

		if( HasCondition(COND_CAN_RANGE_ATTACK2) )
			return SCHED_HUNTER_RANGE_ATTACK2;

		return SCHED_HUNTER_SIEGE_STAND;
	}
	else
	{
		// Otherwise we are loitering in siege mode. Break line of sight with the player
		// if they expose our position.
		if( HasCondition( COND_SEE_PLAYER ) )
			return SCHED_HUNTER_CHANGE_POSITION_SIEGE;
	}

	return SCHED_HUNTER_SIEGE_STAND;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CNPC_Hunter::SelectSchedule()
{
	if ( hunter_stand_still.GetBool() )
	{
		m_bPlanted = false;
		return SCHED_IDLE_STAND;
	}
	
	if ( HasCondition( COND_HUNTER_FORCED_DODGE ) )
		return SCHED_HUNTER_DODGE;

	if ( HasCondition( COND_HUNTER_NEW_HINTGROUP ) || ( GetHintGroup() != NULL_STRING && m_CheckHintGroupTimer.Expired() ) )
	{
		CAI_Hint *pHint;
		CHintCriteria criteria;
		criteria.SetGroup( GetHintGroup() );
		criteria.SetFlag( bits_HINT_NODE_NEAREST );

		if ( HasCondition( COND_HUNTER_NEW_HINTGROUP ) )
		{
			ClearCondition( COND_HUNTER_NEW_HINTGROUP );
			if ( GetEnemy() )
			{
				pHint = CAI_HintManager::FindHint( NULL, GetEnemy()->GetAbsOrigin(), criteria );
			}
			else
			{
				pHint = CAI_HintManager::FindHint( GetAbsOrigin(), criteria );
			}

			if ( pHint )
			{
				pHint->Lock( this );
			}
		} 
		else
		{
			pHint = CAI_HintManager::FindHint( GetAbsOrigin(), criteria );
			if ( pHint )
			{
				if ( (pHint->GetAbsOrigin() - GetAbsOrigin()).Length2DSqr() < Square( 20*12 ) )
				{
					m_CheckHintGroupTimer.Set( 5 );
					pHint = NULL;
				}
				else
				{
					m_CheckHintGroupTimer.Set( 15 );
				}
			}
		}

		if ( pHint )
		{
			SetHintNode( pHint );
			return SCHED_HUNTER_GOTO_HINT;
		}
	}

	if ( HasCondition( COND_HUNTER_INCOMING_VEHICLE ) )
	{
		if ( m_RundownDelay.Expired() )
		{
			int iRundownCounter = 0;
			if ( GetSquad() )
			{
				GetSquad()->GetSquadData( HUNTER_RUNDOWN_SQUADDATA, &iRundownCounter );
			}

			if ( iRundownCounter % 2 == 0 )
			{
				for ( int i = 0; i < g_Hunters.Count(); i++ )
				{
					if ( g_Hunters[i] != this )
					{
						g_Hunters[i]->m_RundownDelay.Set( 3 );
						g_Hunters[i]->m_IgnoreVehicleTimer.Force();
					}
				}
				m_IgnoreVehicleTimer.Set( hunter_dodge_warning.GetFloat() * 4 );
				if ( hunter_dodge_debug.GetBool() )
				{
					Msg( "Hunter %d rundown\n", entindex() );
				}

				if ( HasCondition( COND_SEE_ENEMY ) )
				{
					if ( m_bPlanted && HasCondition( COND_CAN_RANGE_ATTACK2 ) )
					{
						return SCHED_HUNTER_RANGE_ATTACK2;
					}
					else if ( random->RandomInt( 0, 1 ) )
					{
						return SCHED_HUNTER_CHARGE_ENEMY;
					}
					else
					{
						return SCHED_MOVE_AWAY;
					}
				}
				else
				{
					SetTarget( UTIL_GetLocalPlayer() );
					return SCHED_TARGET_FACE;
				}
			}
			else
			{
				if ( hunter_dodge_debug.GetBool() )
				{
					Msg( "Hunter %d safe from rundown\n", entindex() );
				}
				for ( int i = 0; i < g_Hunters.Count(); i++ )
				{
					g_Hunters[i]->m_RundownDelay.Set( 4 );
					g_Hunters[i]->m_IgnoreVehicleTimer.Force();
				}
				if ( GetSquad() )
				{
					GetSquad()->SetSquadData( HUNTER_RUNDOWN_SQUADDATA, iRundownCounter + 1 );
				}
			}
		}

		if ( HasCondition( COND_SEE_ENEMY ) )
		{
			if ( hunter_dodge_debug.GetBool() )
			{
				Msg( "Hunter %d try dodge\n", entindex() );
			}
			return SCHED_HUNTER_DODGE;
		}
		else
		{
			SetTarget( UTIL_GetLocalPlayer() );
			return SCHED_TARGET_FACE;
		}

		CSound *pBestSound = GetBestSound( SOUND_PHYSICS_DANGER );
		if ( pBestSound && ( pBestSound->SoundContext() & SOUND_CONTEXT_PLAYER_VEHICLE ) )
		{
			return SCHED_ALERT_FACE_BESTSOUND;
		}
	}

	if ( HasCondition( COND_HUNTER_FORCED_FLANK_ENEMY ) )
	{
		return SCHED_HUNTER_FLANK_ENEMY;
	}
	
	if ( HasCondition( COND_HUNTER_STAGGERED ) /*|| HasCondition( COND_HUNTER_HIT_BY_STICKYBOMB )*/ )
	{
		return SCHED_HUNTER_STAGGER;
	}

	// Now that we're past all of the forced reactions to things, if we're running the siege
	// behavior, go pick an appropriate siege schedule UNLESS we have an enemy. If we have
	// an enemy, we should focus on attacking that enemy.
	if( IsUsingSiegeTargets() )
	{
		return SelectSiegeSchedule();
	}

	// back away if there's a magnade glued to my head.
	if ( hunter_retreat_striderbusters.GetBool() /*&& GetEnemy() && ( GetEnemy()->IsPlayer() )*/ 
		&& (m_hAttachedBusters.Count() > 0)
		&& m_fCorneredTimer < gpGlobals->curtime)
	{
		return SCHED_HUNTER_TAKE_COVER_FROM_ENEMY;
	}

	if ( !BehaviorSelectSchedule() )
	{
		switch ( GetState() )
		{
			case NPC_STATE_IDLE:
			{
				return SCHED_HUNTER_PATROL;
			}

			case NPC_STATE_ALERT:
			{
				if ( HasCondition( COND_HUNTER_SHOULD_PATROL ) )
					return SCHED_HUNTER_PATROL;
					
				break;
			}

			case NPC_STATE_COMBAT:
			{
				return SelectCombatSchedule();
			}
		}
	}
		
	return BaseClass::SelectSchedule();
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CNPC_Hunter::TranslateSchedule( int scheduleType )
{
	switch ( scheduleType )
	{
		case SCHED_RANGE_ATTACK1:
		{
			return SCHED_HUNTER_RANGE_ATTACK1;
		}

		case SCHED_RANGE_ATTACK2:
		case SCHED_HUNTER_RANGE_ATTACK2:
		{
			if ( scheduleType == SCHED_RANGE_ATTACK2 )
			{
				Msg( "HUNTER IGNORING SQUAD SLOTS\n" );
			}

			if ( IsStriderBuster( GetEnemy() ) )
			{
				// Attack as FAST as possible. The point is to shoot down the buster.
				return SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER;
			}

			return SCHED_HUNTER_RANGE_ATTACK2;
		}

		case SCHED_MELEE_ATTACK1:
		{
			return SCHED_HUNTER_MELEE_ATTACK1;
		}
	
		case SCHED_ESTABLISH_LINE_OF_FIRE_FALLBACK:
		{
			return SCHED_HUNTER_CHANGE_POSITION;
		}

		case SCHED_ALERT_STAND:
		{
			if ( !hunter_disable_patrol.GetBool() )
				return SCHED_HUNTER_PATROL_RUN;
			break;
		}

		case SCHED_COMBAT_FACE:
		{
			return SCHED_HUNTER_COMBAT_FACE;
		}

		case SCHED_HUNTER_PATROL:
		{
			if ( hunter_disable_patrol.GetBool() )
			{
				return SCHED_IDLE_STAND;
			}
			break;
		}
	}

	return BaseClass::TranslateSchedule( scheduleType );
}


//-----------------------------------------------------------------------------
// catch blockage while escaping magnade
//-----------------------------------------------------------------------------
void CNPC_Hunter::TaskFail( AI_TaskFailureCode_t code )
{
	if ( IsCurSchedule( SCHED_HUNTER_TAKE_COVER_FROM_ENEMY, false ) && ( code == FAIL_NO_ROUTE_BLOCKED  ) )
	{
		// cornered!
		if ( m_fCorneredTimer < gpGlobals->curtime )
		{
			m_fCorneredTimer = gpGlobals->curtime + 6.0f;
		}
	}

	BaseClass::TaskFail( code );
}


//-----------------------------------------------------------------------------
// The player is speeding toward us in a vehicle! Find a good activity for dodging.
//-----------------------------------------------------------------------------
void CNPC_Hunter::TaskFindDodgeActivity()
{
	if ( GetEnemy() == NULL )
	{
		TaskFail( "No enemy to dodge" );
		return;
	}

	Vector vecUp;
	Vector vecRight;
	GetVectors( NULL, &vecRight, &vecUp );

	// TODO: find most perpendicular 8-way dodge when we get the anims
	Vector vecEnemyDir = GetEnemy()->GetAbsOrigin() - GetAbsOrigin();
	//Vector vecDir = CrossProduct( vecEnemyDir, vecUp );
	VectorNormalize( vecEnemyDir );
	if ( fabs( DotProduct( vecEnemyDir, vecRight ) ) > 0.7 )
	{
		TaskFail( "Can't dodge, enemy approaching perpendicularly" );
		return;
	}

	// Check left or right randomly first.
	bool bDodgeLeft = false;
	CBaseEntity *pVehicle = GetEnemyVehicle();
	if ( pVehicle  )
	{
		Ray_t enemyRay;
		Ray_t perpendicularRay;
		enemyRay.Init( pVehicle->GetAbsOrigin(), pVehicle->GetAbsOrigin() + pVehicle->GetSmoothedVelocity() );
		Vector vPerpendicularPt = vecEnemyDir;
		vPerpendicularPt.y = -vPerpendicularPt.y;
		perpendicularRay.Init( GetAbsOrigin(), GetAbsOrigin() + vPerpendicularPt );

		enemyRay.m_Start.z = enemyRay.m_Delta.z = enemyRay.m_StartOffset.z;
		perpendicularRay.m_Start.z = perpendicularRay.m_Delta.z = perpendicularRay.m_StartOffset.z;

		float t, s;

		IntersectRayWithRay( perpendicularRay, enemyRay, t, s );

		if ( t > 0 )
		{
			bDodgeLeft = true;
		}
	}
	else if ( random->RandomInt( 0, 1 ) == 0 )
	{
		bDodgeLeft = true;
	}

	bool bFoundDir = false;
	int nTries = 0;

	while ( !bFoundDir && ( nTries < 2 ) )
	{
		// Pick a dodge activity to try.
		if ( bDodgeLeft )
		{
			m_eDodgeActivity = ACT_HUNTER_DODGEL;
		}
		else
		{
			m_eDodgeActivity = ACT_HUNTER_DODGER;
		}

		// See where the dodge will put us.
		Vector vecLocalDelta;
		int nSeq = SelectWeightedSequence( m_eDodgeActivity );
		GetSequenceLinearMotion( nSeq, &vecLocalDelta );

		// Transform the sequence delta into local space.
		matrix3x4_t fRotateMatrix;
		AngleMatrix( GetLocalAngles(), fRotateMatrix );
		Vector vecDelta;
		VectorRotate( vecLocalDelta, fRotateMatrix, vecDelta );

		// Trace a bit high so this works better on uneven terrain.
		Vector testHullMins = GetHullMins();
		testHullMins.z += ( StepHeight() * 2 );

		// See if all is clear in that direction.
		trace_t tr;
		HunterTraceHull_SkipPhysics( GetAbsOrigin(), GetAbsOrigin() + vecDelta, testHullMins, GetHullMaxs(), MASK_NPCSOLID, this, GetCollisionGroup(), &tr, VPhysicsGetObject()->GetMass() * 0.5f );

		// TODO: dodge anyway if we'll make it a certain percentage of the way through the dodge?
		if ( tr.fraction == 1.0f )
		{
			//NDebugOverlay::SweptBox( GetAbsOrigin(), GetAbsOrigin() + vecDelta, testHullMins, GetHullMaxs(), QAngle( 0, 0, 0 ), 0, 255, 0, 128, 5 );
			bFoundDir = true;
			TaskComplete();
		}
		else
		{
			//NDebugOverlay::SweptBox( GetAbsOrigin(), GetAbsOrigin() + vecDelta, testHullMins, GetHullMaxs(), QAngle( 0, 0, 0 ), 255, 0, 0, 128, 5 );
			nTries++;
			bDodgeLeft = !bDodgeLeft;
		}
	}

	if ( nTries < 2 )
	{
		TaskComplete();
	}
	else
	{
		TaskFail( "Couldn't find dodge position\n" );
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::StartTask( const Task_t *pTask )
{
	switch ( pTask->iTask )
	{
		case TASK_HUNTER_FINISH_RANGE_ATTACK:
		{
			if( GetEnemy() != NULL && GetEnemy()->Classify() == CLASS_PLAYER_ALLY_VITAL )
			{
				// Just finished shooting at Alyx! So forget her for a little while and get back on the player
				// !!!LATER - make sure there's someone else in enemy memory to go bother.
				GetEnemies()->SetTimeValidEnemy( GetEnemy(), gpGlobals->curtime + 10.0f );
			}

			if( m_hCurrentSiegeTarget )
			{
				// We probably just fired at our siege target, so dump it.
				KillCurrentSiegeTarget();
			}

			TaskComplete();
		}

		case TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY:
		{
			ChainStartTask( TASK_WAIT_FOR_MOVEMENT, pTask->flTaskData );
			break;
		}

		case TASK_HUNTER_BEGIN_FLANK:
		{
			if ( IsInSquad() && GetSquad()->NumMembers() > 1 )
			{
				// Flank relative to the other shooter in our squad.
				// If there's no other shooter, just flank relative to any squad member.
				AISquadIter_t iter;
				CAI_BaseNPC *pNPC = GetSquad()->GetFirstMember( &iter );
				while ( pNPC == this )
				{
					pNPC = GetSquad()->GetNextMember( &iter );
				}

				m_vSavePosition = pNPC->GetAbsOrigin();
			}
			else
			{
				// Flank relative to our current position.
				m_vSavePosition = GetAbsOrigin();
			}
			
			TaskComplete();
			break;
		}

		case TASK_HUNTER_ANNOUNCE_FLANK:
		{
			EmitSound( "NPC_Hunter.FlankAnnounce" );
			TaskComplete();
			break;
		}

		case TASK_HUNTER_DODGE:
		{
			if ( hunter_dodge_debug. GetBool() )
			{
				Msg( "Hunter %d dodging\n", entindex() );
			}
			SetIdealActivity( m_eDodgeActivity );
			break;
		}
		
		// Guarantee a certain delay between volleys. If we aren't already planted,
		// the plant transition animation will take care of that.
		case TASK_HUNTER_PRE_RANGE_ATTACK2:
		{
			if ( !m_bPlanted  || ( GetEnemy() && IsStriderBuster( GetEnemy() ) ) )
			{
				TaskComplete();
			}
			else
			{
				SetIdealActivity( ACT_HUNTER_ANGRY );
			}
			break;
		}

		case TASK_HUNTER_SHOOT_COMMIT:
		{
			// We're committing to shooting. Don't allow interrupts until after we've shot a bit (see TASK_RANGE_ATTACK1).
			m_flShootAllowInterruptTime = gpGlobals->curtime + 100.0f;
			TaskComplete();
			break;
		}
		
		case TASK_RANGE_ATTACK2:
		{
			if ( GetEnemy() )
			{
				bool bIsBuster = IsStriderBuster( GetEnemy() );
				if ( bIsBuster )
				{
					AddFacingTarget( GetEnemy(), GetEnemy()->GetAbsOrigin() + GetEnemy()->GetSmoothedVelocity() * .5, 1.0, 0.8 );
				}

				// Start the firing sound.
				//CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
				//controller.SoundChangeVolume( m_pGunFiringSound, 1.0, hunter_first_flechette_delay.GetFloat() );

				SetIdealActivity( ACT_RANGE_ATTACK2 );

				// Decide how many shots to fire.
				int nShots = hunter_flechette_volley_size.GetInt();
				if ( g_pGameRules->IsSkillLevel( SKILL_EASY ) )
				{
					nShots--;
				}

				// Decide when to fire the first shot.
				float initialDelay = hunter_first_flechette_delay.GetFloat();
				if ( bIsBuster )
				{
					initialDelay = 0; //*= 0.5;
				}

				BeginVolley( nShots, gpGlobals->curtime + initialDelay );

				// In case we need to miss on purpose, pick a direction now.
				m_bMissLeft = false;
				if ( random->RandomInt( 0, 1 ) == 0 )
				{
					m_bMissLeft = true;
				}

				LockBothEyes( initialDelay + ( nShots * hunter_flechette_delay.GetFloat() ) );
			}
			else
			{
				TaskFail( FAIL_NO_ENEMY );
			}
			
			break;
		}

		case TASK_HUNTER_STAGGER:
		{
			// Stagger in the direction the impact force would push us.
			VMatrix worldToLocalRotation = EntityToWorldTransform();
			Vector vecLocalStaggerDir = worldToLocalRotation.InverseTR().ApplyRotation( m_vecStaggerDir );
			
			float flStaggerYaw = VecToYaw( vecLocalStaggerDir );
			SetPoseParameter( gm_nStaggerYawPoseParam, flStaggerYaw );

			// Go straight there!
			SetActivity( ACT_RESET );
			SetActivity( ( Activity )ACT_HUNTER_STAGGER );
			break;
		}
	
		case TASK_MELEE_ATTACK1:
		{
			SetLastAttackTime( gpGlobals->curtime );
			
			if ( GetEnemy() && GetEnemy()->IsPlayer() )
			{
				ResetIdealActivity( ( Activity )ACT_HUNTER_MELEE_ATTACK1_VS_PLAYER );
			}
			else
			{
				ResetIdealActivity( ACT_MELEE_ATTACK1 );
			}
			
			break;
		}

		case TASK_HUNTER_CORNERED_TIMER:
		{
			m_fCorneredTimer = gpGlobals->curtime + pTask->flTaskData;

			break;
		}

		case TASK_HUNTER_FIND_SIDESTEP_POSITION:
		{
			if ( GetEnemy() == NULL )
			{
				TaskFail( "No enemy to sidestep" );
			}
			else
			{
				Vector vecUp;
				GetVectors( NULL, NULL, &vecUp );
				
				Vector vecEnemyDir = GetEnemy()->GetAbsOrigin() - GetAbsOrigin();
				Vector vecDir = CrossProduct( vecEnemyDir, vecUp );
				VectorNormalize( vecDir );

				// Sidestep left or right randomly.				
				if ( random->RandomInt( 0, 1 ) == 0 )
				{
					vecDir *= -1;
				}

				// Start high and then trace down so that it works on uneven terrain.
				Vector vecPos = GetAbsOrigin() + Vector( 0, 0, 64 ) + random->RandomFloat( 120, 200 ) * vecDir;
				
				// Try to find the ground at the sidestep position.
				trace_t tr;
				UTIL_TraceLine( vecPos, vecPos + Vector( 0, 0, -128 ), MASK_NPCSOLID, NULL, COLLISION_GROUP_NONE, &tr );
				if ( tr.fraction < 1.0f )
				{
					//NDebugOverlay::Line( vecPos, tr.endpos, 0, 255, 0, true, 10 ); 
		
					m_vSavePosition = tr.endpos;
									
					TaskComplete();
				}
				else
				{
					TaskFail( "Couldn't find sidestep position\n" );
				}
			}
			
			break;
		}

		case TASK_HUNTER_FIND_DODGE_POSITION:
		{
			TaskFindDodgeActivity();
			break;
		}

		case TASK_HUNTER_CHARGE:
		{
			SetIdealActivity( ( Activity )ACT_HUNTER_CHARGE_START );
			break;
		}

		case TASK_HUNTER_CHARGE_DELAY:
		{
			m_flNextChargeTime = gpGlobals->curtime + pTask->flTaskData;
			TaskComplete();
			break;
		}
	
		case TASK_DIE:
		{
			GetNavigator()->StopMoving();	
			ResetActivity();
			SetIdealActivity( GetDeathActivity() );
			m_lifeState = LIFE_DYING;

			break;
		}

		//case TASK_HUNTER_END_FLANK:
		//{
		//	
		//}
	
		default:
		{
			BaseClass::StartTask( pTask );
			break;
		}
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::RunTask( const Task_t *pTask )
{
	switch ( pTask->iTask )
	{
		case TASK_HUNTER_PRE_RANGE_ATTACK2:
		{
			if ( IsActivityFinished() )
			{
				TaskComplete();
			}
			break;
		}

		case TASK_RANGE_ATTACK2:
		{
			if( !hunter_hate_thrown_striderbusters.GetBool() && GetEnemy() != NULL && IsStriderBuster( GetEnemy() ) )
			{
				if( !IsValidEnemy(GetEnemy()) )
				{
					TaskFail("No longer hate this StriderBuster");
				}
			}

			bool bIsBuster = IsStriderBuster( GetEnemy() );
			if ( bIsBuster )
			{
				Vector vFuturePosition = GetEnemy()->GetAbsOrigin() + GetEnemy()->GetSmoothedVelocity() * .3;
				AddFacingTarget( GetEnemy(), vFuturePosition, 1.0, 0.8 );

				Vector2D vToFuturePositon = ( vFuturePosition.AsVector2D() - GetAbsOrigin().AsVector2D() );
				vToFuturePositon.NormalizeInPlace();
				Vector2D facingDir = BodyDirection2D().AsVector2D();

				float flDot = DotProduct2D( vToFuturePositon, facingDir );

				if ( flDot < .4 )
				{
					GetMotor()->SetIdealYawToTarget( vFuturePosition );
					GetMotor()->UpdateYaw();
					break;
				}
			}

			if ( gpGlobals->curtime >= m_flNextFlechetteTime )
			{
				// Must have an enemy and a shot queued up.
				bool bDone = false;
				if ( GetEnemy() != NULL && m_nFlechettesQueued > 0 )
				{
					if ( ShootFlechette( GetEnemy(), false ) )
					{
						m_nClampedShots++;
					}
					else
					{
						m_nClampedShots = 0;
					}

					m_nFlechettesQueued--;

					// If we fired three or more clamped shots in a row, call it quits so we don't look dumb.
					if ( ( m_nClampedShots >= 3 ) || ( m_nFlechettesQueued == 0 ) )
					{
						bDone = true;
					}
					else
					{
						// More shooting to do. Schedule our next flechette.
						m_flNextFlechetteTime = gpGlobals->curtime + hunter_flechette_delay.GetFloat();
					}
				}
				else
				{
					bDone = true;
				}
				
				if ( bDone )     
				{
					// Stop the firing sound.
					//CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
					//controller.SoundChangeVolume( m_pGunFiringSound, 0.0f, 0.1f );

					DelayRangedAttackers( hunter_flechette_volley_end_min_delay.GetFloat(), hunter_flechette_volley_end_max_delay.GetFloat(), true );
					TaskComplete();
				}
			}

			break;
		}
	
		case TASK_GET_PATH_TO_ENEMY_LOS:
		{
			ChainRunTask( TASK_GET_PATH_TO_ENEMY_LKP_LOS, pTask->flTaskData );
			break;
		}

		case TASK_HUNTER_DODGE:
		{
			AutoMovement();

			if ( IsActivityFinished() )
			{
				TaskComplete();
			}
			break;
		}

		case TASK_HUNTER_CORNERED_TIMER:
		{
			TaskComplete();
			break;
		}
		
		case TASK_HUNTER_STAGGER:
		{
			if ( IsActivityFinished() )
			{
				TaskComplete();
			}
			break;
		}
				
		case TASK_HUNTER_CHARGE:
		{
			Activity eActivity = GetActivity();

			// See if we're trying to stop after hitting/missing our target
			if ( eActivity == ACT_HUNTER_CHARGE_STOP || eActivity == ACT_HUNTER_CHARGE_CRASH ) 
			{
				if ( IsActivityFinished() )
				{
					m_flNextChargeTime = gpGlobals->curtime + hunter_charge_min_delay.GetFloat() + random->RandomFloat( 0, 2.5 ) + random->RandomFloat( 0, 2.5 );
					float delayMultiplier = ( g_pGameRules->IsSkillLevel( SKILL_EASY ) ) ? 1.5 : 1.0;
					float groupDelay = gpGlobals->curtime +  ( 2.0  + random->RandomFloat( 0, 2 ) ) * delayMultiplier;
					for ( int i = 0; i < g_Hunters.Count(); i++ )
					{
						if ( g_Hunters[i] != this && g_Hunters[i]->m_flNextChargeTime < groupDelay )
						{
							g_Hunters[i]->m_flNextChargeTime = groupDelay;
						}
					}
					TaskComplete();
					return;
				}

				// Still in the process of slowing down. Run movement until it's done.
				AutoMovement();
				return;
			}

			// Check for manual transition
			if ( ( eActivity == ACT_HUNTER_CHARGE_START ) && ( IsActivityFinished() ) )
			{
				SetIdealActivity( ACT_HUNTER_CHARGE_RUN );
			}

			// See if we're still running
			if ( eActivity == ACT_HUNTER_CHARGE_RUN || eActivity == ACT_HUNTER_CHARGE_START ) 
			{
				if ( HasCondition( COND_NEW_ENEMY ) || HasCondition( COND_LOST_ENEMY ) || HasCondition( COND_ENEMY_DEAD ) )
				{
					SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
					return;
				}
				else 
				{
					if ( GetEnemy() != NULL )
					{
						Vector	goalDir = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
						VectorNormalize( goalDir );

						if ( DotProduct( BodyDirection2D(), goalDir ) < 0.25f )
						{
							SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
						}
					}
				}
			}

			// Steer towards our target
			float idealYaw;
			if ( GetEnemy() == NULL )
			{
				idealYaw = GetMotor()->GetIdealYaw();
			}
			else
			{
				idealYaw = CalcIdealYaw( GetEnemy()->GetAbsOrigin() );
			}

			// Add in our steering offset
			idealYaw += ChargeSteer();
			
			// Turn to face
			GetMotor()->SetIdealYawAndUpdate( idealYaw );

			// See if we're going to run into anything soon
			ChargeLookAhead();

			// Let our animations simply move us forward. Keep the result
			// of the movement so we know whether we've hit our target.
			AIMoveTrace_t moveTrace;
			if ( AutoMovement( GetEnemy(), &moveTrace ) == false )
			{
				// Only stop if we hit the world
				if ( HandleChargeImpact( moveTrace.vEndPosition, moveTrace.pObstruction ) )
				{
					// If we're starting up, this is an error
					if ( eActivity == ACT_HUNTER_CHARGE_START )
					{
						TaskFail( "Unable to make initial movement of charge\n" );
						return;
					}

					// Crash unless we're trying to stop already
					if ( eActivity != ACT_HUNTER_CHARGE_STOP )
					{
						if ( moveTrace.fStatus == AIMR_BLOCKED_WORLD && moveTrace.vHitNormal == vec3_origin )
						{
							SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
						}
						else
						{
							// Shake the screen
							if ( moveTrace.fStatus != AIMR_BLOCKED_NPC )
							{
								EmitSound( "NPC_Hunter.ChargeHitWorld" );
								UTIL_ScreenShake( GetAbsOrigin(), 16.0f, 4.0f, 1.0f, 400.0f, SHAKE_START );
							}
							SetIdealActivity( ACT_HUNTER_CHARGE_CRASH );
						}
					}
				}
				else if ( moveTrace.pObstruction )
				{
					// If we hit another hunter, stop
					if ( moveTrace.pObstruction->Classify() == CLASS_COMBINE_HUNTER )
					{
						// Crash unless we're trying to stop already
						if ( eActivity != ACT_HUNTER_CHARGE_STOP )
						{
							SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
						}
					}
					// If we hit an antlion, don't stop, but kill it
					// We never have hunters and antlions together, but you never know.
					else if (moveTrace.pObstruction->Classify() == CLASS_ANTLION )
					{
						if ( FClassnameIs( moveTrace.pObstruction, "npc_antlionguard" ) )
						{
							// Crash unless we're trying to stop already
							if ( eActivity != ACT_HUNTER_CHARGE_STOP )
							{
								SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
							}
						}
						else
						{
							Hunter_ApplyChargeDamage( this, moveTrace.pObstruction, moveTrace.pObstruction->GetHealth() );
						}
					}
				}
			}

			break;
		}
				
		case TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY:
		{
			if ( GetEnemy() )
			{
				Vector vecEnemyLKP = GetEnemyLKP();
				AddFacingTarget( GetEnemy(), vecEnemyLKP, 1.0, 0.8 );
			}
			ChainRunTask( TASK_WAIT_FOR_MOVEMENT, pTask->flTaskData );
			break;
		}

		default:
		{
			BaseClass::RunTask( pTask );
			break;		
		}
	}
}


//-----------------------------------------------------------------------------
// Return true if our charge target is right in front of the hunter.
//-----------------------------------------------------------------------------
bool CNPC_Hunter::EnemyIsRightInFrontOfMe( CBaseEntity **pEntity )
{
	if ( !GetEnemy() )
		return false;

	if ( (GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter()).LengthSqr() < (156*156) )
	{
		Vector vecLOS = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
		vecLOS.z = 0;
		VectorNormalize( vecLOS );
		Vector vBodyDir = BodyDirection2D();
		if ( DotProduct( vecLOS, vBodyDir ) > 0.8 )
		{
			// He's in front of me, and close. Make sure he's not behind a wall.
			trace_t tr;
			UTIL_TraceHull( WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), GetHullMins() * 0.5, GetHullMaxs() * 0.5, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
			if ( tr.m_pEnt == GetEnemy() )
			{
				*pEntity = tr.m_pEnt;
				return true;
			}
		}
	}

	return false;
}


//-----------------------------------------------------------------------------
// While charging, look ahead and see if we're going to run into anything.
// If we are, start the gesture so it looks like we're anticipating the hit.
//-----------------------------------------------------------------------------
void CNPC_Hunter::ChargeLookAhead( void )
{
#if 0
	trace_t	tr;
	Vector vecForward;
	GetVectors( &vecForward, NULL, NULL );
	Vector vecTestPos = GetAbsOrigin() + ( vecForward * m_flGroundSpeed * 0.75 );
	Vector testHullMins = GetHullMins();
	testHullMins.z += (StepHeight() * 2);
	HunterTraceHull_SkipPhysics( GetAbsOrigin(), vecTestPos, testHullMins, GetHullMaxs(), MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, &tr, VPhysicsGetObject()->GetMass() * 0.5 );

	//NDebugOverlay::Box( tr.startpos, testHullMins, GetHullMaxs(), 0, 255, 0, true, 0.1f );
	//NDebugOverlay::Box( vecTestPos, testHullMins, GetHullMaxs(), 255, 0, 0, true, 0.1f );

	if ( tr.fraction != 1.0 )
	{
		// dvs: TODO:
		// Start playing the hit animation
		//AddGesture( ACT_HUNTER_CHARGE_ANTICIPATION );
	}
#endif
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
float CNPC_Hunter::ChargeSteer()
{
	trace_t	tr;
	Vector	testPos, steer, forward, right;
	QAngle	angles;
	const float	testLength = m_flGroundSpeed * 0.15f;

	//Get our facing
	GetVectors( &forward, &right, NULL );

	steer = forward;

	const float faceYaw	= UTIL_VecToYaw( forward );

	//Offset right
	VectorAngles( forward, angles );
	angles[YAW] += 45.0f;
	AngleVectors( angles, &forward );

	// Probe out
	testPos = GetAbsOrigin() + ( forward * testLength );

	// Offset by step height
	Vector testHullMins = GetHullMins();
	testHullMins.z += (StepHeight() * 2);

	// Probe
	HunterTraceHull_SkipPhysics( GetAbsOrigin(), testPos, testHullMins, GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr, VPhysicsGetObject()->GetMass() * 0.5f );

	// Debug info
	if ( g_debug_hunter_charge.GetInt() == 1 )
	{
		if ( tr.fraction == 1.0f )
		{
  			NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 0, 255, 0, 8, 0.1f );
   		}
   		else
   		{
  			NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 255, 0, 0, 8, 0.1f );
		}
	}

	// Add in this component
	steer += ( right * 0.5f ) * ( 1.0f - tr.fraction );

	// Offset left
	angles[YAW] -= 90.0f;
	AngleVectors( angles, &forward );

	// Probe out
	testPos = GetAbsOrigin() + ( forward * testLength );
	HunterTraceHull_SkipPhysics( GetAbsOrigin(), testPos, testHullMins, GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr, VPhysicsGetObject()->GetMass() * 0.5f );

	// Debug
	if ( g_debug_hunter_charge.GetInt() == 1 )
	{
		if ( tr.fraction == 1.0f )
		{
			NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 0, 255, 0, 8, 0.1f );
		}
		else
		{
			NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 255, 0, 0, 8, 0.1f );
		}
	}

	// Add in this component
	steer -= ( right * 0.5f ) * ( 1.0f - tr.fraction );

	// Debug
	if ( g_debug_hunter_charge.GetInt() == 1 )
	{
		NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + ( steer * 512.0f ), 255, 255, 0, true, 0.1f );
		NDebugOverlay::Cross3D( GetAbsOrigin() + ( steer * 512.0f ), Vector(2,2,2), -Vector(2,2,2), 255, 255, 0, true, 0.1f );

		NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + ( BodyDirection3D() * 256.0f ), 255, 0, 255, true, 0.1f );
		NDebugOverlay::Cross3D( GetAbsOrigin() + ( BodyDirection3D() * 256.0f ), Vector(2,2,2), -Vector(2,2,2), 255, 0, 255, true, 0.1f );
	}

	return UTIL_AngleDiff( UTIL_VecToYaw( steer ), faceYaw );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::ChargeDamage( CBaseEntity *pTarget )
{
	if ( pTarget == NULL )
		return;

	CBasePlayer *pPlayer = ToBasePlayer( pTarget );

	if ( pPlayer != NULL )
	{
		//Kick the player angles
		pPlayer->ViewPunch( QAngle( 20, 20, -30 ) );	

		Vector	dir = pPlayer->WorldSpaceCenter() - WorldSpaceCenter();
		VectorNormalize( dir );
		dir.z = 0.0f;
		
		Vector vecNewVelocity = dir * 250.0f;
		vecNewVelocity[2] += 128.0f;
		pPlayer->SetAbsVelocity( vecNewVelocity );

		color32 red = {128,0,0,128};
		UTIL_ScreenFade( pPlayer, red, 1.0f, 0.1f, FFADE_IN );
	}
	
	// Player takes less damage
	float flDamage = ( pPlayer == NULL ) ? 250 : sk_hunter_dmg_charge.GetFloat();
	
	// If it's being held by the player, break that bond
	Pickup_ForcePlayerToDropThisObject( pTarget );

	// Calculate the physics force
	Hunter_ApplyChargeDamage( this, pTarget, flDamage );
}


//-----------------------------------------------------------------------------
// Handles the hunter charging into something. Returns true if it hit the world.
//-----------------------------------------------------------------------------
bool CNPC_Hunter::HandleChargeImpact( Vector vecImpact, CBaseEntity *pEntity )
{
	// Cause a shock wave from this point which will disrupt nearby physics objects
	//ImpactShock( vecImpact, 128, 350 );

	// Did we hit anything interesting?
	if ( !pEntity || pEntity->IsWorld() )
	{
		// Robin: Due to some of the finicky details in the motor, the hunter will hit
		//		  the world when it is blocked by our enemy when trying to step up 
		//		  during a moveprobe. To get around this, we see if the enemy's within
		//		  a volume in front of the hunter when we hit the world, and if he is,
		//		  we hit him anyway.
		EnemyIsRightInFrontOfMe( &pEntity );

		// Did we manage to find him? If not, increment our charge miss count and abort.
		if ( pEntity->IsWorld() )
		{
			return true;
		}
	}

	// Hit anything we don't like
	if ( IRelationType( pEntity ) == D_HT && ( GetNextAttack() < gpGlobals->curtime ) )
	{
		EmitSound( "NPC_Hunter.ChargeHitEnemy" );

		// dvs: TODO:
		//if ( !IsPlayingGesture( ACT_HUNTER_CHARGE_HIT ) )
		//{
		//	RestartGesture( ACT_HUNTER_CHARGE_HIT );
		//}
		
		ChargeDamage( pEntity );

		if ( !pEntity->IsNPC() )
		{
			pEntity->ApplyAbsVelocityImpulse( ( BodyDirection2D() * 400 ) + Vector( 0, 0, 200 ) );
		}

		if ( !pEntity->IsAlive() && GetEnemy() == pEntity )
		{
			SetEnemy( NULL );
		}

		SetNextAttack( gpGlobals->curtime + 2.0f );

		if ( !pEntity->IsAlive() || !pEntity->IsNPC() )
		{
			SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
			return false;
		}
		else
			return true;

	}

	// Hit something we don't hate. If it's not moveable, crash into it.
	if ( pEntity->GetMoveType() == MOVETYPE_NONE || pEntity->GetMoveType() == MOVETYPE_PUSH )
	{		
		CBreakable *pBreakable = dynamic_cast<CBreakable *>(pEntity);
		if ( pBreakable  && pBreakable->IsBreakable() && pBreakable->m_takedamage == DAMAGE_YES && pBreakable->GetHealth() > 0 )
		{
			ChargeDamage( pEntity );
		}
		return true;
	}

	// If it's a vphysics object that's too heavy, crash into it too.
	if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
	{
		IPhysicsObject *pPhysics = pEntity->VPhysicsGetObject();
		if ( pPhysics )
		{
			// If the object is being held by the player, knock it out of his hands
			if ( pPhysics->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
			{
				Pickup_ForcePlayerToDropThisObject( pEntity );
				return false;
			}

			if ( !pPhysics->IsMoveable() )
				return true;

			float entMass = PhysGetEntityMass( pEntity ) ;
			float minMass = VPhysicsGetObject()->GetMass() * 0.5f;
			if ( entMass < minMass )
			{
				if ( entMass < minMass * 0.666f || pEntity->CollisionProp()->BoundingRadius() < GetHullHeight() )
				{
					if ( pEntity->GetHealth() > 0 )
					{
						CBreakableProp *pBreakable = dynamic_cast<CBreakableProp *>(pEntity);
						if ( pBreakable && pBreakable->m_takedamage == DAMAGE_YES && pBreakable->GetHealth() > 0 && pBreakable->GetHealth() <= 50 )
						{
							ChargeDamage( pEntity );
						}
					}
					pEntity->SetNavIgnore( 2.0 );
					return false;
				}
			}
			return true;

		}
	}

	return false;
}


//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void CNPC_Hunter::Explode()
{
	Vector			velocity = vec3_origin;
	AngularImpulse	angVelocity = RandomAngularImpulse( -150, 150 );

	PropBreakableCreateAll( GetModelIndex(), NULL, EyePosition(), GetAbsAngles(), velocity, angVelocity, 1.0, 150, COLLISION_GROUP_NPC, this );

	ExplosionCreate( EyePosition(), GetAbsAngles(), this, 500, 256, (SF_ENVEXPLOSION_NOPARTICLES|SF_ENVEXPLOSION_NOSPARKS|SF_ENVEXPLOSION_NODLIGHTS|SF_ENVEXPLOSION_NODAMAGE|SF_ENVEXPLOSION_NOSMOKE), false );

	// Create liquid fountain gushtacular effect here!
	CEffectData	data;

	data.m_vOrigin = EyePosition();
	data.m_vNormal = Vector( 0, 0, 1 );
	data.m_flScale = 4.0f;

	DispatchEffect( "StriderBlood", data );
	
	// Go away
	m_lifeState = LIFE_DEAD;

	SetThink( &CNPC_Hunter::SUB_Remove );
	SetNextThink( gpGlobals->curtime + 0.1f );

	AddEffects( EF_NODRAW );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
Activity CNPC_Hunter::NPC_TranslateActivity( Activity baseAct )
{
	if ( ( baseAct == ACT_WALK ) || ( baseAct == ACT_RUN ) )
	{
  		if ( GetEnemy() )
  		{
			Vector vecEnemyLKP = GetEnemyLKP();
			
			// Only start facing when we're close enough
			if ( UTIL_DistApprox( vecEnemyLKP, GetAbsOrigin() ) < HUNTER_FACE_ENEMY_DIST )
			{
				return (Activity)ACT_HUNTER_WALK_ANGRY;
			}
		}
	}
	else if ( ( baseAct == ACT_IDLE ) && m_bPlanted )
	{
		return ( Activity )ACT_HUNTER_IDLE_PLANTED;
	}
	else if ( baseAct == ACT_RANGE_ATTACK2 )
	{
		if ( !m_bPlanted && ( m_bEnableUnplantedShooting || IsStriderBuster( GetEnemy() ) ) )
		{
			return (Activity)ACT_HUNTER_RANGE_ATTACK2_UNPLANTED;
		}
	}
	
	return BaseClass::NPC_TranslateActivity( baseAct );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::HandleAnimEvent( animevent_t *pEvent )
{
	Vector footPosition;
	QAngle angles;
	
	if ( pEvent->event == AE_HUNTER_FOOTSTEP_LEFT )
	{
		LeftFootHit( pEvent->eventtime );
		return;
	}

	if ( pEvent->event == AE_HUNTER_FOOTSTEP_RIGHT )
	{
		RightFootHit( pEvent->eventtime );
		return;
	}

	if ( pEvent->event == AE_HUNTER_FOOTSTEP_BACK )
	{
		BackFootHit( pEvent->eventtime );
		return;
	}
	
	if ( pEvent->event == AE_HUNTER_START_EXPRESSION )
	{
		if ( pEvent->options && Q_strlen( pEvent->options ) )
		{
			//m_iszCurrentExpression = AllocPooledString( pEvent->options );
			//SetExpression( pEvent->options );
		}
		return;
	}

	if ( pEvent->event == AE_HUNTER_END_EXPRESSION )
	{
		if ( pEvent->options && Q_strlen( pEvent->options ) )
		{
			//m_iszCurrentExpression = NULL_STRING;
			//RemoveActorFromScriptedScenes( this, true, false, pEvent->options );
		}
		return;
	}

	if ( pEvent->event == AE_HUNTER_MELEE_ANNOUNCE )
	{
		EmitSound( "NPC_Hunter.MeleeAnnounce" );
		return;
	}
		
	if ( pEvent->event == AE_HUNTER_MELEE_ATTACK_LEFT )
	{
		Vector right, forward, dir;
		AngleVectors( GetLocalAngles(), &forward, &right, NULL );

		right = right * -100;
		forward = forward * 600;
		dir = right + forward;
		QAngle angle( 25, 30, -20 );

		MeleeAttack( HUNTER_MELEE_REACH, sk_hunter_dmg_one_slash.GetFloat(), angle, dir, HUNTER_BLOOD_LEFT_FOOT );
		return;
	}

	if ( pEvent->event == AE_HUNTER_MELEE_ATTACK_RIGHT )
	{
		Vector right, forward,dir;
		AngleVectors( GetLocalAngles(), &forward, &right, NULL );

		right = right * 100;
		forward = forward * 600;
		dir = right + forward;
		
		QAngle angle( 25, -30, 20 );

		MeleeAttack( HUNTER_MELEE_REACH, sk_hunter_dmg_one_slash.GetFloat(), angle, dir, HUNTER_BLOOD_LEFT_FOOT );
		return;
	}

	if ( pEvent->event == AE_HUNTER_SPRAY_BLOOD )
	{
		Vector vecOrigin;
		Vector vecDir;
	
		// spray blood from the attachment point
		bool bGotAttachment = false;
		if ( pEvent->options )
		{
			QAngle angDir;
			if ( GetAttachment( pEvent->options, vecOrigin, angDir ) )
			{
				bGotAttachment = true;
				AngleVectors( angDir, &vecDir, NULL, NULL );
			}
		}

		// fall back to our center, tracing forward
		if ( !bGotAttachment )
		{	
			vecOrigin = WorldSpaceCenter();
			GetVectors( &vecDir, NULL, NULL );
		}
		
		UTIL_BloodSpray( vecOrigin, vecDir, BLOOD_COLOR_RED, 4, FX_BLOODSPRAY_ALL );

		for ( int i = 0 ; i < 3 ; i++ )
		{
			Vector vecTraceDir = vecDir;
			vecTraceDir.x += random->RandomFloat( -0.1, 0.1 );
			vecTraceDir.y += random->RandomFloat( -0.1, 0.1 );
			vecTraceDir.z += random->RandomFloat( -0.1, 0.1 );

			trace_t tr;
			AI_TraceLine( vecOrigin, vecOrigin + ( vecTraceDir * 192.0f ), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
			if ( tr.fraction != 1.0 )
			{
				UTIL_BloodDecalTrace( &tr, BLOOD_COLOR_RED );
			}
		}

		return;
	}

	BaseClass::HandleAnimEvent( pEvent );
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::AddEntityRelationship( CBaseEntity *pEntity, Disposition_t nDisposition, int nPriority )
{
	if ( nDisposition ==  D_HT && pEntity->ClassMatches("npc_bullseye") )
		UpdateEnemyMemory( pEntity, pEntity->GetAbsOrigin() );
	BaseClass::AddEntityRelationship( pEntity, nDisposition, nPriority );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Hunter::ScheduledMoveToGoalEntity( int scheduleType, CBaseEntity *pGoalEntity, Activity movementActivity )
{
	if ( IsCurSchedule( SCHED_HUNTER_RANGE_ATTACK1, false ) )
	{
		SetGoalEnt( pGoalEntity );
		return true;
	}
	return BaseClass::ScheduledMoveToGoalEntity( scheduleType, pGoalEntity, movementActivity );
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::OnChangeHintGroup( string_t oldGroup, string_t newGroup )
{
	SetCondition( COND_HUNTER_NEW_HINTGROUP );
	m_CheckHintGroupTimer.Set( 10 );
}


//-----------------------------------------------------------------------------
// Tells whether any given hunter is in a squad that contains other hunters.
// This is useful for preventing timid behavior for Hunters that are not 
// supported by other hunters.
//
// NOTE:	This counts the self! So a hunter that is alone in his squad
//			receives a result of 1.
//-----------------------------------------------------------------------------
int CNPC_Hunter::NumHuntersInMySquad()
{
	AISquadIter_t iter;
	CAI_BaseNPC *pSquadmate = m_pSquad ? m_pSquad->GetFirstMember( &iter ) : NULL;

	if( !pSquadmate )
	{
		// Not in a squad at all, but the caller is not concerned with that. Just
		// tell them that we're in a squad of one (ourself)
		return 1;
	}
	
	int count = 0;

	while ( pSquadmate )
	{
		if( pSquadmate->m_iClassname == m_iClassname )
			count++;

		pSquadmate = m_pSquad->GetNextMember( &iter );
	}

	return count;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::FollowStrider( const char *szStrider )
{
	if ( !szStrider )
		return;

	CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, szStrider, this );
	CNPC_Strider *pStrider = dynamic_cast <CNPC_Strider *>( pEnt );
	FollowStrider(pStrider);
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::FollowStrider( CNPC_Strider * pStrider )
{
	if ( !IsAlive() )
	{
		return;
	}

	if ( pStrider )
	{
		if ( m_EscortBehavior.GetFollowTarget() != pStrider )
		{
			m_iszFollowTarget = pStrider->GetEntityName();
			if ( m_iszFollowTarget == NULL_STRING )
			{
				m_iszFollowTarget = AllocPooledString( "unnamed_strider" );
			}
			m_EscortBehavior.SetEscortTarget( pStrider );
		}
	}
	else
	{
		DevWarning("Hunter set to follow entity %s that is not a strider\n", STRING( m_iszFollowTarget ) );
		m_iszFollowTarget = AllocPooledString( "unknown_strider" );
	}
}

void CAI_HunterEscortBehavior::SetEscortTarget( CNPC_Strider *pStrider, bool fFinishCurSchedule )
{
	m_bEnabled = true;

	if ( GetOuter()->GetSquad() )
	{
		GetOuter()->GetSquad()->RemoveFromSquad( GetOuter() );
	}

	for ( int i = 0; i < g_Hunters.Count(); i++ )
	{
		if ( g_Hunters[i]->m_EscortBehavior.GetFollowTarget() == pStrider )
		{
			Assert( g_Hunters[i]->GetSquad() );
			g_Hunters[i]->GetSquad()->AddToSquad( GetOuter() );
			break;
		}
	}

	if ( !GetOuter()->GetSquad() )
	{
		GetOuter()->AddToSquad( AllocPooledString( CFmtStr( "%s_hunter_squad", STRING( pStrider->GetEntityName() ) ) ) );
	}

	BaseClass::SetFollowTarget( pStrider );
	m_flTimeEscortReturn = gpGlobals->curtime;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::InputEnableUnplantedShooting( inputdata_t &inputdata )
{
	m_bEnableUnplantedShooting = true;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::InputDisableUnplantedShooting( inputdata_t &inputdata )
{
	m_bEnableUnplantedShooting = false;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::InputFollowStrider( inputdata_t &inputdata )
{
	m_iszFollowTarget = inputdata.value.StringID();
	if ( m_iszFollowTarget == s_iszStriderClassname )
	{
		m_EscortBehavior.m_bEnabled = true;
		m_iszFollowTarget = NULL_STRING;
	}
	m_BeginFollowDelay.Start( .1 ); // Allow time for strider to spawn
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::InputUseSiegeTargets( inputdata_t &inputdata )
{
	m_iszSiegeTargetName = inputdata.value.StringID();
	m_flTimeNextSiegeTargetAttack = gpGlobals->curtime + random->RandomFloat( 1, hunter_siege_frequency.GetFloat() );

	if( m_iszSiegeTargetName == NULL_STRING )
	{
		// Turning the feature off. Restore m_flDistTooFar to default.
		m_flDistTooFar = hunter_flechette_max_range.GetFloat();
		m_pSiegeTargets.RemoveAll();
	}
	else
	{
		// We're going into siege mode. Adjust range accordingly.
		m_flDistTooFar = hunter_flechette_max_range.GetFloat() * HUNTER_SIEGE_MAX_DIST_MODIFIER;
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::InputDodge( inputdata_t &inputdata )
{
	SetCondition( COND_HUNTER_FORCED_DODGE );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::InputFlankEnemy( inputdata_t &inputdata )
{
	SetCondition( COND_HUNTER_FORCED_FLANK_ENEMY );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::InputDisableShooting( inputdata_t &inputdata )
{
	m_bDisableShooting = true;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::InputEnableShooting( inputdata_t &inputdata )
{
	m_bDisableShooting = false;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::InputEnableSquadShootDelay( inputdata_t &inputdata )
{
	m_bEnableSquadShootDelay = true;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::InputDisableSquadShootDelay( inputdata_t &inputdata )
{
	m_bEnableSquadShootDelay = false;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Hunter::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
{
	return BaseClass::FVisible( pEntity, traceMask, ppBlocker );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Hunter::IsValidEnemy( CBaseEntity *pTarget )
{
	if ( IsStriderBuster( pTarget) )
	{
		if ( !m_EscortBehavior.m_bEnabled || !m_EscortBehavior.GetEscortTarget() )
		{
			// We only hate striderbusters when we are actively protecting a strider.
			return false;
		}

		if ( pTarget->VPhysicsGetObject() )
		{
			if ( ( pTarget->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) &&
				hunter_hate_held_striderbusters.GetBool() )
			{
				if ( gpGlobals->curtime - StriderBuster_GetPickupTime( pTarget ) > hunter_hate_held_striderbusters_delay.GetFloat())
				{
					if ( StriderBuster_NumFlechettesAttached( pTarget ) <= 2 )
					{
						if ( m_EscortBehavior.GetEscortTarget() && 
							( m_EscortBehavior.GetEscortTarget()->GetAbsOrigin().AsVector2D() - pTarget->GetAbsOrigin().AsVector2D() ).LengthSqr() < Square( hunter_hate_held_striderbusters_tolerance.GetFloat() ) )
						{
							return true;
						}
					}
				}
				return false;
			}

			bool bThrown = ( pTarget->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_WAS_THROWN ) != 0;
			bool bAttached = StriderBuster_IsAttachedStriderBuster( pTarget );

			if ( ( bThrown && !bAttached ) && hunter_hate_thrown_striderbusters.GetBool() )
			{
				float t;
				float dist = CalcDistanceSqrToLineSegment2D( m_EscortBehavior.GetEscortTarget()->GetAbsOrigin().AsVector2D(), 
					pTarget->GetAbsOrigin().AsVector2D(), 
					pTarget->GetAbsOrigin().AsVector2D() + pTarget->GetSmoothedVelocity().AsVector2D(), &t );

				if ( t > 0 && dist < Square( hunter_hate_thrown_striderbusters_tolerance.GetFloat() ))
				{
					return true;
				}
				return false;
			}

			if ( bAttached && StriderBuster_IsAttachedStriderBuster( pTarget, m_EscortBehavior.GetEscortTarget() ) && hunter_hate_attached_striderbusters.GetBool() )
			{
				return true;
			}
		}
		return false;
	}

	return BaseClass::IsValidEnemy( pTarget );
}



//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
Disposition_t CNPC_Hunter::IRelationType( CBaseEntity *pTarget )
{
	if ( !pTarget )
		return D_NU;

	if ( IsStriderBuster( pTarget ) )
	{
		if ( HateThisStriderBuster( pTarget ) )
			return D_HT;

		return D_NU;
	}

	if ( hunter_retreat_striderbusters.GetBool() )
	{
		if ( pTarget->IsPlayer() && (m_hAttachedBusters.Count() > 0) )
		{
			return D_FR;
		}
	}
		
	return BaseClass::IRelationType( pTarget );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CNPC_Hunter::IRelationPriority( CBaseEntity *pTarget )
{
	if ( IsStriderBuster( pTarget ) )
	{
		// If we're here, we already know that we hate striderbusters.
		return 1000.0f;
	}

	return BaseClass::IRelationPriority( pTarget );
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::SetSquad( CAI_Squad *pSquad )
{
	BaseClass::SetSquad( pSquad );
	if ( pSquad && pSquad->NumMembers() == 1 )
	{
		pSquad->SetSquadData( HUNTER_RUNDOWN_SQUADDATA, 0 );
	}
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::OnSeeEntity( CBaseEntity *pEntity )
{
	BaseClass::OnSeeEntity(pEntity);

	if ( IsStriderBuster( pEntity ) && IsValidEnemy( pEntity ) )
	{
		SetCondition( COND_HUNTER_SEE_STRIDERBUSTER );
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Hunter::UpdateEnemyMemory( CBaseEntity *pEnemy, const Vector &position, CBaseEntity *pInformer )
{
	//EmitSound( "NPC_Hunter.Alert" );
	return BaseClass::UpdateEnemyMemory( pEnemy, position, pInformer );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Hunter::CanPlantHere( const Vector &vecPos )
{
	// TODO: cache results?
	//if ( vecPos == m_vecLastCanPlantHerePos )
	//{
	//	return m_bLastCanPlantHere;
	//}

	Vector vecMins = GetHullMins();
	Vector vecMaxs = GetHullMaxs();
	
	vecMins.x -= 16;
	vecMins.y -= 16;

	vecMaxs.x += 16;
	vecMaxs.y += 16;
	vecMaxs.z -= hunter_plant_adjust_z.GetInt();
	
	bool bResult = false;

	trace_t tr;
	UTIL_TraceHull( vecPos, vecPos, vecMins, vecMaxs, MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
	if ( tr.startsolid )
	{
		// Try again, tracing down from above.
		Vector vecStart = vecPos;
		vecStart.z += hunter_plant_adjust_z.GetInt();
		
		UTIL_TraceHull( vecStart, vecPos, vecMins, vecMaxs, MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
	}
	
	if ( tr.startsolid )
	{
		//NDebugOverlay::Box( vecPos, vecMins, vecMaxs, 255, 0, 0, 0, 0 );
	}
	else
	{	
		//NDebugOverlay::Box( vecPos, vecMins, vecMaxs, 0, 255, 0, 0, 0 );
		bResult = true;
	}

	// Cache the results in case we ask again for the same spot.	
	//m_vecLastCanPlantHerePos = vecPos;
	//m_bLastCanPlantHere = bResult;

	return bResult;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CNPC_Hunter::MeleeAttack1ConditionsVsEnemyInVehicle( CBaseCombatCharacter *pEnemy, float flDot )
{
	if( !IsCorporealEnemy( GetEnemy() ) )
		return COND_NONE;

	// Try and trace a box to the player, and if I hit the vehicle, attack it
	Vector vecDelta = (pEnemy->WorldSpaceCenter() - WorldSpaceCenter());
	VectorNormalize( vecDelta );
	trace_t	tr;
	AI_TraceHull( WorldSpaceCenter(), WorldSpaceCenter() + (vecDelta * 64), -Vector(8,8,8), Vector(8,8,8), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
	if ( tr.fraction != 1.0 && tr.m_pEnt == pEnemy->GetVehicleEntity() )
	{
		// We're near the vehicle. Are we facing it?
		if (flDot < 0.7)
			return COND_NOT_FACING_ATTACK;

		return COND_CAN_MELEE_ATTACK1;
	}

	return COND_TOO_FAR_TO_ATTACK;
}


//-----------------------------------------------------------------------------
// For innate melee attack
//-----------------------------------------------------------------------------
int CNPC_Hunter::MeleeAttack1Conditions ( float flDot, float flDist )
{
	if ( !IsCorporealEnemy( GetEnemy() ) )
		return COND_NONE;
		
	if ( ( gpGlobals->curtime < m_flNextMeleeTime ) && // allow berzerk bashing if cornered
		!( m_hAttachedBusters.Count() > 0 && gpGlobals->curtime < m_fCorneredTimer ) )
	{
		return COND_NONE;
	}

	if ( GetEnemy()->Classify() == CLASS_PLAYER_ALLY_VITAL )
	{
		return COND_NONE;
	}
		
	if ( flDist > HUNTER_MELEE_REACH )
	{
		// Translate a hit vehicle into its passenger if found
		if ( GetEnemy() != NULL )
		{
			CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer();
			if ( pCCEnemy != NULL && pCCEnemy->IsInAVehicle() )
			{
				return MeleeAttack1ConditionsVsEnemyInVehicle( pCCEnemy, flDot );
			}

#if defined(HL2_DLL) && !defined(HL2MP)
			// If the player is holding an object, knock it down.
			if ( GetEnemy()->IsPlayer() )
			{
				CBasePlayer *pPlayer = ToBasePlayer( GetEnemy() );

				Assert( pPlayer != NULL );

				// Is the player carrying something?
				CBaseEntity *pObject = GetPlayerHeldEntity(pPlayer);

				if ( !pObject )
				{
					pObject = PhysCannonGetHeldEntity( pPlayer->GetActiveWeapon() );
				}

				if ( pObject )
				{
					float flDist = pObject->WorldSpaceCenter().DistTo( WorldSpaceCenter() );

					if ( flDist <= HUNTER_MELEE_REACH )
					{
						return COND_CAN_MELEE_ATTACK1;
					}
				}
			}
#endif
		}
	
		return COND_TOO_FAR_TO_ATTACK;
	}

	if (flDot < 0.7)
	{
		return COND_NOT_FACING_ATTACK;
	}

	// Build a cube-shaped hull, the same hull that MeleeAttack is going to use.
	Vector vecMins = GetHullMins();
	Vector vecMaxs = GetHullMaxs();
	vecMins.z = vecMins.x;
	vecMaxs.z = vecMaxs.x;

	Vector forward;
	GetVectors( &forward, NULL, NULL );

	trace_t	tr;
	AI_TraceHull( WorldSpaceCenter(), WorldSpaceCenter() + forward * HUNTER_MELEE_REACH, vecMins, vecMaxs, MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );

	if ( tr.fraction == 1.0 || !tr.m_pEnt )
	{
		// This attack would miss completely. Trick the hunter into moving around some more.
		return COND_TOO_FAR_TO_ATTACK;
	}

	if ( tr.m_pEnt == GetEnemy() || tr.m_pEnt->IsNPC() || (tr.m_pEnt->m_takedamage == DAMAGE_YES && (dynamic_cast<CBreakableProp*>(tr.m_pEnt))) )
	{
		// Let the hunter swipe at his enemy if he's going to hit them.
		// Also let him swipe at NPC's that happen to be between the hunter and the enemy. 
		// This makes mobs of hunters seem more rowdy since it doesn't leave guys in the back row standing around.
		// Also let him swipe at things that takedamage, under the assumptions that they can be broken.
		return COND_CAN_MELEE_ATTACK1;
	}

	// dvs TODO: incorporate this
	/*if ( tr.m_pEnt->IsBSPModel() )
	{
		// The trace hit something solid, but it's not the enemy. If this item is closer to the hunter than
		// the enemy is, treat this as an obstruction.
		Vector vecToEnemy = GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter();
		Vector vecTrace = tr.endpos - tr.startpos;

		if ( vecTrace.Length2DSqr() < vecToEnemy.Length2DSqr() )
		{
			return COND_HUNTER_LOCAL_MELEE_OBSTRUCTION;
		}
	}*/

	if ( !tr.m_pEnt->IsWorld() && GetEnemy() && GetEnemy()->GetGroundEntity() == tr.m_pEnt )
	{
		// Try to swat whatever the player is standing on instead of acting like a dill.
		return COND_CAN_MELEE_ATTACK1;
	}

	// Move around some more
	return COND_TOO_FAR_TO_ATTACK;
}


//-----------------------------------------------------------------------------
// For innate melee attack
//-----------------------------------------------------------------------------
int CNPC_Hunter::MeleeAttack2Conditions ( float flDot, float flDist )
{
	return COND_NONE;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Hunter::IsCorporealEnemy( CBaseEntity *pEnemy ) 
{
	if( !pEnemy )
		return false;

	// Generally speaking, don't melee attack anything the player can't see.
	if( pEnemy->IsEffectActive( EF_NODRAW ) )
		return false;

	// Don't flank, melee attack striderbusters.
	if ( IsStriderBuster( pEnemy ) )
		return false;

	return true;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CNPC_Hunter::RangeAttack1Conditions( float flDot, float flDist )
{
	return COND_NONE;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CNPC_Hunter::RangeAttack2Conditions( float flDot, float flDist )
{
	bool bIsBuster = IsStriderBuster( GetEnemy() );
	bool bIsPerfectBullseye = ( GetEnemy() && dynamic_cast<CNPC_Bullseye *>(GetEnemy()) && ((CNPC_Bullseye *)GetEnemy())->UsePerfectAccuracy() );

	if ( !bIsPerfectBullseye && !bIsBuster && !hunter_flechette_test.GetBool() && ( gpGlobals->curtime < m_flNextRangeAttack2Time ) )
	{
		return COND_NONE;
	}

	if ( m_bDisableShooting )
	{
		return COND_NONE;
	}

	if ( !HasCondition( COND_SEE_ENEMY ) )
	{
		return COND_NONE;
	}

	float flMaxFlechetteRange = hunter_flechette_max_range.GetFloat();

	if ( IsUsingSiegeTargets() )
	{
		flMaxFlechetteRange *= HUNTER_SIEGE_MAX_DIST_MODIFIER;
	}

 	if ( !bIsBuster && ( flDist > flMaxFlechetteRange ) )
	{
		return COND_TOO_FAR_TO_ATTACK;
	}
	else if ( !bIsBuster && ( !GetEnemy() || !GetEnemy()->ClassMatches( "npc_bullseye" ) ) && flDist < hunter_flechette_min_range.GetFloat() )
	{
		return COND_TOO_CLOSE_TO_ATTACK;
	}
	else if ( flDot < HUNTER_FACING_DOT )
	{
		return COND_NOT_FACING_ATTACK;
	}
	
	if ( !bIsBuster && !m_bEnableUnplantedShooting && !hunter_flechette_test.GetBool() && !CanPlantHere( GetAbsOrigin() ) )
	{
		return COND_HUNTER_CANT_PLANT;
	}

	return COND_CAN_RANGE_ATTACK2;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Hunter::WeaponLOSCondition(const Vector &ownerPos, const Vector &targetPos, bool bSetConditions)
{
	CBaseEntity *pTargetEnt;

	pTargetEnt = GetEnemy();

	trace_t tr;
	Vector vFrom = ownerPos + GetViewOffset();
	AI_TraceLine( vFrom, targetPos, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );

	if ( ( pTargetEnt && tr.m_pEnt == pTargetEnt) || tr.fraction == 1.0 || CanShootThrough( tr, targetPos ) )
	{
		static Vector vMins( -2.0, -2.0, -2.0 );
		static Vector vMaxs( -vMins);
		// Hit the enemy, or hit nothing (traced all the way to a nonsolid enemy like a bullseye)
		AI_TraceHull( vFrom - Vector( 0, 0, 18 ), targetPos, vMins, vMaxs, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);

		if ( ( pTargetEnt && tr.m_pEnt == pTargetEnt) || tr.fraction == 1.0 || CanShootThrough( tr, targetPos ) )
		{
			if ( hunter_show_weapon_los_condition.GetBool() )
			{
				NDebugOverlay::Line( vFrom, targetPos, 255, 0, 255, false, 0.1 );
				NDebugOverlay::Line( vFrom - Vector( 0, 0, 18 ), targetPos, 0, 0, 255, false, 0.1 );
			}
			return true;
		}
	}
	else if ( bSetConditions )
	{
		SetCondition( COND_WEAPON_SIGHT_OCCLUDED );
		SetEnemyOccluder( tr.m_pEnt );
	}

	return false;
}

//-----------------------------------------------------------------------------
// Look in front and see if the claw hit anything.
//
// Input  :	flDist				distance to trace		
//			iDamage				damage to do if attack hits
//			vecViewPunch		camera punch (if attack hits player)
//			vecVelocityPunch	velocity punch (if attack hits player)
//
// Output : The entity hit by claws. NULL if nothing.
//-----------------------------------------------------------------------------
CBaseEntity *CNPC_Hunter::MeleeAttack( float flDist, int iDamage, QAngle &qaViewPunch, Vector &vecVelocityPunch, int BloodOrigin  )
{
	// Added test because claw attack anim sometimes used when for cases other than melee
	if ( GetEnemy() )
	{
		trace_t	tr;
		AI_TraceHull( WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), -Vector(8,8,8), Vector(8,8,8), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );

		if ( tr.fraction < 1.0f )
			return NULL;
	}

	//
	// Trace out a cubic section of our hull and see what we hit.
	//
	Vector vecMins = GetHullMins();
	Vector vecMaxs = GetHullMaxs();
	vecMins.z = vecMins.x;
	vecMaxs.z = vecMaxs.x;

	CBaseEntity *pHurt = CheckTraceHullAttack( flDist, vecMins, vecMaxs, iDamage, DMG_SLASH );

	if ( pHurt )
	{
		EmitSound( "NPC_Hunter.MeleeHit" );
		EmitSound( "NPC_Hunter.TackleHit" );

		CBasePlayer *pPlayer = ToBasePlayer( pHurt );

		if ( pPlayer != NULL && !(pPlayer->GetFlags() & FL_GODMODE ) )
		{
			pPlayer->ViewPunch( qaViewPunch );
			pPlayer->VelocityPunch( vecVelocityPunch );
			
			// Shake the screen
			UTIL_ScreenShake( pPlayer->GetAbsOrigin(), 100.0, 1.5, 1.0, 2, SHAKE_START );

			// Red damage indicator
			color32 red = { 128, 0, 0, 128 };
			UTIL_ScreenFade( pPlayer, red, 1.0f, 0.1f, FFADE_IN );

			/*if ( UTIL_ShouldShowBlood( pPlayer->BloodColor() ) )
			{
				// Spray some of the player's blood on the hunter.			
				trace_t tr;
				
				Vector vecHunterEyePos; // = EyePosition();
				QAngle angDiscard;
				GetBonePosition( LookupBone( "MiniStrider.top_eye_bone" ), vecHunterEyePos, angDiscard );

				Vector vecPlayerEyePos = pPlayer->EyePosition();
				
				Vector vecDir = vecHunterEyePos - vecPlayerEyePos;
				float flLen = VectorNormalize( vecDir );
				
				Vector vecStart = vecPlayerEyePos - ( vecDir * 64 );
				Vector vecEnd = vecPlayerEyePos + ( vecDir * ( flLen + 64 ) );
				
				NDebugOverlay::HorzArrow( vecStart, vecEnd, 16, 255, 255, 0, 255, false, 10 );
				
				UTIL_TraceLine( vecStart, vecEnd, MASK_SHOT, pPlayer, COLLISION_GROUP_NONE, &tr );
	
				if ( tr.m_pEnt )
				{
					Msg( "Hit %s!!!\n", tr.m_pEnt->GetDebugName() );
					UTIL_DecalTrace( &tr, "Blood" );
				}
			}*/
		}
		else if ( !pPlayer )
		{
			if ( IsMovablePhysicsObject( pHurt ) )
			{
				// If it's a vphysics object that's too heavy, crash into it too.
				IPhysicsObject *pPhysics = pHurt->VPhysicsGetObject();
				if ( pPhysics )
				{
					// If the object is being held by the player, break it or make them drop it.
					if ( pPhysics->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
					{
						// If it's breakable, break it.
						if ( pHurt->m_takedamage == DAMAGE_YES )
						{
							CBreakableProp *pBreak = dynamic_cast<CBreakableProp*>(pHurt);
							if ( pBreak )
							{
								CTakeDamageInfo info( this, this, 20, DMG_SLASH );
								pBreak->Break( this, info );
							}
						}
					}
				}
			}		
		
			if ( UTIL_ShouldShowBlood(pHurt->BloodColor()) )
			{
				// Hit an NPC. Bleed them!
				Vector vecBloodPos;

				switch ( BloodOrigin )
				{
					case HUNTER_BLOOD_LEFT_FOOT:
					{
						if ( GetAttachment( "blood_left", vecBloodPos ) )
						{
							SpawnBlood( vecBloodPos, g_vecAttackDir, pHurt->BloodColor(), MIN( iDamage, 30 ) );
						}
						
						break;
					}
				}
			}
		}
	}
	else 
	{
		// TODO:
		//AttackMissSound();
	}

	m_flNextMeleeTime = gpGlobals->curtime + hunter_melee_delay.GetFloat();

	return pHurt;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Hunter::TestShootPosition(const Vector &vecShootPos, const Vector &targetPos )	
{ 
	if ( !CanPlantHere(vecShootPos ) )
	{
		return false;
	}

	return BaseClass::TestShootPosition( vecShootPos, targetPos );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
Vector CNPC_Hunter::Weapon_ShootPosition( )
{
	matrix3x4_t gunMatrix;
	GetAttachment( gm_nTopGunAttachment, gunMatrix );

	Vector vecShootPos;
	MatrixGetColumn( gunMatrix, 3, vecShootPos );

	return vecShootPos;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType )
{
	float flTracerDist;
	Vector vecDir;
	Vector vecEndPos;

	vecDir = tr.endpos - vecTracerSrc;

	flTracerDist = VectorNormalize( vecDir );

	int nAttachment = LookupAttachment( "MiniGun" );

	UTIL_Tracer( vecTracerSrc, tr.endpos, nAttachment, TRACER_FLAG_USEATTACHMENT, 5000, true, "HunterTracer" );
}


//-----------------------------------------------------------------------------
// Trace didn't hit the intended target, but should the hunter
// shoot anyway? We use this to get the hunter to destroy 
// breakables that are between him and his target.
//-----------------------------------------------------------------------------
bool CNPC_Hunter::CanShootThrough( const trace_t &tr, const Vector &vecTarget )
{
	if ( !tr.m_pEnt )
	{
		return false;
	}

	if ( !tr.m_pEnt->GetHealth() )
	{
		return false;
	}
	
	// Don't try to shoot through allies.
	CAI_BaseNPC *pNPC = tr.m_pEnt->MyNPCPointer();
	if ( pNPC && ( IRelationType( pNPC ) == D_LI ) )
	{
		return false;
	}

	// Would a trace ignoring this entity continue to the target?
	trace_t continuedTrace;
	AI_TraceLine( tr.endpos, vecTarget, MASK_SHOT, tr.m_pEnt, COLLISION_GROUP_NONE, &continuedTrace );

	if ( continuedTrace.fraction != 1.0 )
	{
		if ( continuedTrace.m_pEnt != GetEnemy() )
		{
			return false;
		}
	}

	return true;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CNPC_Hunter::GetSoundInterests()
{
	return SOUND_WORLD | SOUND_COMBAT | SOUND_PLAYER | SOUND_DANGER | SOUND_PHYSICS_DANGER | SOUND_PLAYER_VEHICLE | SOUND_BULLET_IMPACT | SOUND_MOVE_AWAY;
}

//-----------------------------------------------------------------------------
// Tells us whether the Hunter is acting in a large, outdoor map, 
// currently only ep2_outland_12. This allows us to create logic
// branches here in the AI code so that we can make choices that
// tailor behavior to larger and smaller maps.
//-----------------------------------------------------------------------------
bool CNPC_Hunter::IsInLargeOutdoorMap()
{
	return m_bInLargeOutdoorMap;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::AlertSound()
{
	EmitSound( "NPC_Hunter.Alert" );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::PainSound( const CTakeDamageInfo &info )
{
	if ( gpGlobals->curtime > m_flNextDamageTime )
	{
		EmitSound( "NPC_Hunter.Pain" );
		m_flNextDamageTime = gpGlobals->curtime + random->RandomFloat( 0.5, 1.2 ); 
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::DeathSound( const CTakeDamageInfo &info )
{
	EmitSound( "NPC_Hunter.Death" );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
{
	CTakeDamageInfo info = inputInfo;

	// Even though the damage might not hurt us, we want to react to it
	// if it's from the player.
	if ( info.GetAttacker()->IsPlayer() )
	{
		if ( !HasMemory( bits_MEMORY_PROVOKED ) )
		{
			GetEnemies()->ClearMemory( info.GetAttacker() );
			Remember( bits_MEMORY_PROVOKED );
			SetCondition( COND_LIGHT_DAMAGE );
		}
	}

	// HUnters have special resisitance to some types of damage.
	if ( ( info.GetDamageType() & DMG_BULLET ) ||
		 ( info.GetDamageType() & DMG_BUCKSHOT ) ||
		 ( info.GetDamageType() & DMG_CLUB ) ||
		 ( info.GetDamageType() & DMG_NEVERGIB ) )
	{
		float flScale = 1.0;
		
		if ( info.GetDamageType() & DMG_BUCKSHOT )
		{
			flScale = sk_hunter_buckshot_damage_scale.GetFloat();
		}
		else if ( ( info.GetDamageType() & DMG_BULLET ) || ( info.GetDamageType() & DMG_NEVERGIB ) )
		{
			// Hunters resist most bullet damage, but they are actually vulnerable to .357 rounds, 
			// since players regard that weapon as one of the game's truly powerful weapons.
			if( info.GetAmmoType() == GetAmmoDef()->Index("357") )
			{
				flScale = 1.16f;
			}
			else
			{
				flScale = sk_hunter_bullet_damage_scale.GetFloat();
			}
		}

		if ( GetActivity() == ACT_HUNTER_CHARGE_RUN )
		{
			flScale *= sk_hunter_charge_damage_scale.GetFloat();
		}
		
		if ( flScale != 0 )
		{
			float flDamage = info.GetDamage() * flScale;
			info.SetDamage( flDamage );
		}
		
		QAngle vecAngles;
		VectorAngles( ptr->plane.normal, vecAngles );
		DispatchParticleEffect( "blood_impact_synth_01", ptr->endpos, vecAngles );
		DispatchParticleEffect( "blood_impact_synth_01_arc_parent", PATTACH_POINT_FOLLOW, this, gm_nHeadCenterAttachment );
	}

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


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
const impactdamagetable_t &CNPC_Hunter::GetPhysicsImpactDamageTable()
{
	return s_HunterImpactDamageTable;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::PhysicsDamageEffect( const Vector &vecPos, const Vector &vecDir )
{
	CEffectData data;
	data.m_vOrigin = vecPos;
	data.m_vNormal = vecDir;
	DispatchEffect( "HunterDamage", data );

	if ( random->RandomInt( 0, 1 ) == 0 )
	{
		CBaseEntity *pTrail = CreateEntityByName( "sparktrail" );
		pTrail->SetOwnerEntity( this );
		pTrail->Spawn();
	}
}


//-----------------------------------------------------------------------------
// We were hit by a strider buster. Do the tesla effect on our hitboxes.
//-----------------------------------------------------------------------------
void CNPC_Hunter::TeslaThink()
{
	CEffectData data;
	data.m_nEntIndex = entindex();
	data.m_flMagnitude = 3;
	data.m_flScale = 0.5f;
	DispatchEffect( "TeslaHitboxes", data );
	EmitSound( "RagdollBoogie.Zap" );

	if ( gpGlobals->curtime < m_flTeslaStopTime )
	{
		SetContextThink( &CNPC_Hunter::TeslaThink, gpGlobals->curtime + random->RandomFloat( 0.1f, 0.3f ), HUNTER_ZAP_THINK ); 
	}
}


//-----------------------------------------------------------------------------
// Our health is low. Show damage effects.
//-----------------------------------------------------------------------------
void CNPC_Hunter::BleedThink()
{
	// Spurt blood from random points on the hunter's head.
	Vector vecOrigin;
	QAngle angDir;
	GetAttachment( gm_nHeadCenterAttachment, vecOrigin, angDir );
	
	Vector vecDir = RandomVector( -1, 1 );
	VectorNormalize( vecDir );
	VectorAngles( vecDir, Vector( 0, 0, 1 ), angDir );

	vecDir *= gm_flHeadRadius;
	DispatchParticleEffect( "blood_spurt_synth_01", vecOrigin + vecDir, angDir );

	SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.6, 1.5 ), HUNTER_BLEED_THINK );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Hunter::IsHeavyDamage( const CTakeDamageInfo &info )
{
	if ( info.GetDamage() < 45 )
	{
		return false;
	}

	if ( info.GetDamage() < 180 )
	{
		if ( !m_HeavyDamageDelay.Expired() || !BaseClass::IsHeavyDamage( info ) )
		{
			return false;
		}
	}

	m_HeavyDamageDelay.Set( 15, 25 );
	return true;

}


//-----------------------------------------------------------------------------
// We've taken some damage. Maybe we should flinch because of it.
//-----------------------------------------------------------------------------
void CNPC_Hunter::ConsiderFlinching( const CTakeDamageInfo &info )
{
	if ( !m_FlinchTimer.Expired() )
	{
		// Someone is whaling on us. Push out the timer so we don't keep flinching.
		m_FlinchTimer.Set( random->RandomFloat( 0.3 ) );
		return;
	}

	if ( GetState() == NPC_STATE_SCRIPT )
	{
		return;
	}

	Activity eGesture = ACT_HUNTER_FLINCH_N;

	Vector forward;
	GetVectors( &forward, NULL, NULL );
	
	Vector vecForceDir = info.GetDamageForce();
	VectorNormalize( vecForceDir );
	
	float flDot = DotProduct( forward, vecForceDir );
	
	if ( flDot > 0.707 )
	{
		// flinch forward
		eGesture = ACT_HUNTER_FLINCH_N;
	}
	else if ( flDot < -0.707 )
	{
		// flinch back
		eGesture = ACT_HUNTER_FLINCH_S;
	}
	else
	{
		// flinch left or right
		Vector cross = CrossProduct( forward, vecForceDir );
		
		if ( cross.z > 0 )
		{
			eGesture = ACT_HUNTER_FLINCH_W;
		}
		else
		{
			eGesture = ACT_HUNTER_FLINCH_E;
		}
	}

	if ( !IsPlayingGesture( eGesture ) )
	{
		RestartGesture( eGesture );
		m_FlinchTimer.Set( random->RandomFloat( 0.3, 1.0 ) );
	}
}


//-----------------------------------------------------------------------------
// This is done from a think function because when the hunter is killed,
// the physics code puts the vehicle's pre-collision velocity back so the jostle
// is basically discared.
//-----------------------------------------------------------------------------
void CNPC_Hunter::JostleVehicleThink()
{
	CBaseEntity *pInflictor = m_hHitByVehicle;
	if ( !pInflictor )
		return;

	Vector vecVelDir = pInflictor->GetSmoothedVelocity();
	float flSpeed = VectorNormalize( vecVelDir );
	Vector vecForce = CrossProduct( vecVelDir, Vector( 0, 0, 1 ) );
	if ( DotProduct( vecForce, GetAbsOrigin() ) < DotProduct( vecForce, pInflictor->GetAbsOrigin() ) )
	{
		// We're to the left of the vehicle that's hitting us.
		vecForce *= -1;
	}

	VectorNormalize( vecForce );
	vecForce.z = 1.0;

	float flForceScale = RemapValClamped( flSpeed, hunter_jostle_car_min_speed.GetFloat(), hunter_jostle_car_max_speed.GetFloat(), 50.0f, 150.0f );

	vecForce *= ( flForceScale * pInflictor->VPhysicsGetObject()->GetMass() );

	pInflictor->VPhysicsGetObject()->ApplyForceOffset( vecForce, WorldSpaceCenter() );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CNPC_Hunter::OnTakeDamage( const CTakeDamageInfo &info )
{
	CTakeDamageInfo myInfo = info;

	if ( ( info.GetDamageType() & DMG_CRUSH ) && !( info.GetDamageType() & DMG_VEHICLE ) )
	{
		// Don't take damage from physics objects that weren't thrown by the player.
		CBaseEntity *pInflictor = info.GetInflictor();

		IPhysicsObject *pObj = pInflictor->VPhysicsGetObject();
		//Assert( pObj );

		if ( !pObj || !pInflictor->HasPhysicsAttacker( 4.0 ) )
		{
			myInfo.SetDamage( 0 );
		}
		else
		{
			// Physics objects that have flechettes stuck in them spoof
			// a flechette hitting us so we dissolve when killed and award
			// the achievement of killing a hunter with its flechettes.
			CUtlVector<CBaseEntity *> children;
			GetAllChildren( pInflictor, children );
			for (int i = 0; i < children.Count(); i++ )
			{
				CBaseEntity *pent = children.Element( i );
				if ( dynamic_cast<CHunterFlechette *>( pent ) )
				{
					myInfo.SetInflictor( pent );
					myInfo.SetDamageType( myInfo.GetDamageType() | DMG_DISSOLVE );
				}
			}
		}
	}
	
	return BaseClass::OnTakeDamage( myInfo );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CNPC_Hunter::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
	CTakeDamageInfo myInfo = info;

	// don't take damage from my own weapons!!!
	// Exception: I "own" a magnade if it's glued to me.
	CBaseEntity *pInflictor = info.GetInflictor();
	CBaseEntity *pAttacker = info.GetAttacker();
	if ( pInflictor )
	{
		if ( IsStriderBuster( pInflictor ) )
		{
			// Get a tesla effect on our hitboxes for a little while.
			SetContextThink( &CNPC_Hunter::TeslaThink, gpGlobals->curtime, HUNTER_ZAP_THINK ); 
			m_flTeslaStopTime = gpGlobals->curtime + 2.0f;
			
			myInfo.SetDamage( sk_hunter_dmg_from_striderbuster.GetFloat() ) ;

			SetCondition( COND_HUNTER_STAGGERED );
		}
		else if ( pInflictor->ClassMatches( GetClassname() ) && !( info.GetDamageType() == DMG_GENERIC ) )
		{
			return 0;
		}
		else if ( pInflictor->ClassMatches( "hunter_flechette" ) ) 
		{
			if ( !( ( CHunterFlechette *)pInflictor )->WasThrownBack() )
			{
				// Flechettes only hurt us if they were thrown back at us by the player. This prevents
				// hunters from hurting themselves when they walk into their own flechette clusters.
				return 0;
			}
		}
	}

	if ( m_EscortBehavior.GetFollowTarget() && m_EscortBehavior.GetFollowTarget() == pAttacker )
	{
		return 0;
	}

	bool bHitByUnoccupiedCar = false;
	if ( ( ( info.GetDamageType() & DMG_CRUSH ) && ( pAttacker && pAttacker->IsPlayer() ) ) ||
		 ( info.GetDamageType() & DMG_VEHICLE ) )
	{
		// myInfo, not info! it may have been modified above.
		float flDamage = myInfo.GetDamage();
		if ( flDamage < HUNTER_MIN_PHYSICS_DAMAGE )
		{
			//DevMsg( "hunter: <<<< ZERO PHYSICS DAMAGE: %f\n", flDamage );
			myInfo.SetDamage( 0 );
		}
		else
		{
			CBaseEntity *pInflictor = info.GetInflictor();
			if ( ( info.GetDamageType() & DMG_VEHICLE ) || 
				 ( pInflictor && pInflictor->GetServerVehicle() && 
				   ( ( bHitByUnoccupiedCar = ( dynamic_cast<CPropVehicleDriveable *>(pInflictor) && static_cast<CPropVehicleDriveable *>(pInflictor)->GetDriver() == NULL ) )  == false ) ) )
			{
				// Adjust the damage from vehicles.
				flDamage *= sk_hunter_vehicle_damage_scale.GetFloat();
				myInfo.SetDamage( flDamage );

				// Apply a force to jostle the vehicle that hit us.
				// Pick a force direction based on which side we're on relative to the vehicle's motion.
				Vector vecVelDir = pInflictor->GetSmoothedVelocity();
				if ( vecVelDir.Length() >= hunter_jostle_car_min_speed.GetFloat() )
				{
					EmitSound( "NPC_Hunter.HitByVehicle" );
					m_hHitByVehicle = pInflictor;
					SetContextThink( &CNPC_Hunter::JostleVehicleThink, gpGlobals->curtime, HUNTER_JOSTLE_VEHICLE_THINK );
				}
			}

			if ( !bHitByUnoccupiedCar )
			{
				SetCondition( COND_HUNTER_STAGGERED );
			}
		}
		
		//DevMsg( "hunter: >>>> PHYSICS DAMAGE: %f (was %f)\n", flDamage, info.GetDamage() );
	}

	// Show damage effects if we actually took damage.
	if ( ( myInfo.GetDamageType() & ( DMG_CRUSH | DMG_BLAST ) ) && ( myInfo.GetDamage() > 0 ) )
	{
		if ( !bHitByUnoccupiedCar )
			SetCondition( COND_HUNTER_STAGGERED );
	}
	
	if ( HasCondition( COND_HUNTER_STAGGERED ) )
	{
		// Throw a bunch of gibs out
		Vector vecForceDir = -myInfo.GetDamageForce();
		VectorNormalize( vecForceDir );
		PhysicsDamageEffect( myInfo.GetDamagePosition(), vecForceDir );

		// Stagger away from the direction the damage came from.
		m_vecStaggerDir = myInfo.GetDamageForce();
		VectorNormalize( m_vecStaggerDir );
	}

	// Take less damage from citizens and Alyx, otherwise hunters go down too easily.
	float flScale = 1.0;

	if ( pAttacker &&
		( ( pAttacker->Classify() == CLASS_CITIZEN_REBEL ) ||
		  ( pAttacker->Classify() == CLASS_PLAYER_ALLY ) || 
		  ( pAttacker->Classify() == CLASS_PLAYER_ALLY_VITAL ) ) )
	{
		flScale *= sk_hunter_citizen_damage_scale.GetFloat();
	}
	
	if ( flScale != 0 )
	{
		// We're taking a nonzero amount of damage.

		// If we're not staggering, consider flinching!
		if ( !HasCondition( COND_HUNTER_STAGGERED ) )
		{
			ConsiderFlinching( info );
		}
		
		if( pAttacker && pAttacker->IsPlayer() )
		{
			// This block of code will distract the Hunter back to the player if the 
			// player does harm to the Hunter but is not the Hunter's current enemy.
			// This is achieved by updating the Hunter's enemy memory of the player and 
			// making the Hunter's current enemy invalid for a short time.
			if( !GetEnemy() || !GetEnemy()->IsPlayer() )
			{
				UpdateEnemyMemory( pAttacker, pAttacker->GetAbsOrigin(), this );

				if( GetEnemy() )
				{
					// Gotta forget about this person for a little bit.
					GetEnemies()->SetTimeValidEnemy( GetEnemy(), gpGlobals->curtime + HUNTER_IGNORE_ENEMY_TIME );
				}
			}
		}

		float flDamage = myInfo.GetDamage() * flScale;
		myInfo.SetDamage( flDamage );
	}

	int nRet = BaseClass::OnTakeDamage_Alive( myInfo );

	m_EscortBehavior.OnDamage( myInfo );

	// Spark at 30% health.
	if ( !IsBleeding() && ( GetHealth() <= sk_hunter_health.GetInt() * 0.3 ) )
	{
		StartBleeding();
	}
	
	if ( IsUsingSiegeTargets() && info.GetAttacker() != NULL && info.GetAttacker()->IsPlayer() )
	{
		// Defend myself. Try to siege attack immediately.
		m_flTimeNextSiegeTargetAttack = gpGlobals->curtime;
	}
	
	return nRet;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::Event_Killed( const CTakeDamageInfo &info )
{
	// Remember the killing blow to make decisions about ragdolling.
	m_nKillingDamageType = info.GetDamageType();

	if ( m_EscortBehavior.GetFollowTarget() )
	{
		if ( AIGetNumFollowers( m_EscortBehavior.GetFollowTarget(), m_iClassname ) == 1 )
		{
			m_EscortBehavior.GetEscortTarget()->AlertSound();
			if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() )
			{
				m_EscortBehavior.GetEscortTarget()->UpdateEnemyMemory( UTIL_GetLocalPlayer(), UTIL_GetLocalPlayer()->GetAbsOrigin(), this );
			}
		}
	}
	
	if ( info.GetDamageType() & DMG_VEHICLE )
	{
		bool bWasRunDown = false;
		int iRundownCounter = 0;
		if ( GetSquad() )
		{
			if ( !m_IgnoreVehicleTimer.Expired() )
			{
				GetSquad()->GetSquadData( HUNTER_RUNDOWN_SQUADDATA, &iRundownCounter );
				GetSquad()->SetSquadData( HUNTER_RUNDOWN_SQUADDATA, iRundownCounter + 1 );
				bWasRunDown = true;
			}
		}

		if ( hunter_dodge_debug.GetBool() )
			Msg( "Hunter %d was%s run down\n", entindex(), ( bWasRunDown ) ? "" : " not" );

		// Death by vehicle! Decrement the hunters to run over counter.
		// When the counter reaches zero hunters will start dodging.
		if ( GlobalEntity_GetCounter( s_iszHuntersToRunOver ) > 0 )
		{
			GlobalEntity_AddToCounter( s_iszHuntersToRunOver, -1 );
		}
	}

	// Stop all our thinks
	SetContextThink( NULL, 0, HUNTER_BLEED_THINK );

	StopParticleEffects( this );

	BaseClass::Event_Killed( info );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::StartBleeding()
{
	// Do this even if we're already bleeding (see OnRestore).
	m_bIsBleeding = true;

	// Start gushing blood from our... anus or something.
	DispatchParticleEffect( "blood_drip_synth_01", PATTACH_POINT_FOLLOW, this, gm_nHeadBottomAttachment );

	// Emit spurts of our blood
	SetContextThink( &CNPC_Hunter::BleedThink, gpGlobals->curtime + 0.1, HUNTER_BLEED_THINK );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
float CNPC_Hunter::MaxYawSpeed()
{
	if ( IsStriderBuster( GetEnemy() ) )
	{
		return 60;
	}

	if ( GetActivity() == ACT_HUNTER_ANGRY )
		return 0;

	if ( GetActivity() == ACT_HUNTER_CHARGE_RUN )
		return 5;

	if ( GetActivity() == ACT_HUNTER_IDLE_PLANTED )
		return 0;

	if ( GetActivity() == ACT_HUNTER_RANGE_ATTACK2_UNPLANTED )
		return 180;

	switch ( GetActivity() )
	{
		case ACT_RANGE_ATTACK2:
		{
			return 0;
		}
		
		case ACT_90_LEFT:
		case ACT_90_RIGHT:
		{
			return 45;
		}
		
		case ACT_TURN_LEFT:
		case ACT_TURN_RIGHT:
		{
			return 45;
		}
		
		case ACT_WALK:
		{
			return 25;
		}
		
		default:
		{
			return 35;
		}
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Hunter::IsJumpLegal(const Vector &startPos, const Vector &apex, const Vector &endPos) const
{
	float MAX_JUMP_RISE		= 220.0f;
	float MAX_JUMP_DISTANCE	= 512.0f;
	float MAX_JUMP_DROP		= 384.0f;

	trace_t tr;	
	UTIL_TraceHull( startPos, startPos, GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
	if ( tr.startsolid )
	{
		// Trying to start a jump in solid! Consider checking for this in CAI_MoveProbe::JumpMoveLimit.
		Assert( 0 );
		return false;
	}

	if ( BaseClass::IsJumpLegal( startPos, apex, endPos, MAX_JUMP_RISE, MAX_JUMP_DROP, MAX_JUMP_DISTANCE ) )
	{
		return true;
	}
	return false;
}


//-----------------------------------------------------------------------------
// Let the probe know I can run through small debris
// Stolen shamelessly from the Antlion Guard
//-----------------------------------------------------------------------------
bool CNPC_Hunter::ShouldProbeCollideAgainstEntity( CBaseEntity *pEntity )
{
	if ( s_iszPhysPropClassname != pEntity->m_iClassname )
		return BaseClass::ShouldProbeCollideAgainstEntity( pEntity );

	if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
	{
		IPhysicsObject *pPhysObj = pEntity->VPhysicsGetObject();

		if( pPhysObj && pPhysObj->GetMass() <= 500.0f )
		{
			return false;
		}
	}

	return BaseClass::ShouldProbeCollideAgainstEntity( pEntity );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::DoMuzzleFlash( int nAttachment )
{
	BaseClass::DoMuzzleFlash();
	
	DispatchParticleEffect( "hunter_muzzle_flash", PATTACH_POINT_FOLLOW, this, nAttachment );

	// Dispatch the elight	
	CEffectData data;
	data.m_nAttachmentIndex = nAttachment;
	data.m_nEntIndex = entindex();
	DispatchEffect( "HunterMuzzleFlash", data );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CNPC_Hunter::CountRangedAttackers()
{
	CBaseEntity *pEnemy = GetEnemy();
	if ( !pEnemy )
	{
		return 0;
	}

	int nAttackers = 0;
	for ( int i = 0; i < g_Hunters.Count(); i++ )
	{
		CNPC_Hunter *pOtherHunter = g_Hunters[i];
		if ( pOtherHunter->GetEnemy() == pEnemy )
		{
			if ( pOtherHunter->IsCurSchedule( SCHED_HUNTER_RANGE_ATTACK2 ) )
			{
				nAttackers++;
			}
		}
	}
	return nAttackers;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::DelayRangedAttackers( float minDelay, float maxDelay, bool bForced )
{
	float delayMultiplier = ( g_pGameRules->IsSkillLevel( SKILL_EASY ) ) ? 1.25 : 1.0;
	if ( !m_bEnableSquadShootDelay && !bForced )
	{
		m_flNextRangeAttack2Time = gpGlobals->curtime + random->RandomFloat( minDelay, maxDelay ) * delayMultiplier;
		return;
	}

	CBaseEntity *pEnemy = GetEnemy();
	for ( int i = 0; i < g_Hunters.Count(); i++ )
	{
		CNPC_Hunter *pOtherHunter = g_Hunters[i];
		if ( pOtherHunter->GetEnemy() == pEnemy )
		{
			float nextTime = gpGlobals->curtime + random->RandomFloat( minDelay, maxDelay ) * delayMultiplier;
			if ( nextTime > pOtherHunter->m_flNextRangeAttack2Time )
				pOtherHunter->m_flNextRangeAttack2Time = nextTime;
		}
	}
}


//-----------------------------------------------------------------------------
// Given a target to shoot at, decide where to aim.
//-----------------------------------------------------------------------------
void CNPC_Hunter::GetShootDir( Vector &vecDir, const Vector &vecSrc, CBaseEntity *pTargetEntity, bool bStriderBuster, int nShotNum, bool bSingleShot )
{
	//RestartGesture( ACT_HUNTER_GESTURE_SHOOT );

	EmitSound( "NPC_Hunter.FlechetteShoot" );

	Vector vecBodyTarget;

	if( pTargetEntity->Classify() == CLASS_PLAYER_ALLY_VITAL )
	{
		// Shooting at Alyx, most likely (in EP2). The attack is designed to displace
		// her, not necessarily actually harm her. So shoot at the area around her feet.
		vecBodyTarget = pTargetEntity->GetAbsOrigin();
	}
	else
	{
		vecBodyTarget = pTargetEntity->BodyTarget( vecSrc );
	}

	Vector vecTarget = vecBodyTarget;

	Vector vecDelta = pTargetEntity->GetAbsOrigin() - GetAbsOrigin();
	float flDist = vecDelta.Length();

	if ( !bStriderBuster )
	{
		// If we're not firing at a strider buster, miss in an entertaining way for the 
		// first three shots of each volley.
		if ( ( nShotNum < 3 ) && ( flDist > 200 ) )
		{
			Vector vecTargetForward;
			Vector vecTargetRight;
			pTargetEntity->GetVectors( &vecTargetForward, &vecTargetRight, NULL );

			Vector vecForward;
			GetVectors( &vecForward, NULL, NULL );

			float flDot = DotProduct( vecTargetForward, vecForward );

			if ( flDot < -0.8f )
			{
				// Our target is facing us, shoot the ground between us.
				float flPerc = 0.7 + ( 0.1 * nShotNum );
				vecTarget = GetAbsOrigin() + ( flPerc * ( pTargetEntity->GetAbsOrigin() - GetAbsOrigin() ) );
			}
			else if ( flDot > 0.8f )
			{
				// Our target is facing away from us, shoot to the left or right.
				Vector vecMissDir = vecTargetRight;
				if ( m_bMissLeft )
				{
					vecMissDir *= -1.0f;
				}

				vecTarget = pTargetEntity->EyePosition() +  ( 36.0f * ( 3 - nShotNum ) ) * vecMissDir;
			}
			else
			{
				// Our target is facing vaguely perpendicular to us, shoot across their view.
				vecTarget = pTargetEntity->EyePosition() +  ( 36.0f * ( 3 - nShotNum ) ) * vecTargetForward;
			}
		}	
		// If we can't see them, shoot where we last saw them.
		else if ( !HasCondition( COND_SEE_ENEMY ) )
		{
			Vector vecDelta = vecTarget - pTargetEntity->GetAbsOrigin();
			vecTarget = m_vecEnemyLastSeen + vecDelta;
		}
	}
	else
	{
		// If we're firing at a striderbuster, lead it.
		float flSpeed = hunter_flechette_speed.GetFloat();
		if ( !flSpeed )
		{
			flSpeed = 2500.0f;
		}

		flSpeed *= 1.5;

		float flDeltaTime = flDist / flSpeed;
		vecTarget = vecTarget + flDeltaTime * pTargetEntity->GetSmoothedVelocity();
	}

	vecDir = vecTarget - vecSrc;
	VectorNormalize( vecDir );
}


//-----------------------------------------------------------------------------
// Ensures that we don't exceed our pitch/yaw limits when shooting flechettes.
// Returns true if we had to clamp, false if not.
//-----------------------------------------------------------------------------
bool CNPC_Hunter::ClampShootDir( Vector &vecDir )
{
	Vector vecDir2D = vecDir;
	vecDir2D.z = 0;
	
	Vector vecForward;
	GetVectors( &vecForward, NULL, NULL );

	Vector vecForward2D = vecForward;
	vecForward2D.z = 0;
	
	float flDot = DotProduct( vecForward2D, vecDir2D );
	if ( flDot >= HUNTER_SHOOT_MAX_YAW_COS )
	{
		// No need to clamp.
		return false;		
	}
	
	Vector vecAxis;
	CrossProduct( vecDir, vecForward, vecAxis );
	VectorNormalize( vecAxis );

	Quaternion q;
	AxisAngleQuaternion( vecAxis, -HUNTER_SHOOT_MAX_YAW_DEG, q );

	matrix3x4_t rot;
	QuaternionMatrix( q, rot );
	VectorRotate( vecForward, rot, vecDir );
	VectorNormalize( vecDir );
	
	return true;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Hunter::ShouldSeekTarget( CBaseEntity *pTargetEntity, bool bStriderBuster )
{
	bool bSeek = false;

	if ( bStriderBuster )
	{
		bool bSeek = false;

		if ( pTargetEntity->VPhysicsGetObject() && ( pTargetEntity->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) )
		{
			bSeek = true;
		}
		else if ( StriderBuster_NumFlechettesAttached( pTargetEntity ) == 0 )
		{
			if ( StriderBuster_IsAttachedStriderBuster(pTargetEntity) )
			{
				bSeek = true;
			}
			else if ( hunter_seek_thrown_striderbusters_tolerance.GetFloat() > 0.0 )
			{
				CNPC_Strider *pEscortTarget = m_EscortBehavior.GetEscortTarget();
				if ( pEscortTarget && ( pEscortTarget->GetAbsOrigin() - pTargetEntity->GetAbsOrigin() ).LengthSqr() < Square( hunter_seek_thrown_striderbusters_tolerance.GetFloat() ) )
				{
					bSeek = true;
				}
			}
		}
	}

	return bSeek;
}	


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::BeginVolley( int nNum, float flStartTime )
{
	m_nFlechettesQueued = nNum;
	m_nClampedShots = 0;
	m_flNextFlechetteTime = flStartTime;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Hunter::ShootFlechette( CBaseEntity *pTargetEntity, bool bSingleShot )
{
	if ( !pTargetEntity )
	{
		Assert( false );
		return false;
	}

	int nShotNum = hunter_flechette_volley_size.GetInt() - m_nFlechettesQueued;

	bool bStriderBuster = IsStriderBuster( pTargetEntity );

	// Choose the next muzzle to shoot from.
	Vector vecSrc;
	QAngle angMuzzle;

	if ( m_bTopMuzzle )
	{
		GetAttachment( gm_nTopGunAttachment, vecSrc, angMuzzle );
		DoMuzzleFlash( gm_nTopGunAttachment );
	}
	else
	{
		GetAttachment( gm_nBottomGunAttachment, vecSrc, angMuzzle );
		DoMuzzleFlash( gm_nBottomGunAttachment );
	}

	m_bTopMuzzle = !m_bTopMuzzle;

	Vector vecDir;
	GetShootDir( vecDir, vecSrc, pTargetEntity, bStriderBuster, nShotNum, bSingleShot );

	bool bClamped = false;
	if ( hunter_clamp_shots.GetBool() )
	{
		bClamped = ClampShootDir( vecDir );
	}

	CShotManipulator manipulator( vecDir );
	Vector vecShoot;

	if( IsUsingSiegeTargets() && nShotNum >= 2 && (nShotNum % 2) == 0 )
	{
		// Near perfect accuracy for these three shots, so they are likely to fly right into the windows.
		// NOTE! In siege behavior in the map that this behavior was designed for (ep2_outland_10), the
		// Hunters will only ever shoot at siege targets in siege mode. If you allow Hunters in siege mode
		// to attack players or other NPCs, this accuracy bonus will apply unless we apply a bit more logic to it.
		vecShoot = manipulator.ApplySpread( VECTOR_CONE_1DEGREES * 0.5, 1.0f );
	}
	else
	{
		vecShoot = manipulator.ApplySpread( VECTOR_CONE_4DEGREES, 1.0f );
	}

	QAngle angShoot;
	VectorAngles( vecShoot, angShoot );

	CHunterFlechette *pFlechette = CHunterFlechette::FlechetteCreate( vecSrc, angShoot, this );

	pFlechette->AddEffects( EF_NOSHADOW );

	vecShoot *= hunter_flechette_speed.GetFloat();

	pFlechette->Shoot( vecShoot, bStriderBuster );

	if ( ShouldSeekTarget( pTargetEntity, bStriderBuster ) )
	{
		pFlechette->SetSeekTarget( pTargetEntity );
	}

	if( nShotNum == 1 && pTargetEntity->Classify() == CLASS_PLAYER_ALLY_VITAL )
	{
		// Make this person afraid and react to ME, not to the flechettes. 
		// Otherwise they could be scared into running towards the hunter.
		CSoundEnt::InsertSound( SOUND_DANGER|SOUND_CONTEXT_REACT_TO_SOURCE|SOUND_CONTEXT_EXCLUDE_COMBINE, pTargetEntity->EyePosition(), 180.0f, 2.0f, this );
	}

	return bClamped;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
Vector CNPC_Hunter::LeftFootHit( float eventtime )
{
	Vector footPosition;

	GetAttachment( "left foot", footPosition );
	CPASAttenuationFilter filter( this );
	EmitSound( filter, entindex(), "NPC_Hunter.Footstep", &footPosition, eventtime );

	FootFX( footPosition );

	return footPosition;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
Vector CNPC_Hunter::RightFootHit( float eventtime )
{
	Vector footPosition;

	GetAttachment( "right foot", footPosition );
	CPASAttenuationFilter filter( this );
	EmitSound( filter, entindex(), "NPC_Hunter.Footstep", &footPosition, eventtime );
	FootFX( footPosition );

	return footPosition;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
Vector CNPC_Hunter::BackFootHit( float eventtime )
{
	Vector footPosition;

	GetAttachment( "back foot", footPosition );
	CPASAttenuationFilter filter( this );
	EmitSound( filter, entindex(), "NPC_Hunter.BackFootstep", &footPosition, eventtime );
	FootFX( footPosition );

	return footPosition;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::FootFX( const Vector &origin )
{
	return;

	// dvs TODO: foot dust? probably too expensive for these guys
	/*trace_t tr;
	AI_TraceLine( origin, origin - Vector(0,0,100), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
	float yaw = random->RandomInt(0,120);
	for ( int i = 0; i < 3; i++ )
	{
		Vector dir = UTIL_YawToVector( yaw + i*120 ) * 10;
		VectorNormalize( dir );
		dir.z = 0.25;
		VectorNormalize( dir );
		g_pEffects->Dust( tr.endpos, dir, 12, 50 );
	}*/
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CBaseEntity *CNPC_Hunter::GetEnemyVehicle()
{
	if ( GetEnemy() == NULL )
		return false;

	CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer();
	if ( pCCEnemy != NULL )
		return pCCEnemy->GetVehicleEntity();

	return false;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::DrawDebugGeometryOverlays()
{
	if (m_debugOverlays & OVERLAY_BBOX_BIT) 
	{	
		float flViewRange	= acos(0.8);
		Vector vEyeDir = EyeDirection2D( );
		Vector vLeftDir, vRightDir;
		float fSin, fCos;
		SinCos( flViewRange, &fSin, &fCos );

		vLeftDir.x			= vEyeDir.x * fCos - vEyeDir.y * fSin;
		vLeftDir.y			= vEyeDir.x * fSin + vEyeDir.y * fCos;
		vLeftDir.z			=  vEyeDir.z;
		fSin				= sin(-flViewRange);
		fCos				= cos(-flViewRange);
		vRightDir.x			= vEyeDir.x * fCos - vEyeDir.y * fSin;
		vRightDir.y			= vEyeDir.x * fSin + vEyeDir.y * fCos;
		vRightDir.z			=  vEyeDir.z;

		int nSeq = GetSequence();
		if ( ( GetEntryNode( nSeq ) == gm_nPlantedNode ) && ( GetExitNode( nSeq ) == gm_nPlantedNode ) )
		{
			// planted - green
			NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 0, 255, 0, 128, 0 );
		}
		else if ( ( GetEntryNode( nSeq ) == gm_nUnplantedNode ) && ( GetExitNode( nSeq ) == gm_nUnplantedNode ) )
		{
			// unplanted - blue
			NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 0, 0, 255, 128, 0 );
		}
		else if ( ( GetEntryNode( nSeq ) == gm_nUnplantedNode ) && ( GetExitNode( nSeq ) == gm_nPlantedNode ) )
		{
			// planting transition - cyan
			NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 0, 255, 255, 128, 0 );
		}
		else if ( ( GetEntryNode( nSeq ) == gm_nPlantedNode ) && ( GetExitNode( nSeq ) == gm_nUnplantedNode ) ) 
		{
			// unplanting transition - purple
			NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 255, 0, 255, 128, 0 );
		}
		else
		{
			// unknown / other node - red
			Msg( "UNKNOWN: %s\n", GetSequenceName( GetSequence() ) );
			NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 255, 0, 0, 128, 0 );
		}

		NDebugOverlay::BoxDirection(EyePosition(), Vector(0,0,-1), Vector(200,0,1), vLeftDir, 255, 0, 0, 50, 0 );
		NDebugOverlay::BoxDirection(EyePosition(), Vector(0,0,-1), Vector(200,0,1), vRightDir, 255, 0, 0, 50, 0 );
		NDebugOverlay::BoxDirection(EyePosition(), Vector(0,0,-1), Vector(200,0,1), vEyeDir, 0, 255, 0, 50, 0 );
		NDebugOverlay::Box(EyePosition(), -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, 128, 0 );
	}	
	
	m_EscortBehavior.DrawDebugGeometryOverlays();
	
	BaseClass::DrawDebugGeometryOverlays();
}


//-----------------------------------------------------------------------------
// Player has illuminated this NPC with the flashlight
//-----------------------------------------------------------------------------
void CNPC_Hunter::PlayerHasIlluminatedNPC( CBasePlayer *pPlayer, float flDot )
{
	if ( m_bFlashlightInEyes )
		return;

	// Ignore the flashlight if it's not shining at my eyes
	if ( PlayerFlashlightOnMyEyes( pPlayer ) )
	{
		//Msg( ">>>> SHINING FLASHLIGHT ON ME\n" );
		m_bFlashlightInEyes = true;
		SetExpression( "scenes/npc/hunter/hunter_eyeclose.vcd" );
		m_flPupilDilateTime = gpGlobals->curtime + 0.2f;
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Hunter::PlayerFlashlightOnMyEyes( CBasePlayer *pPlayer )
{
	Vector vecEyes, vecEyeForward, vecPlayerForward;
 	GetAttachment( gm_nTopGunAttachment, vecEyes, &vecEyeForward );
 	pPlayer->EyeVectors( &vecPlayerForward );

	Vector vecToEyes = (vecEyes - pPlayer->EyePosition());
	//float flDist = VectorNormalize( vecToEyes ); 

	float flDot = DotProduct( vecPlayerForward, vecToEyes );
	if ( flDot < 0.98 )
		return false;

	// Check facing to ensure we're in front of her
 	Vector los = ( pPlayer->EyePosition() - EyePosition() );
	los.z = 0;
	VectorNormalize( los );
	Vector facingDir = EyeDirection2D();
 	flDot = DotProduct( los, facingDir );
	return ( flDot > 0.3 );
}


//-----------------------------------------------------------------------------
// Return a random expression for the specified state to play over 
// the state's expression loop.
//-----------------------------------------------------------------------------
const char *CNPC_Hunter::SelectRandomExpressionForState( NPC_STATE state )
{
	if ( m_bFlashlightInEyes )
		return NULL;
		
	if ( !hunter_random_expressions.GetBool() )
		return NULL;

	char *szExpressions[4] =
	{
		"scenes/npc/hunter/hunter_scan.vcd",
		"scenes/npc/hunter/hunter_eyeclose.vcd",
		"scenes/npc/hunter/hunter_roar.vcd",
		"scenes/npc/hunter/hunter_pain.vcd"
	};
	
	int nIndex = random->RandomInt( 0, 3 );
	//Msg( "RANDOM Expression: %s\n", szExpressions[nIndex] );
	return szExpressions[nIndex];
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::PlayExpressionForState( NPC_STATE state )
{
	if ( m_bFlashlightInEyes )
	{
		return;
	}

	BaseClass::PlayExpressionForState( state );
}


//-----------------------------------------------------------------------------
// TODO: remove if we're not doing striderbuster stuff
//-----------------------------------------------------------------------------
void CNPC_Hunter::StriderBusterAttached( CBaseEntity *pAttached )
{
	// Add another to the list
	m_hAttachedBusters.AddToTail( pAttached );

	SetCondition( COND_HUNTER_HIT_BY_STICKYBOMB );
	if (m_hAttachedBusters.Count() == 1)
	{
		EmitSound( "NPC_Hunter.Alert" );
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::StriderBusterDetached( CBaseEntity *pAttached )
{
	int elem = m_hAttachedBusters.Find(pAttached);
	if (elem >= 0)
	{
		m_hAttachedBusters.FastRemove(elem);
	}
}


//-----------------------------------------------------------------------------
// Set direction that the hunter aims his body and eyes (guns).
//-----------------------------------------------------------------------------
void CNPC_Hunter::SetAim( const Vector &aimDir, float flInterval )
{
	QAngle angDir;
	VectorAngles( aimDir, angDir );
	float curPitch = GetPoseParameter( gm_nBodyPitchPoseParam );
	float curYaw = GetPoseParameter( gm_nBodyYawPoseParam );

	float newPitch;
	float newYaw;

	if ( GetEnemy() )
	{
		// clamp and dampen movement
		newPitch = curPitch + 0.8 * UTIL_AngleDiff( UTIL_ApproachAngle( angDir.x, curPitch, 20 ), curPitch );

		float flRelativeYaw = UTIL_AngleDiff( angDir.y, GetAbsAngles().y );
		newYaw = curYaw + UTIL_AngleDiff( flRelativeYaw, curYaw );
	}
	else
	{
		// Sweep your weapon more slowly if you're not fighting someone
		newPitch = curPitch + 0.6 * UTIL_AngleDiff( UTIL_ApproachAngle( angDir.x, curPitch, 20 ), curPitch );

		float flRelativeYaw = UTIL_AngleDiff( angDir.y, GetAbsAngles().y );
		newYaw = curYaw + 0.6 * UTIL_AngleDiff( flRelativeYaw, curYaw );
	}

	newPitch = AngleNormalize( newPitch );
	newYaw = AngleNormalize( newYaw );

	//Msg( "pitch=%f, yaw=%f\n", newPitch, newYaw );

	SetPoseParameter( gm_nAimPitchPoseParam, 0 );
	SetPoseParameter( gm_nAimYawPoseParam, 0 );

	SetPoseParameter( gm_nBodyPitchPoseParam, clamp( newPitch, -45, 45 ) );
	SetPoseParameter( gm_nBodyYawPoseParam, clamp( newYaw, -45, 45 ) );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::RelaxAim( float flInterval )
{
	float curPitch = GetPoseParameter( gm_nBodyPitchPoseParam );
	float curYaw = GetPoseParameter( gm_nBodyYawPoseParam );

	// dampen existing aim
	float newPitch = AngleNormalize( UTIL_ApproachAngle( 0, curPitch, 3 ) );
	float newYaw = AngleNormalize( UTIL_ApproachAngle( 0, curYaw, 2 ) );

	SetPoseParameter( gm_nAimPitchPoseParam, 0 );
	SetPoseParameter( gm_nAimYawPoseParam, 0 );

	SetPoseParameter( gm_nBodyPitchPoseParam, clamp( newPitch, -45, 45 ) );
	SetPoseParameter( gm_nBodyYawPoseParam, clamp( newYaw, -45, 45 ) );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Hunter::UpdateAim()
{
	if ( !GetModelPtr() || !GetModelPtr()->SequencesAvailable() )
		return;
		
	float flInterval = GetAnimTimeInterval();

	// Some activities look bad if we're giving our enemy the stinkeye.
	int eActivity = GetActivity();

	if ( GetEnemy() &&
		 GetState() != NPC_STATE_SCRIPT && 
		 ( eActivity != ACT_HUNTER_CHARGE_CRASH ) &&
		 ( eActivity != ACT_HUNTER_CHARGE_HIT ) )
	{
		Vector vecShootOrigin;

		vecShootOrigin = Weapon_ShootPosition();
		Vector vecShootDir = GetShootEnemyDir( vecShootOrigin, false );

		SetAim( vecShootDir, flInterval );
	}
	else
	{
		RelaxAim( flInterval );
	}
}


//-----------------------------------------------------------------------------
// Don't become a ragdoll until we've finished our death anim
//-----------------------------------------------------------------------------
bool CNPC_Hunter::CanBecomeRagdoll()
{
	return ( m_nKillingDamageType & DMG_CRUSH ) ||
		IsCurSchedule( SCHED_DIE, false ) ||								// Finished playing death anim, time to ragdoll
		IsCurSchedule( SCHED_HUNTER_CHARGE_ENEMY, false ) ||				// While moving, it looks better to ragdoll instantly
		IsCurSchedule( SCHED_SCRIPTED_RUN, false ) ||
		( GetActivity() == ACT_WALK ) || ( GetActivity() == ACT_RUN ) ||
		GetCurSchedule() == NULL;											// Failsafe
}


//-----------------------------------------------------------------------------
// Determines the best type of death anim to play based on how we died.
//-----------------------------------------------------------------------------
Activity CNPC_Hunter::GetDeathActivity()
{
	return ACT_DIESIMPLE;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_HunterEscortBehavior::OnDamage( const CTakeDamageInfo &info )
{
	if ( info.GetDamage() > 0 && info.GetAttacker()->IsPlayer() &&
		GetFollowTarget() && ( AIGetNumFollowers( GetFollowTarget() ) > 1 ) &&
		( GetOuter()->GetSquad()->GetSquadSoundWaitTime() <= gpGlobals->curtime ) ) // && !FarFromFollowTarget()
	{
		// Start the clock ticking. We'll return the the strider when the timer elapses.
		m_flTimeEscortReturn = gpGlobals->curtime + random->RandomFloat( 15.0f, 25.0f );
		GetOuter()->GetSquad()->SetSquadSoundWaitTime( m_flTimeEscortReturn + 1.0 ); // prevent others from breaking escort
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_HunterEscortBehavior::BuildScheduleTestBits()
{
	BaseClass::BuildScheduleTestBits();

	if ( ( m_flTimeEscortReturn != 0 ) && ( gpGlobals->curtime > m_flTimeEscortReturn ) )
	{
		// We're delinquent! Return to strider!
		GetOuter()->ClearCustomInterruptCondition( COND_NEW_ENEMY );
		GetOuter()->ClearCustomInterruptCondition( COND_SEE_ENEMY );
		GetOuter()->ClearCustomInterruptCondition( COND_SEE_HATE );
		GetOuter()->ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK1 );
		GetOuter()->ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK2 );
	}
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_HunterEscortBehavior::CheckBreakEscort()
{
	if ( m_flTimeEscortReturn != 0 && ( FarFromFollowTarget() || gpGlobals->curtime >= m_flTimeEscortReturn ) )
	{
		if ( FarFromFollowTarget() )
		{
			m_flTimeEscortReturn = gpGlobals->curtime;
		}
		else
		{
			m_flTimeEscortReturn = 0;
		}
		if ( GetOuter()->GetSquad() )
		{
			GetOuter()->GetSquad()->SetSquadSoundWaitTime( gpGlobals->curtime + random->RandomFloat( 5.0f, 12.0f ) );
		}
	}
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_HunterEscortBehavior::GatherConditionsNotActive( void )
{
	if ( m_bEnabled )
	{
		DistributeFreeHunters();
	}

	BaseClass::GatherConditionsNotActive();
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_HunterEscortBehavior::GatherConditions( void )
{
	m_bEnabled = true;

	DistributeFreeHunters();

	BaseClass::GatherConditions();

	if ( GetEnemy() && GetEnemy()->IsPlayer() && HasCondition( COND_SEE_ENEMY ) )
	{
		if ( GetOuter()->GetSquad()->GetSquadSoundWaitTime() <= gpGlobals->curtime && ((CBasePlayer *)GetEnemy())->IsInAVehicle() )
		{
			m_flTimeEscortReturn = gpGlobals->curtime + random->RandomFloat( 15.0f, 25.0f );
			GetOuter()->GetSquad()->SetSquadSoundWaitTime( m_flTimeEscortReturn + 1.0 ); // prevent others from breaking escort
		}
	}
}

	
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CAI_HunterEscortBehavior::ShouldFollow()
{
	if ( IsStriderBuster( GetEnemy() ) )
		return false;

	if ( HasCondition( COND_HEAR_PHYSICS_DANGER ) )
		return false;

	if ( m_flTimeEscortReturn <= gpGlobals->curtime )
	{
		return CAI_FollowBehavior::ShouldFollow();
	}

	return false;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_HunterEscortBehavior::BeginScheduleSelection()
{
	BaseClass::BeginScheduleSelection();
	Assert( m_SavedDistTooFar == GetOuter()->m_flDistTooFar );
	GetOuter()->m_flDistTooFar *= 2;
}	


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CAI_HunterEscortBehavior::SelectSchedule()
{
	if( m_FollowDelay.IsRunning() && !m_FollowDelay.Expired() )
	{
		return FollowCallBaseSelectSchedule();
	}
	return BaseClass::SelectSchedule();
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CAI_HunterEscortBehavior::FollowCallBaseSelectSchedule() 
{
	if ( GetOuter()->GetState() == NPC_STATE_COMBAT )
	{
		return GetOuter()->SelectCombatSchedule();
	}

	return BaseClass::FollowCallBaseSelectSchedule(); 
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_HunterEscortBehavior::StartTask( const Task_t *pTask )
{
	switch( pTask->iTask )
	{
	case TASK_MOVE_TO_FOLLOW_POSITION:
		{
			if ( GetEnemy() )
			{
				if ( GetOuter()->OccupyStrategySlot( SQUAD_SLOT_RUN_SHOOT ) )
				{
					if ( GetOuter()->GetSquad()->GetSquadMemberNearestTo( GetEnemy()->GetAbsOrigin() ) == GetOuter() )
					{
						GetOuter()->BeginVolley( NUM_FLECHETTE_VOLLEY_ON_FOLLOW, gpGlobals->curtime + 1.0 + random->RandomFloat( 0, .25 ) + random->RandomFloat( 0, .25 ) );
					}
					else
					{
						GetOuter()->VacateStrategySlot();
					}
				}
			}
			BaseClass::StartTask( pTask );
			break;
		}

	default:
		BaseClass::StartTask( pTask );
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_HunterEscortBehavior::RunTask( const Task_t *pTask )
{
	switch( pTask->iTask )
	{
	case TASK_MOVE_TO_FOLLOW_POSITION:
		{
			if ( !GetFollowTarget() )
			{
				TaskFail( FAIL_NO_TARGET );
			}
			else
			{
				if ( GetEnemy() )
				{
					CNPC_Hunter *pHunter = GetOuter();
					Vector vecEnemyLKP = pHunter->GetEnemyLKP();
					pHunter->AddFacingTarget( pHunter->GetEnemy(), vecEnemyLKP, 1.0, 0.8 );
					bool bVacate = false;

					bool bHasSlot = pHunter->HasStrategySlot( SQUAD_SLOT_RUN_SHOOT );
					if ( HasCondition( COND_SEE_ENEMY ) )
					{
						float maxDist = hunter_flechette_max_range.GetFloat() * 3;
						float distSq = ( pHunter->GetAbsOrigin() - pHunter->GetEnemy()->GetAbsOrigin() ).Length2DSqr();

						if ( distSq < Square( maxDist ) )
						{
							if ( gpGlobals->curtime >= pHunter->m_flNextFlechetteTime )
							{
								if ( !bHasSlot )
								{
									if ( GetOuter()->OccupyStrategySlot( SQUAD_SLOT_RUN_SHOOT ) )
									{
										if ( GetOuter()->GetSquad()->GetSquadMemberNearestTo( GetEnemy()->GetAbsOrigin() ) == GetOuter() )
										{
											bHasSlot = true;
										}
										else
										{
											GetOuter()->VacateStrategySlot();
										}
									}
								}

								if ( bHasSlot )
								{
									// Start the firing sound.
									//CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
									//if ( controller.SoundGetVolume( pHunter->m_pGunFiringSound ) == 0.0f )
									//{
									//	controller.SoundChangeVolume( pHunter->m_pGunFiringSound, 1.0f, 0.0f );
									//}
									
									pHunter->ShootFlechette( GetEnemy(), true );

									if ( --pHunter->m_nFlechettesQueued > 0 )
									{
										pHunter->m_flNextFlechetteTime = gpGlobals->curtime + hunter_flechette_delay.GetFloat();
									}
									else
									{
										// Stop the firing sound.
										//CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
										//controller.SoundChangeVolume( pHunter->m_pGunFiringSound, 0, 0.01f );

										bVacate = true;
										pHunter->BeginVolley( NUM_FLECHETTE_VOLLEY_ON_FOLLOW, gpGlobals->curtime + 1.0 + random->RandomFloat( 0, .25 ) + random->RandomFloat( 0, .25 ) );
									}
								}
							}
						}
						else if ( bHasSlot )
						{
							bVacate = true;
						}
					}
					else if ( bHasSlot )
					{
						bVacate = true;
					}

					if ( bVacate )
					{
						pHunter->VacateStrategySlot();
					}
				}

				if ( m_FollowAttackTimer.Expired() && IsFollowTargetInRange( .8 )) 
				{
					m_FollowAttackTimer.Set( 8, 24 );
					TaskComplete();
				}
				else
				{
					BaseClass::RunTask( pTask );
				}
			}
			break;
		}

	default:
		BaseClass::RunTask( pTask );
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_HunterEscortBehavior::FindFreeHunters( CUtlVector<CNPC_Hunter *> *pFreeHunters )
{
	pFreeHunters->EnsureCapacity( g_Hunters.Count() );
	int i;

	for ( i = 0; i < g_Hunters.Count(); i++ )
	{
		CNPC_Hunter *pHunter = g_Hunters[i];
		if ( pHunter->IsAlive() && pHunter->m_EscortBehavior.m_bEnabled )
		{
			if ( pHunter->m_EscortBehavior.GetFollowTarget() == NULL || !pHunter->m_EscortBehavior.GetFollowTarget()->IsAlive() )
			{
				pFreeHunters->AddToTail( pHunter);
			}
		}
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_HunterEscortBehavior::DistributeFreeHunters()
{
	if ( g_TimeLastDistributeFreeHunters != -1 && gpGlobals->curtime - g_TimeLastDistributeFreeHunters < FREE_HUNTER_DISTRIBUTE_INTERVAL )
	{
		return;
	}

	g_TimeLastDistributeFreeHunters = gpGlobals->curtime;

	CUtlVector<CNPC_Hunter *> freeHunters;
	int i;
	FindFreeHunters( &freeHunters );

	CAI_BaseNPC **ppNPCs = g_AI_Manager.AccessAIs();
	for ( i = 0; i < g_AI_Manager.NumAIs() && freeHunters.Count(); i++ )
	{
		int nToAdd;
		CNPC_Strider *pStrider = ( ppNPCs[i]->IsAlive() ) ? dynamic_cast<CNPC_Strider *>( ppNPCs[i] ) : NULL;
		if ( pStrider && !pStrider->CarriedByDropship() )
		{
			if ( ( nToAdd = 3 - AIGetNumFollowers( pStrider ) ) > 0 )
			{
				for ( int j = freeHunters.Count() - 1; j >= 0 && nToAdd > 0; --j )
				{
					DevMsg( "npc_hunter %d assigned to npc_strider %d\n", freeHunters[j]->entindex(), pStrider->entindex() );
					freeHunters[j]->FollowStrider( pStrider );
					freeHunters.FastRemove( j );
					nToAdd--;
				}
			}
		}
	}

	for ( i = 0; i < freeHunters.Count(); i++ )
	{
		//DevMsg( "npc_hunter %d assigned to free_hunters_squad\n", freeHunters[i]->entindex() );
		freeHunters[i]->m_EscortBehavior.SetFollowTarget( NULL );
		freeHunters[i]->AddToSquad( AllocPooledString( "free_hunters_squad" ) );
	}

#if 0
	CBaseEntity *pHunterMaker = gEntList.FindEntityByClassname( NULL, "npc_hunter_maker" ); // TODO: this picks the same one every time!
	if ( pHunterMaker )
	{
		for ( i = 0; i < freeHunters.Count(); i++ )
		{
			freeHunters[i]->m_EscortBehavior.SetFollowTarget( pHunterMaker );
		}
	}
#endif
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_HunterEscortBehavior::DrawDebugGeometryOverlays()
{
	if ( !GetFollowTarget() )
		return;

	Vector vecFollowPos = GetGoalPosition();
	if ( FarFromFollowTarget() )
	{
		if ( gpGlobals->curtime >= m_flTimeEscortReturn )
		{
			NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecFollowPos, 16.0f, 255, 0, 0, 0, true, 0 );
		}
		else
		{
			NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecFollowPos, 16.0f, 255, 255, 0, 0, true, 0 );
		}
	}
	else
	{
		NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecFollowPos, 16.0f, 0, 255, 0, 0, true, 0 );
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool Hunter_IsHunter(CBaseEntity *pEnt)
{
	return dynamic_cast<CNPC_Hunter *>(pEnt) != NULL;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void Hunter_StriderBusterLaunched( CBaseEntity *pBuster )
{
	CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs();
	int nAIs = g_AI_Manager.NumAIs();

	for ( int i = 0; i < nAIs; i++ )
	{
		CAI_BaseNPC *pNPC = ppAIs[ i ];
		if ( pNPC && ( pNPC->Classify() == CLASS_COMBINE_HUNTER ) && pNPC->m_lifeState == LIFE_ALIVE )
		{
			if ( !pNPC->GetEnemy() || !IsStriderBuster( pNPC->GetEnemy() ) )
			{
				Vector vecDelta = pNPC->GetAbsOrigin() - pBuster->GetAbsOrigin();
				if ( vecDelta.Length2DSqr() < 9437184.0f ) // 3072 * 3072
				{
					pNPC->SetEnemy( pBuster );
					pNPC->SetState( NPC_STATE_COMBAT );
					pNPC->UpdateEnemyMemory( pBuster, pBuster->GetAbsOrigin() );

					// Stop whatever we're doing.
					pNPC->SetCondition( COND_SCHEDULE_DONE );
				}
			}
		}
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void Hunter_StriderBusterAttached( CBaseEntity *pHunter, CBaseEntity *pAttached )
{
	Assert(dynamic_cast<CNPC_Hunter *>(pHunter));

	static_cast<CNPC_Hunter *>(pHunter)->StriderBusterAttached(pAttached);
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void Hunter_StriderBusterDetached( CBaseEntity *pHunter, CBaseEntity *pAttached )
{
	Assert(dynamic_cast<CNPC_Hunter *>(pHunter));

	static_cast<CNPC_Hunter *>(pHunter)->StriderBusterDetached(pAttached);
}

//-------------------------------------------------------------------------------------------------
//
// ep2_outland_12 custom npc makers
//
//-------------------------------------------------------------------------------------------------

class CHunterMaker : public CTemplateNPCMaker
{
	typedef CTemplateNPCMaker BaseClass;
public:
	void MakeMultipleNPCS( int nNPCs )
	{
		const float MIN_HEALTH_PCT = 0.2;

		CUtlVector<CNPC_Hunter *> candidates;
		CUtlVectorFixed<CNPC_Hunter *, 3> freeHunters;
		CAI_HunterEscortBehavior::FindFreeHunters( &candidates );

		freeHunters.EnsureCapacity( 3 );
		int i;

		for ( i = 0; i < candidates.Count() && freeHunters.Count() < 3; i++ )
		{
			if ( candidates[i]->GetHealth() > candidates[i]->GetMaxHealth() * MIN_HEALTH_PCT )
			{
				freeHunters.AddToTail( candidates[i] );
			}
		}

		int nRequested = nNPCs;
		if ( nNPCs < 3 )
		{
			nNPCs = MIN( 3, nNPCs + freeHunters.Count() );
		}

		int nSummoned = 0;
		for ( i = 0; i < freeHunters.Count() && nNPCs; i++ )
		{
			freeHunters[i]->m_EscortBehavior.SetFollowTarget( this ); // this will make them not "free"
			freeHunters[i]->SetName( m_iszTemplateName ); // this will force the hunter to get the FollowStrider input
			nNPCs--;
			nSummoned++;
		}

		DevMsg( "Requested %d to spawn, Summoning %d free hunters, spawning %d new hunters\n", nRequested, nSummoned, nNPCs );
		if ( nNPCs )
		{
			BaseClass::MakeMultipleNPCS( nNPCs );
		}
	}
};

LINK_ENTITY_TO_CLASS( npc_hunter_maker, CHunterMaker );


//-------------------------------------------------------------------------------------------------
//
// Schedules
//
//-------------------------------------------------------------------------------------------------
AI_BEGIN_CUSTOM_NPC( npc_hunter, CNPC_Hunter )

	DECLARE_TASK( TASK_HUNTER_AIM )
	DECLARE_TASK( TASK_HUNTER_FIND_DODGE_POSITION )
	DECLARE_TASK( TASK_HUNTER_DODGE )
	DECLARE_TASK( TASK_HUNTER_PRE_RANGE_ATTACK2 )
	DECLARE_TASK( TASK_HUNTER_SHOOT_COMMIT )
	DECLARE_TASK( TASK_HUNTER_ANNOUNCE_FLANK )
	DECLARE_TASK( TASK_HUNTER_BEGIN_FLANK )
	DECLARE_TASK( TASK_HUNTER_STAGGER )
	DECLARE_TASK( TASK_HUNTER_CORNERED_TIMER )
	DECLARE_TASK( TASK_HUNTER_FIND_SIDESTEP_POSITION )
	DECLARE_TASK( TASK_HUNTER_CHARGE )
	DECLARE_TASK( TASK_HUNTER_FINISH_RANGE_ATTACK )
	DECLARE_TASK( TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY )
	DECLARE_TASK( TASK_HUNTER_CHARGE_DELAY )

	DECLARE_ACTIVITY( ACT_HUNTER_DEPLOYRA2 )
	DECLARE_ACTIVITY( ACT_HUNTER_DODGER )
	DECLARE_ACTIVITY( ACT_HUNTER_DODGEL )
	DECLARE_ACTIVITY( ACT_HUNTER_GESTURE_SHOOT )
	DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_STICKYBOMB )
	DECLARE_ACTIVITY( ACT_HUNTER_STAGGER )
	DECLARE_ACTIVITY( ACT_DI_HUNTER_MELEE )
	DECLARE_ACTIVITY( ACT_DI_HUNTER_THROW )
	DECLARE_ACTIVITY( ACT_HUNTER_MELEE_ATTACK1_VS_PLAYER )
	DECLARE_ACTIVITY( ACT_HUNTER_ANGRY )
	DECLARE_ACTIVITY( ACT_HUNTER_WALK_ANGRY )
	DECLARE_ACTIVITY( ACT_HUNTER_FOUND_ENEMY )
	DECLARE_ACTIVITY( ACT_HUNTER_FOUND_ENEMY_ACK )
	DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_START )
	DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_RUN )
	DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_STOP )
	DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_CRASH )
	DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_HIT )
	DECLARE_ACTIVITY( ACT_HUNTER_RANGE_ATTACK2_UNPLANTED )
	DECLARE_ACTIVITY( ACT_HUNTER_IDLE_PLANTED )
	DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_N )
	DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_S )
	DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_E )
	DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_W )

	DECLARE_INTERACTION( g_interactionHunterFoundEnemy );

	DECLARE_SQUADSLOT( SQUAD_SLOT_HUNTER_CHARGE )
	DECLARE_SQUADSLOT( SQUAD_SLOT_HUNTER_FLANK_FIRST )
	DECLARE_SQUADSLOT( SQUAD_SLOT_RUN_SHOOT )

	DECLARE_CONDITION( COND_HUNTER_SHOULD_PATROL )
	DECLARE_CONDITION( COND_HUNTER_FORCED_FLANK_ENEMY )
	DECLARE_CONDITION( COND_HUNTER_CAN_CHARGE_ENEMY )
	DECLARE_CONDITION( COND_HUNTER_STAGGERED )
	DECLARE_CONDITION( COND_HUNTER_IS_INDOORS )
	DECLARE_CONDITION( COND_HUNTER_HIT_BY_STICKYBOMB )
	DECLARE_CONDITION( COND_HUNTER_SEE_STRIDERBUSTER )
	DECLARE_CONDITION( COND_HUNTER_FORCED_DODGE )
	DECLARE_CONDITION( COND_HUNTER_INCOMING_VEHICLE )
	DECLARE_CONDITION( COND_HUNTER_NEW_HINTGROUP )
	DECLARE_CONDITION( COND_HUNTER_CANT_PLANT )
	DECLARE_CONDITION( COND_HUNTER_SQUADMATE_FOUND_ENEMY )
	
	DECLARE_ANIMEVENT( AE_HUNTER_FOOTSTEP_LEFT )
	DECLARE_ANIMEVENT( AE_HUNTER_FOOTSTEP_RIGHT )
	DECLARE_ANIMEVENT( AE_HUNTER_FOOTSTEP_BACK )
	DECLARE_ANIMEVENT( AE_HUNTER_MELEE_ANNOUNCE )
	DECLARE_ANIMEVENT( AE_HUNTER_MELEE_ATTACK_LEFT )
	DECLARE_ANIMEVENT( AE_HUNTER_MELEE_ATTACK_RIGHT )
	DECLARE_ANIMEVENT( AE_HUNTER_DIE )
	DECLARE_ANIMEVENT( AE_HUNTER_SPRAY_BLOOD )
	DECLARE_ANIMEVENT( AE_HUNTER_START_EXPRESSION )
	DECLARE_ANIMEVENT( AE_HUNTER_END_EXPRESSION )

	//=========================================================
	// Attack (Deploy/shoot/finish)
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HUNTER_RANGE_ATTACK1,

		"	Tasks"
		"		TASK_STOP_MOVING					0"
		"		TASK_HUNTER_SHOOT_COMMIT			0"
		"		TASK_RANGE_ATTACK1					0"
		"	"
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_ENEMY_DEAD"
		"		COND_LOST_ENEMY"
		"		COND_ENEMY_OCCLUDED"
		"		COND_WEAPON_SIGHT_OCCLUDED"
		"		COND_TOO_CLOSE_TO_ATTACK"
		"		COND_TOO_FAR_TO_ATTACK"
		"		COND_NOT_FACING_ATTACK"
	)

	//=========================================================
	// Attack (Deploy/shoot/finish)
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HUNTER_RANGE_ATTACK2,

		"	Tasks"
		"		TASK_STOP_MOVING				0"
		"		TASK_HUNTER_PRE_RANGE_ATTACK2	0"
		"		TASK_HUNTER_SHOOT_COMMIT		0"
		"		TASK_RANGE_ATTACK2				0"
		"		TASK_HUNTER_FINISH_RANGE_ATTACK	0"
		"		TASK_SET_ACTIVITY				ACTIVITY:ACT_IDLE"
		"		TASK_WAIT        				0.4"
		"		TASK_WAIT_RANDOM				0.2"
		"	"
		"	Interrupts"
		"		COND_NEW_ENEMY"
	)

	//=========================================================
	// Shoot at striderbuster. Distinct from generic range attack
	// because of BuildScheduleTestBits.
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER,

		"	Tasks"
		"		TASK_STOP_MOVING				0"
		"		TASK_HUNTER_SHOOT_COMMIT		0"
		"		TASK_RANGE_ATTACK2				0"
		"	"
		"	Interrupts"
	)

	//=========================================================
	// Shoot at striderbuster with a little latency beforehand
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER_LATENT,

		"	Tasks"
		"		TASK_STOP_MOVING				0"
		"		TASK_HUNTER_SHOOT_COMMIT		0"
		"		TASK_WAIT						0.2"
		"		TASK_PLAY_SEQUENCE_FACE_ENEMY	ACTIVITY:ACT_RANGE_ATTACK2"
		"		TASK_RANGE_ATTACK2				0"
		"	"
		"	Interrupts"
	)

	//=========================================================
	// Dodge Incoming vehicle
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HUNTER_DODGE,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_HUNTER_FAIL_DODGE"
		"		TASK_HUNTER_FIND_DODGE_POSITION			0"
		"		TASK_HUNTER_DODGE						0"
		""
		"	Interrupts"
	)

	//=========================================================
	// Dodge Incoming vehicle
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HUNTER_FAIL_DODGE,

		"	Tasks"
		"		TASK_STOP_MOVING		0"
		"		TASK_SET_ACTIVITY		ACTIVITY:ACT_IDLE"
		"		TASK_FACE_ENEMY			0"
		""
		"	Interrupts"
	)

	//==================================================
	// > SCHED_HUNTER_CHARGE_ENEMY
	// Rush at my enemy and head-butt them.
	//==================================================
	DEFINE_SCHEDULE
	(
		SCHED_HUNTER_CHARGE_ENEMY,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_HUNTER_FAIL_CHARGE_ENEMY"
		"		TASK_STOP_MOVING				0"
		"		TASK_FACE_ENEMY					0"
		"		TASK_HUNTER_CHARGE				0"
		""
		"	Interrupts"
		"		COND_TASK_FAILED"
		"		COND_ENEMY_DEAD"
	)

	DEFINE_SCHEDULE
	(
		SCHED_HUNTER_FAIL_CHARGE_ENEMY,

		"	Tasks"
		"		TASK_HUNTER_CHARGE_DELAY		10"
	)

	//=========================================================
	// Chase the enemy with intent to claw
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HUNTER_CHASE_ENEMY_MELEE,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_ESTABLISH_LINE_OF_FIRE"
		"		TASK_STOP_MOVING				0"
		"		TASK_GET_CHASE_PATH_TO_ENEMY	300"
		"		TASK_RUN_PATH					0"
		"		TASK_WAIT_FOR_MOVEMENT			0"
		"		TASK_FACE_ENEMY					0"
		""
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_ENEMY_DEAD"
		"		COND_ENEMY_UNREACHABLE"
		"		COND_CAN_MELEE_ATTACK1"
		"		COND_CAN_MELEE_ATTACK2"
		//"		COND_TOO_CLOSE_TO_ATTACK"
		"		COND_LOST_ENEMY"
	)

	//=========================================================
	// Chase my enemy, shoot or claw when possible to do so.
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HUNTER_CHASE_ENEMY,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_ESTABLISH_LINE_OF_FIRE"
		"		TASK_STOP_MOVING				0"
		"		TASK_GET_CHASE_PATH_TO_ENEMY	300"
		"		TASK_RUN_PATH					0"
		"		TASK_WAIT_FOR_MOVEMENT			0"
		"		TASK_FACE_ENEMY					0"
		""
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_ENEMY_DEAD"
		"		COND_ENEMY_UNREACHABLE"
		"		COND_CAN_RANGE_ATTACK1"
		"		COND_CAN_RANGE_ATTACK2"
		"		COND_CAN_MELEE_ATTACK1"
		"		COND_CAN_MELEE_ATTACK2"
		"		COND_TOO_CLOSE_TO_ATTACK"
		"		COND_LOST_ENEMY"
	)

	//=========================================================
	// Move to a flanking position, then shoot if possible.
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HUNTER_FLANK_ENEMY,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE					SCHEDULE:SCHED_ESTABLISH_LINE_OF_FIRE"
		"		TASK_STOP_MOVING						0"
		"		TASK_HUNTER_BEGIN_FLANK					0"
		"		TASK_GET_FLANK_ARC_PATH_TO_ENEMY_LOS	30"
		"		TASK_HUNTER_ANNOUNCE_FLANK				0"
		"		TASK_RUN_PATH							0"
		"		TASK_WAIT_FOR_MOVEMENT					0"
		"		TASK_FACE_ENEMY							0"
		//"		TASK_HUNTER_END_FLANK					0"
		""
		"	Interrupts"
		"		COND_NEW_ENEMY"
		//"		COND_CAN_RANGE_ATTACK1"
		//"		COND_CAN_RANGE_ATTACK2"
		"		COND_CAN_MELEE_ATTACK1"
		"		COND_CAN_MELEE_ATTACK2"
		"		COND_ENEMY_DEAD"
		"		COND_ENEMY_UNREACHABLE"
		"		COND_TOO_CLOSE_TO_ATTACK"
		"		COND_LOST_ENEMY"
	)

	//=========================================================
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HUNTER_COMBAT_FACE,

		"	Tasks"
		"		TASK_STOP_MOVING		0"
		"		TASK_SET_ACTIVITY		ACTIVITY:ACT_IDLE"
		"		TASK_WAIT_FACE_ENEMY	1"
		""
		"	Interrupts"
		"		COND_CAN_RANGE_ATTACK1"
		"		COND_CAN_RANGE_ATTACK2"
		"		COND_CAN_MELEE_ATTACK1"
		"		COND_CAN_MELEE_ATTACK2"
		"		COND_NEW_ENEMY"
		"		COND_ENEMY_DEAD"
	)
	
	//=========================================================
	// Like the base class, only don't stop in the middle of 
	// swinging if the enemy is killed, hides, or new enemy.
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HUNTER_MELEE_ATTACK1,

		"	Tasks"
		"		TASK_STOP_MOVING		0"
		"		TASK_FACE_ENEMY			0"
		"		TASK_MELEE_ATTACK1		0"
		//"		TASK_SET_SCHEDULE		SCHEDULE:SCHED_HUNTER_POST_MELEE_WAIT"
		""
		"	Interrupts"
	)

	//=========================================================
	// In a fight with nothing to do. Make busy!
	//=========================================================
	DEFINE_SCHEDULE	
	(
		SCHED_HUNTER_CHANGE_POSITION,

		"	Tasks"
		"		TASK_STOP_MOVING				0"
		"		TASK_WANDER						720432" // 6 feet to 36 feet
		"		TASK_RUN_PATH					0"
		"		TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY	0"
		"		TASK_STOP_MOVING				0"
		"		TASK_SET_SCHEDULE				SCHEDULE:SCHED_HUNTER_CHANGE_POSITION_FINISH"
		""
		"	Interrupts"
		"		COND_ENEMY_DEAD"
		"		COND_CAN_MELEE_ATTACK1"
		"		COND_CAN_MELEE_ATTACK2"
		"		COND_HEAR_DANGER"
		"		COND_HEAR_MOVE_AWAY"
		"		COND_NEW_ENEMY"
	)

	//=========================================================
	// In a fight with nothing to do. Make busy!
	//=========================================================
	DEFINE_SCHEDULE	
	(
		SCHED_HUNTER_CHANGE_POSITION_FINISH,

		"	Tasks"
		"		TASK_FACE_ENEMY					0"
		"		TASK_WAIT_FACE_ENEMY_RANDOM		5"
		""
		"	Interrupts"
		"		COND_ENEMY_DEAD"
		"		COND_CAN_RANGE_ATTACK1"
		"		COND_CAN_RANGE_ATTACK2"
		"		COND_CAN_MELEE_ATTACK1"
		"		COND_CAN_MELEE_ATTACK2"
		"		COND_HEAR_DANGER"
		"		COND_HEAR_MOVE_AWAY"
		"		COND_NEW_ENEMY"
	)

	//=========================================================
	// In a fight with nothing to do. Make busy!
	//=========================================================
	DEFINE_SCHEDULE	
	(
		SCHED_HUNTER_SIDESTEP,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_HUNTER_FAIL_IMMEDIATE" // used because sched_fail includes a one second pause. ick!
		"		TASK_STOP_MOVING						0"
		"		TASK_HUNTER_FIND_SIDESTEP_POSITION		0"
		"		TASK_GET_PATH_TO_SAVEPOSITION			0"
		"		TASK_RUN_PATH							0"
		"		TASK_WAIT_FOR_MOVEMENT					0"
		"		TASK_FACE_ENEMY							0"
		""
		"	Interrupts"
	)

	//=========================================================
	//=========================================================
	DEFINE_SCHEDULE	
	(
		SCHED_HUNTER_PATROL,

		"	Tasks"
		"		TASK_STOP_MOVING				0"
		"		TASK_WANDER						720432" // 6 feet to 36 feet
		"		TASK_WALK_PATH					0"
		"		TASK_WAIT_FOR_MOVEMENT			0"
		"		TASK_STOP_MOVING				0"
		"		TASK_FACE_REASONABLE			0"
		"		TASK_WAIT_RANDOM				3"
		""
		"	Interrupts"
		"		COND_ENEMY_DEAD"
		"		COND_LIGHT_DAMAGE"
		"		COND_HEAVY_DAMAGE"
		"		COND_HEAR_DANGER"
		"		COND_HEAR_COMBAT"
		"		COND_HEAR_PLAYER"
		"		COND_HEAR_BULLET_IMPACT"
		"		COND_HEAR_MOVE_AWAY"
		"		COND_NEW_ENEMY"
		"		COND_SEE_ENEMY"
		"		COND_CAN_RANGE_ATTACK1"
		"		COND_CAN_RANGE_ATTACK2"
		"		COND_CAN_MELEE_ATTACK1"
		"		COND_CAN_MELEE_ATTACK2"
	)

	//=========================================================
	// Stagger because I got hit by something heavy
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HUNTER_STAGGER,

		"	Tasks"
		"		TASK_HUNTER_STAGGER			0"
		""
		"	Interrupts"
	)

	//=========================================================
	// Run around randomly until we detect an enemy
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HUNTER_PATROL_RUN,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_COMBAT_FACE"
		"		TASK_SET_ROUTE_SEARCH_TIME		5"	// Spend 5 seconds trying to build a path if stuck
		"		TASK_GET_PATH_TO_RANDOM_NODE	200"
		"		TASK_RUN_PATH					0"
		"		TASK_WAIT_FOR_MOVEMENT			0"
		""
		"	Interrupts"
		"		COND_CAN_RANGE_ATTACK1 "
		"		COND_CAN_RANGE_ATTACK2 "
		"		COND_CAN_MELEE_ATTACK1 "
		"		COND_CAN_MELEE_ATTACK2"
		"		COND_GIVE_WAY"
		"		COND_NEW_ENEMY"
		"		COND_HEAR_COMBAT"
		"		COND_HEAR_DANGER"
		"		COND_HEAR_PLAYER"
		"		COND_LIGHT_DAMAGE"
		"		COND_HEAVY_DAMAGE"
	)

	//=========================================================
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HUNTER_TAKE_COVER_FROM_ENEMY,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_HUNTER_CHASE_ENEMY_MELEE"
		"		TASK_HUNTER_CORNERED_TIMER		10.0"
		"		TASK_WAIT						0.0"
	//	"		TASK_SET_TOLERANCE_DISTANCE		24"
	//	"		TASK_FIND_COVER_FROM_ENEMY		0"
		"		TASK_FIND_FAR_NODE_COVER_FROM_ENEMY 200.0"
		"		TASK_RUN_PATH					0"
		"		TASK_HUNTER_CORNERED_TIMER		0.0"
	//	"		TASK_CLEAR_FAIL_SCHEDULE		0" // not used because sched_fail includes a one second pause. ick!
		"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_HUNTER_FAIL_IMMEDIATE"
		"		TASK_WAIT_FOR_MOVEMENT			0"
		"		TASK_REMEMBER					MEMORY:INCOVER"
		"		TASK_SET_SCHEDULE				SCHEDULE:SCHED_HUNTER_HIDE_UNDER_COVER"
		/*
		"		TASK_FACE_ENEMY					0"
		"		TASK_SET_ACTIVITY				ACTIVITY:ACT_IDLE"	// Translated to cover
		"		TASK_WAIT						1"
		*/
		""
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_HEAR_DANGER"
	)

	//=========================================================
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HUNTER_HIDE_UNDER_COVER,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_HUNTER_FAIL_IMMEDIATE" // used because sched_fail includes a one second pause. ick!
		"		TASK_FACE_ENEMY					0"
		"		TASK_SET_ACTIVITY				ACTIVITY:ACT_IDLE"	// Translated to cover
		"		TASK_WAIT						1"
		""
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_HEAR_DANGER"
		"		COND_HAVE_ENEMY_LOS"
	)
	
	//=========================================================
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HUNTER_FOUND_ENEMY,

		"	Tasks"
		"		TASK_STOP_MOVING				0"
		"		TASK_FACE_ENEMY					0"
		"		TASK_PLAY_SEQUENCE_FACE_ENEMY	ACTIVITY:ACT_HUNTER_FOUND_ENEMY"
		""
		"	Interrupts"
		"		COND_LIGHT_DAMAGE"
		"		COND_HEAVY_DAMAGE"
	)
	
	//=========================================================
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HUNTER_FOUND_ENEMY_ACK,

		"	Tasks"
		"		TASK_STOP_MOVING				0"
		"		TASK_WAIT_RANDOM				0.75"
		"		TASK_FACE_ENEMY					0"
		"		TASK_PLAY_SEQUENCE_FACE_ENEMY	ACTIVITY:ACT_HUNTER_FOUND_ENEMY_ACK"
		""
		"	Interrupts"
		"		COND_LIGHT_DAMAGE"
		"		COND_HEAVY_DAMAGE"
	)
	
	//=========================================================
	// An empty schedule that immediately bails out, faster than
	// SCHED_FAIL which stops moving and waits for one second.
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HUNTER_FAIL_IMMEDIATE,

		"	Tasks"
		"		TASK_WAIT			0"

	)

	DEFINE_SCHEDULE
	( 
		SCHED_HUNTER_GOTO_HINT,
		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_HUNTER_CLEAR_HINTNODE" // used because sched_fail includes a one second pause. ick!
		"		TASK_GET_PATH_TO_HINTNODE		1"
		"		TASK_WAIT_FOR_MOVEMENT			0"
		"		TASK_CLEAR_HINTNODE				0"
		""
		""
		"	Interrupts"
	)

	DEFINE_SCHEDULE
	( 
		SCHED_HUNTER_CLEAR_HINTNODE,
		"	Tasks"
		"		TASK_CLEAR_HINTNODE				0"
		""
		""
		"	Interrupts"
	)

	DEFINE_SCHEDULE
	(
		SCHED_HUNTER_SIEGE_STAND,
		"	Tasks"
		"		TASK_STOP_MOVING				0"
		"		TASK_SET_ACTIVITY				ACTIVITY:ACT_IDLE"
		"		TASK_FACE_PLAYER				0"
		"		TASK_WAIT						10"
		"		TASK_WAIT_RANDOM				2"
		"		TASK_SET_SCHEDULE				SCHEDULE:SCHED_HUNTER_CHANGE_POSITION_SIEGE"
		""
		""
		"	Interrupts"
		"		COND_SEE_PLAYER"
		"		COND_NEW_ENEMY"
	)

	DEFINE_SCHEDULE	
	(
		SCHED_HUNTER_CHANGE_POSITION_SIEGE,

		"	Tasks"
		"		TASK_STOP_MOVING				0"
		"		TASK_WANDER						2400480"
		"		TASK_RUN_PATH					0"
		"		TASK_WAIT_FOR_MOVEMENT			0"
		"		TASK_STOP_MOVING				0"
		"		TASK_SET_ACTIVITY				ACTIVITY:ACT_IDLE"
		"		TASK_FACE_PLAYER				0"
		""
		"	Interrupts"
		"		COND_NEW_ENEMY"
	)

	// formula is MIN_DIST * 10000 + MAX_DIST

AI_END_CUSTOM_NPC()
