text: //========= Copyright Valve Corporation, All rights reserved. ============//
  //
  // Purpose: 
  //
  //=============================================================================//
  
  #include "cbase.h"
  #include "soundent.h"
  #include "npcevent.h"
  #include "globalstate.h"
  #include "ai_squad.h"
  #include "ai_tacticalservices.h"
  #include "npc_manhack.h"
  #include "npc_metropolice.h"
  #include "weapon_stunstick.h"
  #include "basegrenade_shared.h"
  #include "ai_route.h"
  #include "hl2_player.h"
  #include "iservervehicle.h"
  #include "items.h"
  #include "hl2_gamerules.h"
  
  // memdbgon must be the last include file in a .cpp file!!!
  #include "tier0/memdbgon.h"
  
  //#define SF_METROPOLICE_					0x00010000
  #define SF_METROPOLICE_SIMPLE_VERSION		0x00020000
  #define SF_METROPOLICE_ALWAYS_STITCH		0x00080000
  #define SF_METROPOLICE_NOCHATTER			0x00100000
  #define SF_METROPOLICE_ARREST_ENEMY			0x00200000
  #define SF_METROPOLICE_NO_FAR_STITCH		0x00400000
  #define SF_METROPOLICE_NO_MANHACK_DEPLOY	0x00800000
  #define SF_METROPOLICE_ALLOWED_TO_RESPOND	0x01000000
  #define SF_METROPOLICE_MID_RANGE_ATTACK		0x02000000
  
  #define METROPOLICE_MID_RANGE_ATTACK_RANGE	3500.0f
  
  #define METROPOLICE_SQUAD_STITCH_MIN_INTERVAL	1.0f
  #define METROPOLICE_SQUAD_STITCH_MAX_INTERVAL	1.2f
  
  #define AIM_ALONG_SIDE_LINE_OF_DEATH_DISTANCE		300.0f
  #define AIM_ALONG_SIDE_STEER_DISTANCE				200.0f
  #define AIM_ALONG_SIDE_DEFAULT_STITCH_LENGTH		750.0f
  #define AIM_ALONG_SIDE_LINE_OF_DEATH_LEAD_TIME		0.0f
  #define AIM_ALONG_SIDE_LINE_INITIAL_DRAW_FRACTION	0.2f
  
  #define AIM_BEHIND_DEFAULT_STITCH_LENGTH		1000.0f
  #define AIM_BEHIND_MINIMUM_DISTANCE				650.0f
  #define AIM_BEHIND_STEER_DISTANCE				150.0f
  
  #define RECENT_DAMAGE_INTERVAL		3.0f
  #define RECENT_DAMAGE_THRESHOLD		0.2f
  
  #define VEHICLE_PREDICT_ACCELERATION		333.0f
  #define VEHICLE_PREDICT_MAX_SPEED			600.0f
  
  #define	METROPOLICE_MAX_WARNINGS	3
  
  #define	METROPOLICE_BODYGROUP_MANHACK	1
  
  enum
  {
  	// NOTE: Exact #s are important, since they are referred to by number in schedules below
  
  	METROPOLICE_SENTENCE_FREEZE			= 0,
  	METROPOLICE_SENTENCE_HES_OVER_HERE	= 1,
  	METROPOLICE_SENTENCE_HES_RUNNING	= 2,
  	METROPOLICE_SENTENCE_TAKE_HIM_DOWN	= 3,	
  	METROPOLICE_SENTENCE_ARREST_IN_POSITION	= 4,
  	METROPOLICE_SENTENCE_DEPLOY_MANHACK	= 5,
  	METROPOLICE_SENTENCE_MOVE_INTO_POSITION	= 6,
  	METROPOLICE_SENTENCE_HEARD_SOMETHING	= 7,
  };
  
  enum
  {
  	METROPOLICE_ANNOUNCE_ATTACK_PRIMARY = 1,
  	METROPOLICE_ANNOUNCE_ATTACK_SECONDARY,
  	METROPOLICE_ANNOUNCE_ATTACK_HARASS,
  };
  
  enum
  {
  	METROPOLICE_CHATTER_WAIT_FOR_RESPONSE = 0,
  	METROPOLICE_CHATTER_ASK_QUESTION = 1,
  	METROPOLICE_CHATTER_RESPONSE = 2,
  
  	METROPOLICE_CHATTER_RESPONSE_TYPE_COUNT = 2,
  };
  
  
  enum SpeechMemory_t
  {
  	bits_MEMORY_PAIN_LIGHT_SOUND	= bits_MEMORY_CUSTOM1,
  	bits_MEMORY_PAIN_HEAVY_SOUND	= bits_MEMORY_CUSTOM2,
  	bits_MEMORY_PLAYER_HURT			= bits_MEMORY_CUSTOM3,
  	bits_MEMORY_PLAYER_HARASSED		= bits_MEMORY_CUSTOM4,
  };
  
  //Metrocop
  int	g_interactionMetrocopStartedStitch = 0;
  int g_interactionMetrocopIdleChatter = 0;
  int g_interactionMetrocopClearSentenceQueues = 0;
  
  extern int g_interactionHitByPlayerThrownPhysObj;
  
  ConVar	sk_metropolice_stitch_reaction( "sk_metropolice_stitch_reaction","1.0");
  ConVar	sk_metropolice_stitch_tight_hitcount( "sk_metropolice_stitch_tight_hitcount","2");
  ConVar	sk_metropolice_stitch_at_hitcount( "sk_metropolice_stitch_at_hitcount","1");
  ConVar	sk_metropolice_stitch_behind_hitcount( "sk_metropolice_stitch_behind_hitcount","3");
  ConVar	sk_metropolice_stitch_along_hitcount( "sk_metropolice_stitch_along_hitcount","2");
  
  
  ConVar	sk_metropolice_health( "sk_metropolice_health","0");
  ConVar	sk_metropolice_simple_health( "sk_metropolice_simple_health","26");
  ConVar	sk_metropolice_stitch_distance( "sk_metropolice_stitch_distance","1000");
  
  ConVar	metropolice_chase_use_follow( "metropolice_chase_use_follow", "0" );
  ConVar  metropolice_move_and_melee("metropolice_move_and_melee", "1" );
  ConVar  metropolice_charge("metropolice_charge", "1" );
  
  // How many clips of pistol ammo a metropolice carries.
  #define METROPOLICE_NUM_CLIPS			5
  #define METROPOLICE_BURST_RELOAD_COUNT	20
  
  int AE_METROPOLICE_BATON_ON;
  int	AE_METROPOLICE_BATON_OFF;
  int AE_METROPOLICE_SHOVE;
  int AE_METROPOLICE_START_DEPLOY;
  int AE_METROPOLICE_DRAW_PISTOL;		// was	50
  int AE_METROPOLICE_DEPLOY_MANHACK;	// was	51
  
  // -----------------------------------------------
  //	> Squad slots
  // -----------------------------------------------
  enum SquadSlot_T
  {
  	SQUAD_SLOT_POLICE_CHARGE_ENEMY = LAST_SHARED_SQUADSLOT,
  	SQUAD_SLOT_POLICE_HARASS, // Yell at the player with a megaphone, etc.
  	SQUAD_SLOT_POLICE_DEPLOY_MANHACK,
  	SQUAD_SLOT_POLICE_ADVANCE,
  	SQUAD_SLOT_POLICE_ATTACK_OCCLUDER1,
  	SQUAD_SLOT_POLICE_ATTACK_OCCLUDER2,
  	SQUAD_SLOT_POLICE_COVERING_FIRE1,
  	SQUAD_SLOT_POLICE_COVERING_FIRE2,
  	SQUAD_SLOT_POLICE_ARREST_ENEMY,
  };
  
  //=========================================================
  // Metro Police  Activities
  //=========================================================
  int ACT_METROPOLICE_DRAW_PISTOL;
  int ACT_METROPOLICE_DEPLOY_MANHACK;
  int ACT_METROPOLICE_FLINCH_BEHIND;
  
  int	ACT_WALK_BATON;
  int	ACT_IDLE_ANGRY_BATON;
  int	ACT_PUSH_PLAYER;
  int ACT_MELEE_ATTACK_THRUST;
  int ACT_ACTIVATE_BATON;
  int ACT_DEACTIVATE_BATON;
   
  LINK_ENTITY_TO_CLASS( npc_metropolice, CNPC_MetroPolice );
  
  BEGIN_DATADESC( CNPC_MetroPolice )
  
  	DEFINE_EMBEDDED( m_BatonSwingTimer ),	
  	DEFINE_EMBEDDED( m_NextChargeTimer ),
  	DEFINE_FIELD( m_flBatonDebounceTime, FIELD_FLOAT ),
  	DEFINE_FIELD( m_bShouldActivateBaton, FIELD_BOOLEAN ),
  	DEFINE_FIELD( m_iPistolClips, FIELD_INTEGER ),
  	DEFINE_KEYFIELD( m_fWeaponDrawn, FIELD_BOOLEAN, "weapondrawn" ),
  	DEFINE_FIELD( m_LastShootSlot, FIELD_INTEGER ),
  	DEFINE_EMBEDDED( m_TimeYieldShootSlot ),
  	DEFINE_EMBEDDED( m_Sentences ),
  	DEFINE_FIELD( m_bPlayerIsNear, FIELD_BOOLEAN ),
  
  	DEFINE_FIELD( m_vecBurstTargetPos, FIELD_POSITION_VECTOR ),
  	DEFINE_FIELD( m_vecBurstDelta, FIELD_VECTOR ),
  	DEFINE_FIELD( m_nBurstHits, FIELD_INTEGER ),
  	DEFINE_FIELD( m_nMaxBurstHits, FIELD_INTEGER ),
  	DEFINE_FIELD( m_flBurstPredictTime, FIELD_TIME ),
  	DEFINE_FIELD( m_nBurstReloadCount, FIELD_INTEGER ),
  	DEFINE_FIELD( m_vecBurstLineOfDeathDelta, FIELD_VECTOR ),
  	DEFINE_FIELD( m_vecBurstLineOfDeathOrigin, FIELD_POSITION_VECTOR ),
  	DEFINE_FIELD( m_flBurstSteerDistance, FIELD_FLOAT ),
  	DEFINE_FIELD( m_nBurstMode, FIELD_INTEGER ),
  	DEFINE_FIELD( m_nBurstSteerMode, FIELD_INTEGER ),
  	DEFINE_FIELD( m_vecBurstPredictedVelocityDir, FIELD_VECTOR ),
  	DEFINE_FIELD( m_vecBurstPredictedSpeed, FIELD_FLOAT ),
  	DEFINE_FIELD( m_flValidStitchTime, FIELD_TIME ),
  	DEFINE_FIELD( m_flNextLedgeCheckTime, FIELD_TIME ),
  	DEFINE_FIELD( m_flTaskCompletionTime, FIELD_TIME ),
  	DEFINE_FIELD( m_flLastPhysicsFlinchTime, FIELD_TIME ),
  	DEFINE_FIELD( m_flLastDamageFlinchTime, FIELD_TIME ),
  
  	DEFINE_FIELD( m_hManhack, FIELD_EHANDLE ),
  	DEFINE_FIELD( m_hBlockingProp, FIELD_EHANDLE ),
  
  	DEFINE_FIELD( m_nRecentDamage, FIELD_INTEGER ),
  	DEFINE_FIELD( m_flRecentDamageTime, FIELD_TIME ),
  
  	DEFINE_FIELD( m_flNextPainSoundTime, FIELD_TIME ),
  	DEFINE_FIELD( m_flNextLostSoundTime, FIELD_TIME ),
  	DEFINE_FIELD( m_nIdleChatterType, FIELD_INTEGER ),
  
  	DEFINE_FIELD( m_bSimpleCops, FIELD_BOOLEAN ),
  	DEFINE_FIELD( m_flLastHitYaw, FIELD_FLOAT ),
  
  	DEFINE_FIELD( m_bPlayerTooClose,	FIELD_BOOLEAN ),
  	DEFINE_FIELD( m_bKeepFacingPlayer,	FIELD_BOOLEAN ),
  	DEFINE_FIELD( m_flChasePlayerTime,	FIELD_TIME ),
  	DEFINE_FIELD( m_vecPreChaseOrigin,	FIELD_VECTOR ),
  	DEFINE_FIELD( m_flPreChaseYaw,		FIELD_FLOAT ),
  	DEFINE_FIELD( m_nNumWarnings,		FIELD_INTEGER ),
  	DEFINE_FIELD( m_iNumPlayerHits,		FIELD_INTEGER ),
  
  	//								m_ActBusyBehavior (auto saved by AI)
  	//								m_StandoffBehavior (auto saved by AI)
  	//								m_AssaultBehavior (auto saved by AI)
  	//								m_FuncTankBehavior (auto saved by AI)
  	//								m_RappelBehavior (auto saved by AI)
  	//								m_PolicingBehavior (auto saved by AI)
  	//								m_FollowBehavior (auto saved by AI)
  
  	DEFINE_KEYFIELD( m_iManhacks, FIELD_INTEGER, "manhacks" ),
  	DEFINE_INPUTFUNC( FIELD_VOID, "EnableManhackToss", InputEnableManhackToss ),
  	DEFINE_INPUTFUNC( FIELD_STRING, "SetPoliceGoal", InputSetPoliceGoal ),
  	DEFINE_INPUTFUNC( FIELD_VOID, "ActivateBaton", InputActivateBaton ),
  	
  	DEFINE_USEFUNC( PrecriminalUse ),
  
  	DEFINE_OUTPUT( m_OnStunnedPlayer,	"OnStunnedPlayer" ),
  	DEFINE_OUTPUT( m_OnCupCopped, "OnCupCopped" ),
  
  END_DATADESC()
  
  //------------------------------------------------------------------------------
  
  float CNPC_MetroPolice::gm_flTimeLastSpokePeek;
  
  //------------------------------------------------------------------------------
  // Purpose 
  //------------------------------------------------------------------------------
  CBaseEntity *CNPC_MetroPolice::CheckTraceHullAttack( float flDist, const Vector &mins, const Vector &maxs, int iDamage, int iDmgType, float forceScale, bool bDamageAnyNPC )
  {
  	// If only a length is given assume we want to trace in our facing direction
  	Vector forward;
  	AngleVectors( GetAbsAngles(), &forward );
  	Vector vStart = GetAbsOrigin();
  
  	// The ideal place to start the trace is in the center of the attacker's bounding box.
  	// however, we need to make sure there's enough clearance. Some of the smaller monsters aren't 
  	// as big as the hull we try to trace with. (SJB)
  	float flVerticalOffset = WorldAlignSize().z * 0.5;
  
  	if( flVerticalOffset < maxs.z )
  	{
  		// There isn't enough room to trace this hull, it's going to drag the ground.
  		// so make the vertical offset just enough to clear the ground.
  		flVerticalOffset = maxs.z + 1.0;
  	}
  
  	vStart.z += flVerticalOffset;
  	Vector vEnd = vStart + (forward * flDist );
  	return CheckTraceHullAttack( vStart, vEnd, mins, maxs, iDamage, iDmgType, forceScale, bDamageAnyNPC );
  }
  
  //------------------------------------------------------------------------------
  // Melee filter for police
  //------------------------------------------------------------------------------
  class CTraceFilterMetroPolice : public CTraceFilterEntitiesOnly
  {
  public:
  	// It does have a base, but we'll never network anything below here..
  	DECLARE_CLASS_NOBASE( CTraceFilterMetroPolice );
  	
  	CTraceFilterMetroPolice( const IHandleEntity *passentity, int collisionGroup, CTakeDamageInfo *dmgInfo, float flForceScale, bool bDamageAnyNPC )
  		: m_pPassEnt(passentity), m_collisionGroup(collisionGroup), m_dmgInfo(dmgInfo), m_pHit(NULL), m_flForceScale(flForceScale), m_bDamageAnyNPC(bDamageAnyNPC)
  	{
  	}
  	
  	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;
  
  			if ( pEntity->m_takedamage == DAMAGE_NO )
  				return false;
  
  			// Translate the vehicle into its driver for damage
  			if ( pEntity->GetServerVehicle() != NULL )
  			{
  				CBaseEntity *pDriver = pEntity->GetServerVehicle()->GetPassenger();
  
  				if ( pDriver != NULL )
  				{
  					pEntity = pDriver;
  				}
  			}
  	
  			Vector	attackDir = pEntity->WorldSpaceCenter() - m_dmgInfo->GetAttacker()->WorldSpaceCenter();
  			VectorNormalize( attackDir );
  
  			CTakeDamageInfo info = (*m_dmgInfo);				
  			CalculateMeleeDamageForce( &info, attackDir, info.GetAttacker()->WorldSpaceCenter(), m_flForceScale );
  
  			if( !(pEntity->GetFlags() & FL_ONGROUND) )
  			{
  				// Don't hit airborne entities so hard. They fly farther since
  				// there's no friction with the ground.
  				info.ScaleDamageForce( 0.001 );
  			}
  
  			CBaseCombatCharacter *pBCC = info.GetAttacker()->MyCombatCharacterPointer();
  			CBaseCombatCharacter *pVictimBCC = pEntity->MyCombatCharacterPointer();
  
  			// Only do these comparisons between NPCs
  			if ( pBCC && pVictimBCC )
  			{
  				// Can only damage other NPCs that we hate
  				if ( m_bDamageAnyNPC || pBCC->IRelationType( pEntity ) == D_HT || pEntity->IsPlayer() )
  				{
  					if ( info.GetDamage() )
  					{
  						// If gordon's a criminal, do damage now
  						if ( !pEntity->IsPlayer() || GlobalEntity_GetState( "gordon_precriminal" ) == GLOBAL_OFF )
  						{
  							if ( pEntity->IsPlayer() && ((CBasePlayer *)pEntity)->IsSuitEquipped() )
  							{
  								info.ScaleDamage( .25 );
  								info.ScaleDamageForce( .25 );
  							}
  
  							pEntity->TakeDamage( info );
  						}
  					}
  					
  					m_pHit = pEntity;
  					return true;
  				}
  			}
  			else
  			{
  				// Make sure if the player is holding this, he drops it
  				Pickup_ForcePlayerToDropThisObject( pEntity );
  
  				// Otherwise just damage passive objects in our way
  				if ( info.GetDamage() )
  				{
  					pEntity->TakeDamage( info );
  				}
  			}
  		}
  
  		return false;
  	}
  
  public:
  	const IHandleEntity *m_pPassEnt;
  	int					m_collisionGroup;
  	CTakeDamageInfo		*m_dmgInfo;
  	CBaseEntity			*m_pHit;
  	float				m_flForceScale;
  	bool				m_bDamageAnyNPC;
  };
  
  //------------------------------------------------------------------------------
  // Purpose :	start and end trace position, amount 
  //				of damage to do, and damage type. Returns a pointer to
  //				the damaged entity in case the NPC wishes to do
  //				other stuff to the victim (punchangle, etc)
  //
  //				Used for many contact-range melee attacks. Bites, claws, etc.
  // Input   :
  // Output  :
  //------------------------------------------------------------------------------
  CBaseEntity *CNPC_MetroPolice::CheckTraceHullAttack( const Vector &vStart, const Vector &vEnd, const Vector &mins, const Vector &maxs, int iDamage, int iDmgType, float flForceScale, bool bDamageAnyNPC )
  {
  
  	CTakeDamageInfo	dmgInfo( this, this, iDamage, DMG_SLASH );
  	
  	CTraceFilterMetroPolice traceFilter( this, COLLISION_GROUP_NONE, &dmgInfo, flForceScale, bDamageAnyNPC );
  
  	Ray_t ray;
  	ray.Init( vStart, vEnd, mins, maxs );
  
  	trace_t tr;
  	enginetrace->TraceRay( ray, MASK_SHOT, &traceFilter, &tr );
  
  	CBaseEntity *pEntity = traceFilter.m_pHit;
  	
  	if ( pEntity == NULL )
  	{
  		// See if perhaps I'm trying to claw/bash someone who is standing on my head.
  		Vector vecTopCenter;
  		Vector vecEnd;
  		Vector vecMins, vecMaxs;
  
  		// Do a tracehull from the top center of my bounding box.
  		vecTopCenter = GetAbsOrigin();
  		CollisionProp()->WorldSpaceAABB( &vecMins, &vecMaxs );
  		vecTopCenter.z = vecMaxs.z + 1.0f;
  		vecEnd = vecTopCenter;
  		vecEnd.z += 2.0f;
  		
  		ray.Init( vecTopCenter, vEnd, mins, maxs );
  		enginetrace->TraceRay( ray, MASK_SHOT_HULL, &traceFilter, &tr );
  
  		pEntity = traceFilter.m_pHit;
  	}
  
  	return pEntity;
  }
  
  //-----------------------------------------------------------------------------
  // My buddies got killed!
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::NotifyDeadFriend( CBaseEntity* pFriend )
  {
  	BaseClass::NotifyDeadFriend(pFriend);
  
  	if ( pFriend == m_hManhack )
  	{
  		m_Sentences.Speak( "METROPOLICE_MANHACK_KILLED", SENTENCE_PRIORITY_NORMAL, SENTENCE_CRITERIA_NORMAL );
  		DevMsg("My manhack died!\n");
  		m_hManhack = NULL;
  		return;
  	}
  
  	// No notifications for squadmates' dead manhacks
  	if ( FClassnameIs( pFriend, "npc_manhack" ) )
  		return;
  
  	// Reset idle chatter, we may never get a response back
  	if ( m_nIdleChatterType == METROPOLICE_CHATTER_WAIT_FOR_RESPONSE )
  	{
  		m_nIdleChatterType = METROPOLICE_CHATTER_ASK_QUESTION;
  	}
  
  	if ( GetSquad()->NumMembers() < 2 )
  	{
  		m_Sentences.Speak( "METROPOLICE_LAST_OF_SQUAD", SENTENCE_PRIORITY_MEDIUM, SENTENCE_CRITERIA_NORMAL );
  		return;
  	}
  
  	m_Sentences.Speak( "METROPOLICE_MAN_DOWN", SENTENCE_PRIORITY_MEDIUM );
  }
  
  
  //-----------------------------------------------------------------------------
  //-----------------------------------------------------------------------------
  CNPC_MetroPolice::CNPC_MetroPolice()
  {
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::OnScheduleChange()
  {
  	BaseClass::OnScheduleChange();
  
  	if ( GetEnemy() && HasCondition( COND_ENEMY_DEAD ) )
  	{
  		AnnounceEnemyKill( GetEnemy() );
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::PrescheduleThink( void )
  {
  	BaseClass::PrescheduleThink();
  
  	// Speak any queued sentences
  	m_Sentences.UpdateSentenceQueue();
  
  	// Look at near players, always
  	m_bPlayerIsNear = false;
  	if ( PlayerIsCriminal() == false )
  	{
  		CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
  		
  		if ( pPlayer && ( pPlayer->WorldSpaceCenter() - WorldSpaceCenter() ).LengthSqr() < (128*128) )
  		{
  			m_bPlayerIsNear = true;
  			AddLookTarget( pPlayer, 0.75f, 5.0f );
  			
  			if ( ( m_PolicingBehavior.IsEnabled() == false ) && ( m_nNumWarnings >= METROPOLICE_MAX_WARNINGS ) )
  			{
  				m_flBatonDebounceTime = gpGlobals->curtime + random->RandomFloat( 2.5f, 4.0f );
  				SetTarget( pPlayer );
  				SetBatonState( true );
  			}
  		}
  		else 
  		{
  			if ( m_PolicingBehavior.IsEnabled() == false && gpGlobals->curtime > m_flBatonDebounceTime )
  			{
  				SetBatonState( false );
  			}
  
  			m_bKeepFacingPlayer = false;
  		}
  	}
  
  	if( IsOnFire() )
  	{
  		SetCondition( COND_METROPOLICE_ON_FIRE );
  	}
  	else
  	{
  		ClearCondition( COND_METROPOLICE_ON_FIRE );
  	}
  
  	if (gpGlobals->curtime > m_flRecentDamageTime + RECENT_DAMAGE_INTERVAL)
  	{
  		m_nRecentDamage = 0;
  		m_flRecentDamageTime = 0;
  	}
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  // Input  : &move - 
  //			flInterval - 
  // Output : Returns true on success, false on failure.
  //-----------------------------------------------------------------------------
  bool CNPC_MetroPolice::OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval )
  {
  	// Don't do this if we're scripted
  	if ( IsInAScript() )
  		return BaseClass::OverrideMoveFacing( move, flInterval );
    	
  	// ROBIN: Disabled at request of mapmakers for now
  	/*
    	// If we're moving during a police sequence, always face our target
  	if ( m_PolicingBehavior.IsEnabled() )
    	{	
  		CBaseEntity *pTarget = m_PolicingBehavior.GetGoalTarget();
  
  		if ( pTarget )
  		{
  			AddFacingTarget( pTarget, pTarget->WorldSpaceCenter(), 1.0f, 0.2f );
  		}
  	}
  	*/
  
  	return BaseClass::OverrideMoveFacing( move, flInterval );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::Precache( void )
  {
  
  	PrecacheModel( STRING( GetModelName() ) );
  
  	UTIL_PrecacheOther( "npc_manhack" );
  
  	PrecacheScriptSound( "NPC_Metropolice.Shove" );
  	PrecacheScriptSound( "NPC_MetroPolice.WaterSpeech" );
  	PrecacheScriptSound( "NPC_MetroPolice.HidingSpeech" );
  	enginesound->PrecacheSentenceGroup( "METROPOLICE" );
  
  	BaseClass::Precache();
  }
  
  
  //-----------------------------------------------------------------------------
  // Create components
  //-----------------------------------------------------------------------------
  bool CNPC_MetroPolice::CreateComponents()
  {
  	if ( !BaseClass::CreateComponents() )
  		return false;
  
  	m_Sentences.Init( this, "NPC_Metropolice.SentenceParameters" );
  	return true;
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //
  //
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::Spawn( void )
  {
  	// jackathan 2019/10/04 - copied from npc_kleiner.cpp
  	// sets up "szModel" keyvalue string
  	char *szModel = (char *)STRING( GetModelName() );
  	if (!szModel || !*szModel)
  	{
  		szModel = "models/police.mdl";
  		SetModelName( AllocPooledString(szModel) );
  	}
  	
  	// jackathan 2019/10/04 - moved from Precache function
  	// checks for efficiency spawnflag, if yes use cheaple, if no use szModel
  	if ( HasSpawnFlags( SF_NPC_START_EFFICIENT ) )
  	{
  		szModel = "models/police_cheaple.mdl";
  		SetModelName( AllocPooledString(szModel) );
  	}
  	else
  	{
  		SetModelName( AllocPooledString(szModel) );
  	}
  	
  	Precache();
  
  #ifdef _XBOX
  	// Always fade the corpse
  	AddSpawnFlags( SF_NPC_FADE_CORPSE );
  #endif // _XBOX
  	
  	// jackathan 2019/10/04 - copied from npc_kleiner.cpp
  	// sets model to "szModel" keyvalue string instead of default
  	SetModel( szModel );
  	//SetModel( STRING( GetModelName() ) );
  
  	SetHullType(HULL_HUMAN);
  	SetHullSizeNormal();
  
  	SetSolid( SOLID_BBOX );
  	AddSolidFlags( FSOLID_NOT_STANDABLE );
  	SetMoveType( MOVETYPE_STEP );
  	SetBloodColor( BLOOD_COLOR_RED );
  	m_nIdleChatterType = METROPOLICE_CHATTER_ASK_QUESTION; 
  	m_bSimpleCops = HasSpawnFlags( SF_METROPOLICE_SIMPLE_VERSION );
  	if ( HasSpawnFlags( SF_METROPOLICE_NOCHATTER ) )
  	{
  		AddSpawnFlags( SF_NPC_GAG );
  	}
  
  	if (!m_bSimpleCops)
  	{
  		m_iHealth = sk_metropolice_health.GetFloat();
  	}
  	else
  	{
  		m_iHealth = sk_metropolice_simple_health.GetFloat();
  	}
  
  	m_flFieldOfView		= -0.2;// indicates the width of this NPC's forward view cone ( as a dotproduct result )
  	m_NPCState			= NPC_STATE_NONE;
  	if ( !HasSpawnFlags( SF_NPC_START_EFFICIENT ) )
  	{
  		CapabilitiesAdd( bits_CAP_TURN_HEAD | bits_CAP_ANIMATEDFACE );
  		CapabilitiesAdd( bits_CAP_AIM_GUN | bits_CAP_MOVE_SHOOT );
  	}
  	CapabilitiesAdd( bits_CAP_MOVE_GROUND );
  	CapabilitiesAdd( bits_CAP_USE_WEAPONS | bits_CAP_NO_HIT_SQUADMATES );
  	CapabilitiesAdd( bits_CAP_SQUAD );
  	CapabilitiesAdd( bits_CAP_DUCK | bits_CAP_DOORS_GROUP );
  	CapabilitiesAdd( bits_CAP_USE_SHOT_REGULATOR );
  
  	m_nBurstHits = 0;
  	m_HackedGunPos = Vector ( 0, 0, 55 );
  
  	m_iPistolClips = METROPOLICE_NUM_CLIPS;
  
  	NPCInit();
  
  	// NOTE: This must occur *after* init, since init sets default dist look
  	if ( HasSpawnFlags( SF_METROPOLICE_MID_RANGE_ATTACK ) )
  	{
  		m_flDistTooFar = METROPOLICE_MID_RANGE_ATTACK_RANGE;
  		SetDistLook( METROPOLICE_MID_RANGE_ATTACK_RANGE );
  	}
  
  	m_hManhack = NULL;
  
  	if ( GetActiveWeapon() )
  	{
  		CBaseCombatWeapon *pWeapon;
  
  		pWeapon = GetActiveWeapon();
  
  		if( !FClassnameIs( pWeapon, "weapon_pistol" ) )
  		{
  			m_fWeaponDrawn = true;
  		}
  		
  		if( !m_fWeaponDrawn ) 
  		{
  			GetActiveWeapon()->AddEffects( EF_NODRAW );
  		}
  	}
  
  
  	m_TimeYieldShootSlot.Set( 2, 6 );
  
  	GetEnemies()->SetFreeKnowledgeDuration( 6.0 );
  
  	m_bShouldActivateBaton = false;
  	m_flValidStitchTime = -1.0f;
  	m_flNextLedgeCheckTime = -1.0f;
  	m_nBurstReloadCount = METROPOLICE_BURST_RELOAD_COUNT;
  	SetBurstMode( false );
  
  	// Clear out spawnflag if we're missing the smg1
  	if( HasSpawnFlags( SF_METROPOLICE_ALWAYS_STITCH ) )
  	{
  		if ( !Weapon_OwnsThisType( "weapon_smg1" ) )
  		{
  			Warning( "Warning! Metrocop is trying to use the stitch behavior but he has no smg1!\n" );
  			RemoveSpawnFlags( SF_METROPOLICE_ALWAYS_STITCH );
  		}
  	}
  
  	m_nNumWarnings = 0;
  	m_bPlayerTooClose = false;
  	m_bKeepFacingPlayer = false;
  	m_flChasePlayerTime = 0;
  	m_vecPreChaseOrigin = vec3_origin;
  	m_flPreChaseYaw = 0;
  
  	SetUse( &CNPC_MetroPolice::PrecriminalUse );
  
  	// Start us with a visible manhack if we have one
  	if ( m_iManhacks )
  	{
  		SetBodygroup( METROPOLICE_BODYGROUP_MANHACK, true );
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Update weapon ranges
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::Weapon_Equip( CBaseCombatWeapon *pWeapon )
  {
  	BaseClass::Weapon_Equip( pWeapon );
  
  	if ( HasSpawnFlags(SF_METROPOLICE_MID_RANGE_ATTACK) && GetActiveWeapon() )
  	{
  		GetActiveWeapon()->m_fMaxRange1 = METROPOLICE_MID_RANGE_ATTACK_RANGE;
  		GetActiveWeapon()->m_fMaxRange2 = METROPOLICE_MID_RANGE_ATTACK_RANGE;
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // FuncTankBehavior-related sentences
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::SpeakFuncTankSentence( int nSentenceType )
  {
  	switch ( nSentenceType )
  	{
  	case FUNCTANK_SENTENCE_MOVE_TO_MOUNT:
  		m_Sentences.Speak( "METROPOLICE_FT_APPROACH", SENTENCE_PRIORITY_MEDIUM );
  		break;
  
  	case FUNCTANK_SENTENCE_JUST_MOUNTED:
  		m_Sentences.Speak( "METROPOLICE_FT_MOUNT", SENTENCE_PRIORITY_HIGH );
  		break;
  
  	case FUNCTANK_SENTENCE_SCAN_FOR_ENEMIES:
  		m_Sentences.Speak( "METROPOLICE_FT_SCAN", SENTENCE_PRIORITY_NORMAL );
  		break;
  
  	case FUNCTANK_SENTENCE_DISMOUNTING:
  		m_Sentences.Speak( "METROPOLICE_FT_DISMOUNT", SENTENCE_PRIORITY_HIGH );
  		break;
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Standoff Behavior-related sentences
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::SpeakStandoffSentence( int nSentenceType )
  {
  	switch ( nSentenceType )
  	{
  	case STANDOFF_SENTENCE_BEGIN_STANDOFF:
  		m_Sentences.Speak( "METROPOLICE_SO_BEGIN", SENTENCE_PRIORITY_HIGH, SENTENCE_CRITERIA_SQUAD_LEADER );
  		break;
  
  	case STANDOFF_SENTENCE_END_STANDOFF:
  		m_Sentences.Speak( "METROPOLICE_SO_END", SENTENCE_PRIORITY_HIGH, SENTENCE_CRITERIA_SQUAD_LEADER );
  		break;
  
  	case STANDOFF_SENTENCE_OUT_OF_AMMO:
  		AnnounceOutOfAmmo( );
  		break;
  
  	case STANDOFF_SENTENCE_FORCED_TAKE_COVER:
  		m_Sentences.Speak( "METROPOLICE_SO_FORCE_COVER" );
  		break;
  
  	case STANDOFF_SENTENCE_STAND_CHECK_TARGET:
  		if ( gm_flTimeLastSpokePeek != 0 && gpGlobals->curtime - gm_flTimeLastSpokePeek > 20 )
  		{
  			m_Sentences.Speak( "METROPOLICE_SO_PEEK" );
  			gm_flTimeLastSpokePeek = gpGlobals->curtime;
  		}
  		break;
  	}
  }
  
  //-----------------------------------------------------------------------------
  // Assault Behavior-related sentences
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::SpeakAssaultSentence( int nSentenceType )
  {
  	switch ( nSentenceType )
  	{
  	case ASSAULT_SENTENCE_HIT_RALLY_POINT:
  		m_Sentences.SpeakQueued( "METROPOLICE_AS_HIT_RALLY", SENTENCE_PRIORITY_NORMAL );
  		break;
  
  	case ASSAULT_SENTENCE_HIT_ASSAULT_POINT:
  		m_Sentences.SpeakQueued( "METROPOLICE_AS_HIT_ASSAULT", SENTENCE_PRIORITY_NORMAL );
  		break;
  
  	case ASSAULT_SENTENCE_SQUAD_ADVANCE_TO_RALLY:
  		if ( m_Sentences.Speak( "METROPOLICE_AS_ADV_RALLY", SENTENCE_PRIORITY_MEDIUM, SENTENCE_CRITERIA_SQUAD_LEADER ) >= 0 )
  		{
  			GetSquad()->BroadcastInteraction( g_interactionMetrocopClearSentenceQueues, NULL );
  		}
  		break;
  
  	case ASSAULT_SENTENCE_SQUAD_ADVANCE_TO_ASSAULT:
  		if ( m_Sentences.Speak( "METROPOLICE_AS_ADV_ASSAULT", SENTENCE_PRIORITY_MEDIUM, SENTENCE_CRITERIA_SQUAD_LEADER ) >= 0 )
  		{
  			GetSquad()->BroadcastInteraction( g_interactionMetrocopClearSentenceQueues, NULL );
  		}
  		break;
  
  	case ASSAULT_SENTENCE_COVER_NO_AMMO:
  		AnnounceOutOfAmmo( );
  		break;
  
  	case ASSAULT_SENTENCE_UNDER_ATTACK:
  		m_Sentences.Speak( "METROPOLICE_GO_ALERT" );
  		break;
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Speaking while using TASK_SPEAK_SENTENCE
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::SpeakSentence( int nSentenceType )
  {
  	if ( !PlayerIsCriminal() )
  		return;
  
  	if ( nSentenceType >= SENTENCE_BASE_BEHAVIOR_INDEX )
  	{
  		if ( GetRunningBehavior() == &m_FuncTankBehavior )
  		{
  			SpeakFuncTankSentence( nSentenceType );
  			return;
  		}
  
  		if ( GetRunningBehavior() == &m_StandoffBehavior )
  		{
  			SpeakStandoffSentence( nSentenceType );
  			return;
  		}
  
  		if ( GetRunningBehavior() == &m_AssaultBehavior )
  		{
  			SpeakAssaultSentence( nSentenceType );
  			return;
  		}
  	}
  
  	switch ( nSentenceType )
  	{
  	case METROPOLICE_SENTENCE_FREEZE:
  		m_Sentences.Speak( "METROPOLICE_FREEZE", SENTENCE_PRIORITY_MEDIUM, SENTENCE_CRITERIA_NORMAL );
  		break;
  
  	case METROPOLICE_SENTENCE_HES_OVER_HERE:
  		m_Sentences.Speak( "METROPOLICE_OVER_HERE", SENTENCE_PRIORITY_MEDIUM, SENTENCE_CRITERIA_NORMAL );
  		break;
  
  	case METROPOLICE_SENTENCE_HES_RUNNING:
  		m_Sentences.Speak( "METROPOLICE_HES_RUNNING", SENTENCE_PRIORITY_HIGH, SENTENCE_CRITERIA_NORMAL );
  		break;
  
  	case METROPOLICE_SENTENCE_TAKE_HIM_DOWN:
  		m_Sentences.Speak( "METROPOLICE_TAKE_HIM_DOWN", SENTENCE_PRIORITY_HIGH, SENTENCE_CRITERIA_NORMAL );
  		break;
  
  	case METROPOLICE_SENTENCE_ARREST_IN_POSITION:
  		m_Sentences.Speak( "METROPOLICE_ARREST_IN_POS", SENTENCE_PRIORITY_MEDIUM, SENTENCE_CRITERIA_NORMAL );
  		break;
  
  	case METROPOLICE_SENTENCE_DEPLOY_MANHACK:
  		m_Sentences.Speak( "METROPOLICE_DEPLOY_MANHACK" );
  		break;
  
  	case METROPOLICE_SENTENCE_MOVE_INTO_POSITION:
  		{
  			CBaseEntity *pEntity = GetEnemy();
  			
  			// NOTE: This is a good time to check to see if the player is hurt.
  			// Have the cops notice this and call out
  			if ( pEntity && !HasSpawnFlags( SF_METROPOLICE_ARREST_ENEMY ) )
  			{
  				if ( pEntity->IsPlayer() && (pEntity->GetHealth() <= 20) )
  				{
  					if ( !HasMemory(bits_MEMORY_PLAYER_HURT) ) 
  					{
  						if ( m_Sentences.Speak( "METROPOLICE_PLAYERHIT", SENTENCE_PRIORITY_HIGH ) >= 0 )
  						{
  							m_pSquad->SquadRemember(bits_MEMORY_PLAYER_HURT);
  						}
  					}
  				}
  
  				if ( GetNavigator()->GetPath()->GetPathLength() > 20 * 12.0f )
  				{
  					m_Sentences.Speak( "METROPOLICE_FLANK" );
  				}
  			}
  		}
  		break;
  
  	case METROPOLICE_SENTENCE_HEARD_SOMETHING:
  		if ( ( GetState() == NPC_STATE_ALERT ) || ( GetState() == NPC_STATE_IDLE ) )
  		{
  			m_Sentences.Speak( "METROPOLICE_HEARD_SOMETHING", SENTENCE_PRIORITY_MEDIUM );
  		}
  		break;
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Speaking
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::AnnounceEnemyType( CBaseEntity *pEnemy )
  {
  	if ( !pEnemy || !m_pSquad )
  		return;
  
  	// Don't announce enemies when the player isn't a criminal
  	if ( !PlayerIsCriminal() )
  		return;
  
  	// Don't announce enemies when I'm in arrest behavior
  	if ( HasSpawnFlags( SF_METROPOLICE_ARREST_ENEMY ) )
  		return;
  
  	if ( m_pSquad->IsLeader( this ) || ( m_pSquad->GetLeader() && m_pSquad->GetLeader()->GetEnemy() != GetEnemy() ) )
  	{
  		// First contact, and I'm the squad leader.
  		const char *pSentenceName = "METROPOLICE_MONST";
  		switch ( pEnemy->Classify() )
  		{
  		case CLASS_PLAYER:
  			{
  				CBasePlayer *pPlayer = assert_cast<CBasePlayer*>( pEnemy );
  				if ( pPlayer && pPlayer->IsInAVehicle() )
  				{
  					pSentenceName = "METROPOLICE_MONST_PLAYER_VEHICLE";
  				}
  				else
  				{
  					pSentenceName = "METROPOLICE_MONST_PLAYER";
  				}
  			}
  			break;
  
  		case CLASS_PLAYER_ALLY:
  		case CLASS_CITIZEN_REBEL:
  		case CLASS_CITIZEN_PASSIVE:
  		case CLASS_VORTIGAUNT:
  			pSentenceName = "METROPOLICE_MONST_CITIZENS";
  			break;
  
  		case CLASS_PLAYER_ALLY_VITAL:
  			pSentenceName = "METROPOLICE_MONST_CHARACTER";
  			break;
  
  		case CLASS_ANTLION:
  			pSentenceName = "METROPOLICE_MONST_BUGS";
  			break;
  
  		case CLASS_ZOMBIE:
  			pSentenceName = "METROPOLICE_MONST_ZOMBIES";
  			break;
  
  		case CLASS_HEADCRAB:
  		case CLASS_BARNACLE:
  			pSentenceName = "METROPOLICE_MONST_PARASITES";
  			break;
  		}
  
  		m_Sentences.Speak( pSentenceName, SENTENCE_PRIORITY_HIGH );
  	}
  	else
  	{
  		if ( m_pSquad->GetLeader() && FOkToMakeSound( SENTENCE_PRIORITY_MEDIUM ) )
  		{
  			// squelch anything that isn't high priority so the leader can speak
  			JustMadeSound( SENTENCE_PRIORITY_MEDIUM );	
  		}
  	}
  
  }
  
  
  //-----------------------------------------------------------------------------
  // Speaking
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::AnnounceEnemyKill( CBaseEntity *pEnemy )
  {
  	if ( !pEnemy )
  		return;
  
  	const char *pSentenceName = "METROPOLICE_KILL_MONST";
  	switch ( pEnemy->Classify() )
  	{
  	case CLASS_PLAYER:
  		pSentenceName = "METROPOLICE_KILL_PLAYER";
  		break;
  
  	// no sentences for these guys yet
  	case CLASS_PLAYER_ALLY:
  	case CLASS_CITIZEN_REBEL:
  	case CLASS_CITIZEN_PASSIVE:
  	case CLASS_VORTIGAUNT:
  		pSentenceName = "METROPOLICE_KILL_CITIZENS";
  		break;
  
  	case CLASS_PLAYER_ALLY_VITAL:
  		pSentenceName = "METROPOLICE_KILL_CHARACTER";
  		break;
  
  	case CLASS_ANTLION:
  		pSentenceName = "METROPOLICE_KILL_BUGS";
  		break;
  
  	case CLASS_ZOMBIE:
  		pSentenceName = "METROPOLICE_KILL_ZOMBIES";
  		break;
  
  	case CLASS_HEADCRAB:
  	case CLASS_BARNACLE:
  		pSentenceName = "METROPOLICE_KILL_PARASITES";
  		break;
  	}
  
  	m_Sentences.Speak( pSentenceName, SENTENCE_PRIORITY_HIGH );
  }
  
  
  //-----------------------------------------------------------------------------
  // Announce out of ammo
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::AnnounceOutOfAmmo( )
  {
  	if ( HasCondition( COND_NO_PRIMARY_AMMO ) )
  	{
  		m_Sentences.Speak( "METROPOLICE_COVER_NO_AMMO" );
  	}
  	else
  	{
  		m_Sentences.Speak( "METROPOLICE_COVER_LOW_AMMO" );
  	}
  }
  
  //-----------------------------------------------------------------------------
  // We're taking cover from danger
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::AnnounceTakeCoverFromDanger( CSound *pSound )
  {
  	CBaseEntity *pSoundOwner = pSound->m_hOwner;
  	if ( pSoundOwner )
  	{
  		CBaseGrenade *pGrenade = dynamic_cast<CBaseGrenade *>(pSoundOwner);
  		if ( pGrenade )
  		{
  			if ( IRelationType( pGrenade->GetThrower() ) != D_LI )
  			{
  				// special case call out for enemy grenades
  				m_Sentences.Speak( "METROPOLICE_DANGER_GREN", SENTENCE_PRIORITY_HIGH, SENTENCE_CRITERIA_NORMAL );
  			}
  			return;
  		}
  
  		if ( pSoundOwner->GetServerVehicle() )
  		{
  			m_Sentences.Speak( "METROPOLICE_DANGER_VEHICLE", SENTENCE_PRIORITY_HIGH, SENTENCE_CRITERIA_NORMAL );
  			return;
  		}
  
  		if ( FClassnameIs( pSoundOwner, "npc_manhack" ) )
  		{
  			if ( pSoundOwner->HasPhysicsAttacker( 1.0f ) )
  			{
  				m_Sentences.Speak( "METROPOLICE_DANGER_MANHACK", SENTENCE_PRIORITY_HIGH, SENTENCE_CRITERIA_NORMAL );
  			}
  			return;
  		}
  	}
  
  	// I hear something dangerous, probably need to take cover.
  	// dangerous sound nearby!, call it out
  	const char *pSentenceName = "METROPOLICE_DANGER";
  	m_Sentences.Speak( pSentenceName, SENTENCE_PRIORITY_HIGH, SENTENCE_CRITERIA_NORMAL );
  }
  
  
  				
  //-----------------------------------------------------------------------------
  // Are we currently firing a burst?
  //-----------------------------------------------------------------------------
  bool CNPC_MetroPolice::IsCurrentlyFiringBurst() const
  {
  	return (m_nBurstMode != BURST_NOT_ACTIVE);
  }
  
  
  //-----------------------------------------------------------------------------
  // Is my enemy currently in an airboat?
  //-----------------------------------------------------------------------------
  bool CNPC_MetroPolice::IsEnemyInAnAirboat() const
  {
  	// Should this be a condition??
  	if ( !GetEnemy() || !GetEnemy()->IsPlayer() )
  		return false;
  
  	CBaseEntity *pVehicle = static_cast<CBasePlayer*>( GetEnemy() )->GetVehicleEntity(); 
  	if ( !pVehicle )
  		return false;
  
  	// NOTE: Could just return true if in a vehicle maybe
  	return FClassnameIs( pVehicle, "prop_vehicle_airboat" );
  }
  
  
  //-----------------------------------------------------------------------------
  // Returns the airboat
  //-----------------------------------------------------------------------------
  CBaseEntity *CNPC_MetroPolice::GetEnemyAirboat() const
  {
  	// Should this be a condition??
  	if ( !GetEnemy() || !GetEnemy()->IsPlayer() )
  		return NULL;
  
  	return static_cast<CBasePlayer*>( GetEnemy() )->GetVehicleEntity(); 
  }
  
  
  //-----------------------------------------------------------------------------
  // Which entity are we actually trying to shoot at?
  //-----------------------------------------------------------------------------
  CBaseEntity *CNPC_MetroPolice::GetShootTarget()
  {
  	// Should this be a condition??
  	CBaseEntity *pEnemy = GetEnemy();
  	if ( !pEnemy || !pEnemy->IsPlayer() )
  		return pEnemy;
  
  	CBaseEntity *pVehicle = static_cast<CBasePlayer*>( pEnemy )->GetVehicleEntity(); 
  	return pVehicle ? pVehicle : pEnemy;
  }
  
  
  //-----------------------------------------------------------------------------
  // Set up the shot regulator based on the equipped weapon
  //-----------------------------------------------------------------------------
  
  // Ranges across which to tune fire rates
  const float MIN_PISTOL_MODIFY_DIST = 15 * 12;
  const float MAX_PISTOL_MODIFY_DIST = 150 * 12;
  
  // Range for rest period minimums
  const float MIN_MIN_PISTOL_REST_INTERVAL = 0.6;
  const float MAX_MIN_PISTOL_REST_INTERVAL = 1.2;
  
  // Range for rest period maximums
  const float MIN_MAX_PISTOL_REST_INTERVAL = 1.2;
  const float MAX_MAX_PISTOL_REST_INTERVAL = 2.0;
  
  // Range for burst minimums
  const int 	MIN_MIN_PISTOL_BURST = 2;
  const int 	MAX_MIN_PISTOL_BURST = 4;
  
  // Range for burst maximums
  const int 	MIN_MAX_PISTOL_BURST = 5;
  const int 	MAX_MAX_PISTOL_BURST = 8;
  
  void CNPC_MetroPolice::OnUpdateShotRegulator( )
  {
  	BaseClass::OnUpdateShotRegulator();
  
  	// FIXME: This code (except the burst interval) could be used for all weapon types 
  	if( Weapon_OwnsThisType( "weapon_pistol" ) )
  	{
  		if ( m_nBurstMode == BURST_NOT_ACTIVE )
  		{
  			if ( GetEnemy() )
  			{
  				float dist = WorldSpaceCenter().DistTo( GetEnemy()->WorldSpaceCenter() );
  				
  				dist = clamp( dist, MIN_PISTOL_MODIFY_DIST, MAX_PISTOL_MODIFY_DIST );
  				
  				float factor = (dist - MIN_PISTOL_MODIFY_DIST) / (MAX_PISTOL_MODIFY_DIST - MIN_PISTOL_MODIFY_DIST);
  				
  				int		nMinBurst			= MIN_MIN_PISTOL_BURST + ( MAX_MIN_PISTOL_BURST - MIN_MIN_PISTOL_BURST ) * (1.0 - factor);
  				int		nMaxBurst			= MIN_MAX_PISTOL_BURST + ( MAX_MAX_PISTOL_BURST - MIN_MAX_PISTOL_BURST ) * (1.0 - factor);
  				float	flMinRestInterval	= MIN_MIN_PISTOL_REST_INTERVAL + ( MAX_MIN_PISTOL_REST_INTERVAL - MIN_MIN_PISTOL_REST_INTERVAL ) * factor;
  				float	flMaxRestInterval	= MIN_MAX_PISTOL_REST_INTERVAL + ( MAX_MAX_PISTOL_REST_INTERVAL - MIN_MAX_PISTOL_REST_INTERVAL ) * factor;
  				
  				GetShotRegulator()->SetRestInterval( flMinRestInterval, flMaxRestInterval );
  				GetShotRegulator()->SetBurstShotCountRange( nMinBurst, nMaxBurst );
  			}
  			else
  			{
  				GetShotRegulator()->SetBurstShotCountRange(GetActiveWeapon()->GetMinBurst(), GetActiveWeapon()->GetMaxBurst() );
  				GetShotRegulator()->SetRestInterval( 0.6, 1.4 );
  			}
  		}
  
  		// Add some noise into the pistol
  		GetShotRegulator()->SetBurstInterval( 0.2f, 0.5f );
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Burst mode!
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::SetBurstMode( bool bEnable )
  {
  	int nOldBurstMode = m_nBurstMode;
  	m_nBurstSteerMode = BURST_STEER_NONE;
  	m_flBurstPredictTime = gpGlobals->curtime - 1.0f;
  	if ( GetActiveWeapon() )
  	{
  		m_nBurstMode = bEnable ? BURST_ACTIVE : BURST_NOT_ACTIVE;
  		if ( bEnable )
  		{
  			m_nBurstHits = 0;
  		}
  	}
  	else
  	{
  		m_nBurstMode = BURST_NOT_ACTIVE;
  	}
  
  	if ( m_nBurstMode != nOldBurstMode )
  	{
  		OnUpdateShotRegulator();
  		if ( m_nBurstMode == BURST_NOT_ACTIVE )
  		{
  			// Check for inconsistency...
  			int nMinBurstCount, nMaxBurstCount;
  			GetShotRegulator()->GetBurstShotCountRange( &nMinBurstCount, &nMaxBurstCount );
  			if ( GetShotRegulator()->GetBurstShotsRemaining() > nMaxBurstCount )
  			{
  				GetShotRegulator()->SetBurstShotsRemaining( nMaxBurstCount );
  			}
  		}
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Should we attempt to stitch?
  //-----------------------------------------------------------------------------
  bool CNPC_MetroPolice::ShouldAttemptToStitch()
  {
  	if ( IsEnemyInAnAirboat() )
  		return true;
  
  	if ( !GetShootTarget() )
  		return false;
  
  	if ( HasSpawnFlags( SF_METROPOLICE_ALWAYS_STITCH ) )
  	{
  		// Don't stitch if the player is at the same level or higher
  		if ( GetEnemy()->GetAbsOrigin().z - GetAbsOrigin().z > -36 )
  			return false;
  
  		return true;
  	}
  
  	return false;
  }
  
  
  //-----------------------------------------------------------------------------
  // position to shoot at
  //-----------------------------------------------------------------------------
  Vector CNPC_MetroPolice::StitchAimTarget( const Vector &posSrc, bool bNoisy ) 
  {
  	// This will make us aim a stitch at the feet of the player so we can see it
  	if ( !GetEnemy()->IsPlayer() )
  		return GetShootTarget()->BodyTarget( posSrc, bNoisy );
  
  	if ( !IsEnemyInAnAirboat() )
  	{
  		Vector vecBodyTarget;
  		if ( ( GetEnemy()->GetWaterLevel() == 0 ) && ( GetEnemy()->GetFlags() & FL_ONGROUND ) )
  		{
  			GetEnemy()->CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.5f, 0.08f ), &vecBodyTarget );
  			return vecBodyTarget;
  		}
  
  		// Underwater? Just use the normal thing
  		if ( GetEnemy()->GetWaterLevel() == 3 )
  			return GetShootTarget()->BodyTarget( posSrc, bNoisy );
  
  		// Trace down...
  		trace_t	trace;
  		GetEnemy()->CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.5f, 1.0f ), &vecBodyTarget );
  		float flHeight = GetEnemy()->WorldAlignSize().z;
  		UTIL_TraceLine( vecBodyTarget, vecBodyTarget + Vector( 0, 0, -flHeight -80 ), 
  			(MASK_SOLID_BRUSHONLY | MASK_WATER), NULL, COLLISION_GROUP_NONE, &trace );
  		return trace.endpos;
  	}
  
  	// NOTE: HACK! Ths 0.08 is where the water level happens to be.
  	// We probably want to find the exact water level and use that as the z position.
  	Vector vecBodyTarget;
  	if ( !bNoisy )
  	{
  		GetShootTarget()->CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.5f, 0.08f ), &vecBodyTarget );
  	}
  	else
  	{
  		GetShootTarget()->CollisionProp()->RandomPointInBounds( Vector( 0.25f, 0.25f, 0.08f ), Vector( 0.75f, 0.75f, 0.08f ), &vecBodyTarget );
  	}
  
  	return vecBodyTarget;
  }
  
  
  //-----------------------------------------------------------------------------
  // Burst mode!
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::AimBurstRandomly( int nMinCount, int nMaxCount, float flMinDelay, float flMaxDelay )
  {
  	if ( !IsCurrentlyFiringBurst() )
  		return;
  
  	GetShotRegulator()->SetParameters( nMinCount, nMaxCount, flMinDelay, flMaxDelay );
  	GetShotRegulator()->Reset( true );
  
  	int nShotCount = GetShotRegulator()->GetBurstShotsRemaining();
  
  	Vector vecDelta = StitchAimTarget( GetAbsOrigin(), true ) - Weapon_ShootPosition();
  	VectorNormalize( vecDelta );
  
  	// Choose a random direction vector perpendicular to the delta position
  	Vector vecRight, vecUp;
  	VectorVectors( vecDelta, vecRight, vecUp );
  	float flAngle = random->RandomFloat( 0.0f, 2 * M_PI );
  	VectorMultiply( vecRight, cos(flAngle), m_vecBurstDelta );
  	VectorMA( m_vecBurstDelta, sin(flAngle), vecUp, m_vecBurstDelta );
  
  	// The size of this determines the cone angle
  	m_vecBurstDelta *= 0.4f;
  
  	VectorMA( vecDelta, -0.5f, m_vecBurstDelta, m_vecBurstTargetPos );
  	m_vecBurstTargetPos += Weapon_ShootPosition();
  
  	m_vecBurstDelta /= (nShotCount - 1);
  }
  
  
  //-----------------------------------------------------------------------------
  // Choose a random vector somewhere between the two specified vectors
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::RandomDirectionBetweenVectors( const Vector &vecStart, const Vector &vecEnd, Vector *pResult )
  {
  	Assert( fabs( vecStart.Length() - 1.0f ) < 1e-3 );
  	Assert( fabs( vecEnd.Length() - 1.0f ) < 1e-3 );
  
  	float flCosAngle = DotProduct( vecStart, vecEnd );
  	if ( fabs( flCosAngle - 1.0f ) < 1e-3 )
  	{
  		*pResult = vecStart;
  		return;
  	}
  
  	Vector vecNormal;
  	CrossProduct( vecStart, vecEnd, vecNormal );
  	float flLength = VectorNormalize( vecNormal );
  	if ( flLength < 1e-3 )
  	{
  		// This is wrong for anti-parallel vectors. so what?
  		*pResult = vecStart;
  		return;
  	}
  
  	// Rotate the starting angle the specified amount
  	float flAngle = acos(flCosAngle) * random->RandomFloat( 0.0f, 1.0f );
  	VMatrix rotationMatrix;
  	MatrixBuildRotationAboutAxis( rotationMatrix, vecNormal, flAngle );
  	Vector3DMultiply( rotationMatrix, vecStart, *pResult );
  }
  
  
  //-----------------------------------------------------------------------------
  // Compute a predicted shoot target position n seconds into the future
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::PredictShootTargetPosition( float flDeltaTime, float flMinLeadDist, float flAddVelocity, Vector *pVecTarget, Vector *pVecTargetVelocity )
  {
  	CBaseEntity *pShootTarget = GetShootTarget();
  	*pVecTarget = StitchAimTarget( GetAbsOrigin(), true );
  
  	Vector vecSmoothedVel = pShootTarget->GetSmoothedVelocity();
  
  	// When we're in the air, don't predict vertical motion
  	if( (pShootTarget->GetFlags() & FL_ONGROUND) == 0 )
  	{
  		vecSmoothedVel.z = 0.0f;
  	}
  
  	Vector vecVelocity;
  	AngularImpulse angImpulse;
  	GetShootTarget()->GetVelocity( &vecVelocity, &angImpulse );
  
  	Vector vecLeadVector;
  	VMatrix rotationMatrix;
  	float flAngVel = VectorNormalize( angImpulse );
  	flAngVel -= 30.0f;
  	if ( flAngVel > 0.0f )
  	{
  		MatrixBuildRotationAboutAxis( rotationMatrix, angImpulse, flAngVel * flDeltaTime * 0.333f );
  		Vector3DMultiply( rotationMatrix, vecSmoothedVel, vecLeadVector );
  	}
  	else
  	{
  		vecLeadVector = vecSmoothedVel;
  	}
  
  	if ( flAddVelocity != 0.0f )
  	{
  		Vector vecForward;
  		pShootTarget->GetVectors( &vecForward, NULL, NULL );
  		VectorMA( vecLeadVector, flAddVelocity, vecForward,	vecLeadVector );
  	}
  
  	*pVecTargetVelocity = vecLeadVector;
  	
  	if ( (vecLeadVector.LengthSqr() * flDeltaTime * flDeltaTime) < flMinLeadDist * flMinLeadDist )
  	{
  		VectorNormalize( vecLeadVector );
  		vecLeadVector *= flMinLeadDist;
  	}
  	else
  	{
  		vecLeadVector *= flDeltaTime;
  	}
  
  	*pVecTarget += vecLeadVector;
  }
  
  
  //-----------------------------------------------------------------------------
  // Compute a predicted velocity n seconds into the future (given a known acceleration rate)
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::PredictShootTargetVelocity( float flDeltaTime, Vector *pVecTargetVel )
  {
  	*pVecTargetVel = GetShootTarget()->GetSmoothedVelocity();
  
  	// Unless there's a big angular velocity, we can assume he accelerates
  	// along the forward direction. Predict acceleration for
  	Vector vecForward;
  	GetShootTarget()->GetVectors( &vecForward, NULL, NULL );
  
  //	float flBlendFactor = 1.0f;
  //	VectorMA( *pVecTargetVel, flBlendFactor * VEHICLE_PREDICT_ACCELERATION, vecForward, *pVecTargetVel );
  //	if ( pVecTargetVel->LengthSqr() > (VEHICLE_PREDICT_MAX_SPEED * VEHICLE_PREDICT_MAX_SPEED) )
  //	{
  //		VectorNormalize( *pVecTargetVel );
  //		*pVecTargetVel *= VEHICLE_PREDICT_MAX_SPEED;
  //	}
  }
  
  
  //-----------------------------------------------------------------------------
  // How many shots will I fire in a particular amount of time?
  //-----------------------------------------------------------------------------
  int CNPC_MetroPolice::CountShotsInTime( float flDeltaTime ) const
  {
  	return (int)(flDeltaTime / GetActiveWeapon()->GetFireRate() + 0.5f);
  }
  
  float CNPC_MetroPolice::GetTimeForShots( int nShotCount ) const
  {
  	return nShotCount * GetActiveWeapon()->GetFireRate();
  }
  
  
  //-----------------------------------------------------------------------------
  // Visualize stitch
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::VisualizeStitch( const Vector &vecStart, const Vector &vecEnd )
  {
  	NDebugOverlay::Cross3D( vecStart, -Vector(32,32,32), Vector(32,32,32), 255, 0, 0, false, 5.0f );
  	NDebugOverlay::Cross3D( vecEnd, -Vector(32,32,32), Vector(32,32,32), 0, 255, 0, false, 5.0f );
  	NDebugOverlay::Line( vecStart, vecEnd, 0, 255, 0, true, 5.0f );
  }
  
  
  //-----------------------------------------------------------------------------
  // Visualize line of death
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::VisualizeLineOfDeath( )
  {
  	Vector vecAcross, vecStart;
  	CrossProduct( m_vecBurstLineOfDeathDelta, Vector( 0, 0, 1 ), vecAcross );
  	VectorNormalize( vecAcross );
  	NDebugOverlay::Line( m_vecBurstLineOfDeathOrigin, m_vecBurstLineOfDeathOrigin + m_vecBurstLineOfDeathDelta, 255, 255, 0, false, 5.0f );
  	VectorMA( m_vecBurstLineOfDeathOrigin, m_flBurstSteerDistance, vecAcross, vecStart );
  	NDebugOverlay::Line( vecStart, vecStart + m_vecBurstLineOfDeathDelta, 255, 0, 0, false, 5.0f );
  	VectorMA( m_vecBurstLineOfDeathOrigin, -m_flBurstSteerDistance, vecAcross, vecStart );
  	NDebugOverlay::Line( vecStart, vecStart + m_vecBurstLineOfDeathDelta, 255, 0, 0, false, 5.0f );
  }
  
  
  //-----------------------------------------------------------------------------
  // Burst mode!
  //-----------------------------------------------------------------------------
  #define AIM_AT_NEAR_DISTANCE_MIN		400.0f
  #define AIM_AT_NEAR_DISTANCE_MAX		1000.0f
  #define AIM_AT_NEAR_DISTANCE_DELTA		(AIM_AT_NEAR_DISTANCE_MAX - AIM_AT_NEAR_DISTANCE_MIN)
  #define AIM_AT_NEAR_DISTANCE_BONUS  	-200.0f
  
  #define AIM_AT_FAR_DISTANCE_MIN			2000.0f
  #define AIM_AT_FAR_DISTANCE_BONUS_DISTANCE	500.0f
  #define AIM_AT_FAR_DISTANCE_BONUS  		200.0f	// Add this much bonus after each BONUS_DISTANCE
  
  
  //-----------------------------------------------------------------------------
  // Modify the stitch length
  //-----------------------------------------------------------------------------
  float CNPC_MetroPolice::ComputeDistanceStitchModifier( float flDistanceToTarget ) const
  {
  	if ( flDistanceToTarget < AIM_AT_NEAR_DISTANCE_MIN )
  	{
  		return AIM_AT_NEAR_DISTANCE_BONUS;
  	}
  
  	if ( flDistanceToTarget < AIM_AT_NEAR_DISTANCE_MAX )
  	{
  		float flFraction = 1.0f - ((flDistanceToTarget - AIM_AT_NEAR_DISTANCE_MIN) / AIM_AT_NEAR_DISTANCE_DELTA);
  		return flFraction * AIM_AT_NEAR_DISTANCE_BONUS;
  	}
  
  	if ( flDistanceToTarget > AIM_AT_FAR_DISTANCE_MIN )
  	{
  		float flFactor = (flDistanceToTarget - AIM_AT_FAR_DISTANCE_MIN) / AIM_AT_FAR_DISTANCE_BONUS_DISTANCE;
  		return flFactor * AIM_AT_FAR_DISTANCE_BONUS;
  	}
  
  	return 0.0f;
  }
  
  
  //-----------------------------------------------------------------------------
  // Set up the shot regulator
  //-----------------------------------------------------------------------------
  int CNPC_MetroPolice::SetupBurstShotRegulator( float flReactionTime )
  {
  	// We want a certain amount of reaction time before the shots hit the boat
  	int nDesiredShotCount = CountShotsInTime( flReactionTime );
  	GetShotRegulator()->SetBurstShotCountRange( nDesiredShotCount, nDesiredShotCount );
  	GetShotRegulator()->SetRestInterval( 0.7f, 0.9f );
  	GetShotRegulator()->Reset( true );
  	int nShots = GetShotRegulator()->GetBurstShotsRemaining();
  	OnRangeAttack1();
  	return nShots;
  }
  
  
  //-----------------------------------------------------------------------------
  // Shoots a burst right at the player
  //-----------------------------------------------------------------------------
  #define TIGHT_GROUP_MIN_DIST 750.0f
  #define TIGHT_GROUP_MIN_SPEED 400.0f
  
  void CNPC_MetroPolice::AimBurstTightGrouping( float flShotTime )
  {
  	if ( !IsCurrentlyFiringBurst() )
  		return;
  
  	// We want a certain amount of reaction time before the shots hit the boat
  	SetupBurstShotRegulator( flShotTime );
  
  	// Max number of times we can hit the enemy.
  	// Can hit more if we're slow + close
  	float flDistToTargetSqr = GetShootTarget()->WorldSpaceCenter().DistToSqr( Weapon_ShootPosition() );
  
  	int nHitCount = sk_metropolice_stitch_tight_hitcount.GetInt();
  
  	Vector vecTargetVel;
  	GetShootTarget()->GetVelocity( &vecTargetVel, NULL );
  	if (( flDistToTargetSqr > TIGHT_GROUP_MIN_DIST*TIGHT_GROUP_MIN_DIST ) || 
  		( vecTargetVel.LengthSqr() > TIGHT_GROUP_MIN_SPEED * TIGHT_GROUP_MIN_SPEED ))
  	{
  		m_nMaxBurstHits = random->RandomInt( nHitCount, nHitCount + 1 );
  	}
  	else
  	{
  		m_nMaxBurstHits = random->RandomInt( 2 * nHitCount - 1, 2 * nHitCount + 1 );
  	}
  
  	m_nBurstMode = BURST_TIGHT_GROUPING;
  
  	// This helps the NPC model aim at the correct point
  	m_nBurstSteerMode = BURST_STEER_EXACTLY_TOWARD_TARGET;
  	m_vecBurstTargetPos = GetEnemy()->WorldSpaceCenter();
  }
  
  
  //-----------------------------------------------------------------------------
  // Reaction time for stitch
  //-----------------------------------------------------------------------------
  #define AIM_AT_TIME_DELTA_SPEED 100.0f
  #define AIM_AT_TIME_DELTA_DIST 500.0f
  #define AIM_AT_TIME_SPEED_COUNT 6
  #define AIM_AT_TIME_DIST_COUNT 7
  
  static float s_pReactionFraction[AIM_AT_TIME_DIST_COUNT][AIM_AT_TIME_SPEED_COUNT] =
  {
  	{  0.5f, 0.5f,  0.5f,  0.5f,  0.5f, 1.0f },
  	{  0.5f, 0.5f,  0.5f,  0.5f, 0.75f, 1.0f },
  	{  0.5f, 0.5f,  0.5f, 0.65f,  0.8f, 1.0f },
  	{  0.5f, 0.5f,  0.5f, 0.75f,  1.0f, 1.0f },
  	{  0.5f, 0.5f, 0.75f,  1.0f,  1.0f, 1.0f },
  	{ 0.75f, 1.0f,  1.0f,  1.0f,  1.0f, 1.0f },
  	{  1.0f, 1.0f,  1.0f,  1.0f,  1.0f, 1.0f },
  };
  
  float CNPC_MetroPolice::AimBurstAtReactionTime( float flReactionTime, float flDistToTarget, float flCurrentSpeed )
  {
  	flReactionTime *= sk_metropolice_stitch_reaction.GetFloat();
  
  	if ( IsEnemyInAnAirboat() )
  	{
  		float u = flCurrentSpeed / AIM_AT_TIME_DELTA_SPEED;
  		float v = flDistToTarget / AIM_AT_TIME_DELTA_DIST;
  		int nu = (int)u;
  		int nv = (int)v;
  		if (( nu < AIM_AT_TIME_SPEED_COUNT - 1 ) && ( nv < AIM_AT_TIME_DIST_COUNT - 1 ))
  		{
  			float fu = u - nu;
  			float fv = v - nv;
  			float flReactionFactor = s_pReactionFraction[nv][nu] * (1.0f - fu) * (1.0f - fv);
  			flReactionFactor += s_pReactionFraction[nv+1][nu] * (1.0f - fu) * fv;
  			flReactionFactor += s_pReactionFraction[nv][nu+1] * fu * (1.0f - fv);
  			flReactionFactor += s_pReactionFraction[nv+1][nu+1] * fu * fv;
  
  			flReactionTime *= flReactionFactor;
  		}
  	}
  	
  	return flReactionTime;
  }
  
  
  //-----------------------------------------------------------------------------
  // Burst mode!
  //-----------------------------------------------------------------------------
  #define AIM_AT_SHOT_DELTA_SPEED 100.0f
  #define AIM_AT_SHOT_DELTA_DIST 500.0f
  #define AIM_AT_SHOT_SPEED_COUNT 6
  #define AIM_AT_SHOT_DIST_COUNT 6
  
  static int s_pShotCountFraction[AIM_AT_TIME_DIST_COUNT][AIM_AT_TIME_SPEED_COUNT] =
  {
  	{  3.0f, 3.0f,  2.5f,  1.5f,  1.0f, 0.0f },
  	{  3.0f, 3.0f,  2.5f,  1.25f, 0.5f, 0.0f },
  	{  2.5f, 2.5f,  2.0f,  1.0f,  0.0f, 0.0f },
  	{  2.0f, 2.0f,  1.5f,  0.5f,  0.0f, 0.0f },
  	{  1.0f, 1.0f,  1.0f,  0.5f,  0.0f, 0.0f },
  	{  0.0f, 0.0f,  0.0f,  0.0f,  0.0f, 0.0f },
  };
  
  int CNPC_MetroPolice::AimBurstAtSetupHitCount( float flDistToTarget, float flCurrentSpeed )
  {
  	// Max number of times we can hit the enemy
  	int nHitCount = sk_metropolice_stitch_at_hitcount.GetInt();
  	m_nMaxBurstHits = random->RandomInt( nHitCount, nHitCount + 1 );
  
  	if ( IsEnemyInAnAirboat() )
  	{
  		float u = flCurrentSpeed / AIM_AT_SHOT_DELTA_SPEED;
  		float v = flDistToTarget / AIM_AT_SHOT_DELTA_DIST;
  		int nu = (int)u;
  		int nv = (int)v;
  		if (( nu < AIM_AT_SHOT_SPEED_COUNT - 1 ) && ( nv < AIM_AT_SHOT_DIST_COUNT - 1 ))
  		{
  			float fu = u - nu;
  			float fv = v - nv;
  			float flShotFactor = s_pShotCountFraction[nv][nu] * (1.0f - fu) * (1.0f - fv);
  			flShotFactor += s_pShotCountFraction[nv+1][nu] * (1.0f - fu) * fv;
  			flShotFactor += s_pShotCountFraction[nv][nu+1] * fu * (1.0f - fv);
  			flShotFactor += s_pShotCountFraction[nv+1][nu+1] * fu * fv;
  
  			int nExtraShots = nHitCount * flShotFactor;
  			m_nMaxBurstHits += random->RandomInt( nExtraShots, nExtraShots + 1 );
  			return nExtraShots;
  		}
  	}
  
  	return 0;
  }
  
  
  //-----------------------------------------------------------------------------
  // Burst mode!
  //-----------------------------------------------------------------------------
  #define AIM_AT_DEFAULT_STITCH_SHOT_DIST	40.0f
  #define AIM_AT_SPEED_BONUS				200.0f
  #define AIM_AT_REACTION_TIME_FRACTION	0.8f
  #define AIM_AT_NEAR_REACTION_TIME_FRACTION	0.3f
  #define AIM_AT_STEER_DISTANCE			125.0f
  
  void CNPC_MetroPolice::AimBurstAtEnemy( float flReactionTime )
  {
  	if ( !IsCurrentlyFiringBurst() )
  		return;
  
  	Vector vecVelocity;
  	GetShootTarget()->GetVelocity( &vecVelocity, NULL );
  	float flCurrentSpeed = vecVelocity.Length();
  	float flDistToTargetSqr = GetShootTarget()->WorldSpaceCenter().AsVector2D().DistToSqr( Weapon_ShootPosition().AsVector2D() );
  	float flDistToTarget = sqrt(flDistToTargetSqr);
  
  	flReactionTime = AimBurstAtReactionTime( flReactionTime, flDistToTarget, flCurrentSpeed );
  
  	// We want a certain amount of reaction time before the shots hit the boat
  	int nShotCount = SetupBurstShotRegulator( flReactionTime );
  
  	bool bIsInVehicle = IsEnemyInAnAirboat();
  	if ( bIsInVehicle )
  	{
  		m_nBurstMode = BURST_LOCK_ON_AFTER_HIT;
  		m_flBurstSteerDistance = AIM_AT_STEER_DISTANCE;
  	}
  	else
  	{
  		m_nBurstMode = BURST_ACTIVE;
  		m_flBurstSteerDistance = 0;
  	}
  	m_nBurstSteerMode = BURST_STEER_WITHIN_LINE_OF_DEATH;
  
  	// Max number of times we can hit the enemy
  	int nExtraShots = AimBurstAtSetupHitCount( flDistToTarget, flCurrentSpeed );
  	float flExtraTime = GetTimeForShots( nExtraShots ) + (1.0f - AIM_AT_REACTION_TIME_FRACTION) * flReactionTime;
  	float flReactionFraction = 1.0f - flExtraTime / flReactionTime;
  	if ( flReactionFraction < 0.5f )
  	{
  		flReactionFraction = 0.5f;
  	}
  
  	float flFirstHitTime = flReactionTime * flReactionFraction;
  	Vector vecShootAt, vecShootAtVel;
  	PredictShootTargetPosition( flFirstHitTime, 0.0f, 0.0f, &vecShootAt, &vecShootAtVel );
  
  	Vector vecDelta;
  	VectorSubtract( vecShootAt, Weapon_ShootPosition(), vecDelta );
  	float flDistanceToTarget = vecDelta.Length();
  
  	// Always stitch horizontally...
  	vecDelta.z = 0.0f;
  
  	// The max stitch distance here is used to guarantee the cop doesn't try to lead
  	// the airboat so much that he ends up shooting behind himself
  	float flMaxStitchDistance = VectorNormalize( vecDelta );
  	flMaxStitchDistance -= 50.0f;
  	if ( flMaxStitchDistance < 0 )
  	{
  		flMaxStitchDistance = 0.0f;
  	}
  
  	float flStitchLength = nShotCount * AIM_AT_DEFAULT_STITCH_SHOT_DIST;
  
  	// Modify the stitch length based on distance from the shooter
  	flStitchLength += ComputeDistanceStitchModifier( flDistanceToTarget );
  
  	if ( bIsInVehicle )
  	{
  		// Make longer stitches if the enemy is going faster
  		Vector vecEnemyVelocity = GetShootTarget()->GetSmoothedVelocity();
  		if( (GetShootTarget()->GetFlags() & FL_ONGROUND) == 0 )
  		{
  			vecEnemyVelocity.z = 0.0f;
  		}
  
  		float flEnemySpeed = VectorNormalize( vecEnemyVelocity );
  		flStitchLength += AIM_AT_SPEED_BONUS * ( flEnemySpeed / 100.0f );
  
  		// Add in a little randomness across the direction of motion...
  		// Always put it on the side we're currently looking at
  		Vector vecAcross;
  		CrossProduct( vecEnemyVelocity, Vector( 0, 0, 1 ), vecAcross );
  		VectorNormalize( vecAcross );
  		
  		Vector eyeForward;
  		AngleVectors( GetEnemy()->EyeAngles(), &eyeForward );
  		if ( DotProduct( vecAcross, eyeForward ) < 0.0f )
  		{
  			vecAcross *= -1.0f;
  		}
  
  		float flMinAdd = RemapVal( flEnemySpeed, 0.0f, 200.0f, 70.0f, 30.0f );
  		VectorMA( vecShootAt, random->RandomFloat( flMinAdd, 100.0f ), vecAcross, vecShootAt );
  	}
  
  	// Compute the distance along the stitch direction to the cop. we don't want to cross that line
  	Vector vecStitchStart, vecStitchEnd;
  	VectorMA( vecShootAt, -MIN( flStitchLength * flReactionFraction, flMaxStitchDistance ), vecDelta, vecStitchStart );
  	VectorMA( vecShootAt, flStitchLength * (1.0f - flReactionFraction), vecDelta, vecStitchEnd );
  	
  	// Trace down a bit to hit the ground if we're above the ground...
  	trace_t	trace;
  	UTIL_TraceLine( vecStitchStart, vecStitchStart + Vector( 0, 0, -512 ), (MASK_SOLID_BRUSHONLY | MASK_WATER), NULL, COLLISION_GROUP_NONE, &trace );
  	m_vecBurstTargetPos = trace.endpos;
  	VectorSubtract( vecStitchEnd, m_vecBurstTargetPos, m_vecBurstDelta );
  
  	m_vecBurstLineOfDeathOrigin = m_vecBurstTargetPos;
  	m_vecBurstLineOfDeathDelta = m_vecBurstDelta;
  
  	m_vecBurstDelta /= (nShotCount - 1);
  
  //	VisualizeStitch( m_vecBurstTargetPos, vecStitchEnd );
  //	VisualizeLineOfDeath();
  }
  
  
  //-----------------------------------------------------------------------------
  // Burst mode!
  //-----------------------------------------------------------------------------
  #define AIM_IN_FRONT_OF_DEFAULT_STITCH_LENGTH		1000.0f
  #define AIM_IN_FRONT_OF_MINIMUM_DISTANCE			500.0f
  #define AIM_IN_FRONT_DRAW_LINE_OF_DEATH_FRACTION	0.5f
  #define AIM_IN_FRONT_STEER_DISTANCE					150.0f
  #define AIM_IN_FRONT_REACTION_FRACTION				0.8f
  #define AIM_IN_FRONT_EXTRA_VEL						200.0f
  
  void CNPC_MetroPolice::AimBurstInFrontOfEnemy( float flReactionTime )
  {
  	if ( !IsCurrentlyFiringBurst() )
  		return;
  
  	flReactionTime *= sk_metropolice_stitch_reaction.GetFloat();
  
  	// We want a certain amount of reaction time before the shots hit the boat
  	int nShotCount = SetupBurstShotRegulator( flReactionTime );
  
  	// Max number of times we can hit the player in the airboat
  	m_nMaxBurstHits = random->RandomInt( 3, 4 );
  	m_nBurstMode = BURST_LOCK_ON_AFTER_HIT;
  	m_nBurstSteerMode = BURST_STEER_WITHIN_LINE_OF_DEATH;
  
  	// The goal here is to slow him down. Choose a target position such that we predict
  	// where he'd be in he accelerated by N over the reaction time. Prevent him from getting there.
  	Vector vecShootAt, vecShootAtVel, vecAcross;
  	PredictShootTargetPosition( flReactionTime * AIM_IN_FRONT_REACTION_FRACTION, 
  		AIM_IN_FRONT_OF_MINIMUM_DISTANCE, 0.0f, &vecShootAt, &vecShootAtVel );
  
  	// Now add in some extra vel in a random direction + try to prevent that....
  	Vector vecTargetToGun, vecExtraDistance;
  	VectorSubtract( Weapon_ShootPosition(), vecShootAt, vecTargetToGun );
  	VectorNormalize( vecTargetToGun );
  	VectorNormalize( vecShootAtVel );
  	RandomDirectionBetweenVectors( vecShootAtVel, vecTargetToGun, &vecExtraDistance );
  	vecExtraDistance *= AIM_IN_FRONT_EXTRA_VEL;
  	vecShootAt += vecExtraDistance;
  
  	CrossProduct( vecExtraDistance, Vector( 0, 0, 1 ), vecAcross );
  	VectorNormalize( vecAcross );
  
  	float flStitchLength = AIM_IN_FRONT_OF_DEFAULT_STITCH_LENGTH;
  
  	Vector vecEndPoint1, vecEndPoint2;
  	VectorSubtract( Weapon_ShootPosition(), StitchAimTarget( GetAbsOrigin(), false ), vecTargetToGun );
  	float flSign = ( DotProduct( vecAcross, vecTargetToGun ) >= 0.0f ) ? 1.0f : -1.0f;
  	VectorMA( vecShootAt, flSign * flStitchLength * AIM_IN_FRONT_REACTION_FRACTION, vecAcross, vecEndPoint1 );
  	VectorMA( vecShootAt, -flSign * flStitchLength * (1.0f - AIM_IN_FRONT_REACTION_FRACTION), vecAcross, vecEndPoint2 );
  
  	m_vecBurstTargetPos = vecEndPoint1;
  	VectorSubtract( vecEndPoint2, vecEndPoint1, m_vecBurstDelta );
  
  	// This defines the line of death, which, when crossed, results in damage
  	m_vecBurstLineOfDeathOrigin = m_vecBurstTargetPos;
  	m_vecBurstLineOfDeathDelta = m_vecBurstDelta;
  	m_flBurstSteerDistance = AIM_IN_FRONT_STEER_DISTANCE;
  
  	// Make the visual representation of the line of death lie closest to the boat.
  	VectorMA( m_vecBurstTargetPos, -AIM_IN_FRONT_STEER_DISTANCE, vecShootAtVel, m_vecBurstTargetPos );
  	m_vecBurstDelta /= (nShotCount - 1);
  
  //	VisualizeStitch( m_vecBurstTargetPos, m_vecBurstTargetPos + m_vecBurstDelta * (nShotCount - 1) );
  //	VisualizeLineOfDeath();
  }
  
  
  //-----------------------------------------------------------------------------
  // Aim burst behind enemy
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::AimBurstBehindEnemy( float flShotTime )
  {
  	if ( !IsCurrentlyFiringBurst() )
  		return;
  
  	flShotTime *= sk_metropolice_stitch_reaction.GetFloat();
  
  	// We want a certain amount of reaction time before the shots hit the boat
  	int nShotCount = SetupBurstShotRegulator( flShotTime );
  
  	// Max number of times we can hit the player in the airboat
  	int nHitCount = sk_metropolice_stitch_behind_hitcount.GetInt();
  	m_nMaxBurstHits = random->RandomInt( nHitCount, nHitCount + 1 );
  	m_nBurstMode = BURST_LOCK_ON_AFTER_HIT;
  	m_nBurstSteerMode = BURST_STEER_WITHIN_LINE_OF_DEATH;
  
  	// Shoot across the enemy in between the enemy and me
  	Vector vecShootAt, vecShootAtVel, vecAcross;
  	PredictShootTargetPosition( 0.0f, 0.0f, 0.0f, &vecShootAt, &vecShootAtVel );
  
  	// Choose a point in between the shooter + the target
  	Vector vecDelta;
  	VectorSubtract( Weapon_ShootPosition(), vecShootAt, vecDelta );
  	vecDelta.z = 0.0f;
  	float flDistTo = VectorNormalize( vecDelta );
  	if ( flDistTo > AIM_BEHIND_MINIMUM_DISTANCE )
  	{
  		flDistTo = AIM_BEHIND_MINIMUM_DISTANCE;
  	}
  	VectorMA( vecShootAt, flDistTo, vecDelta, vecShootAt );
  	CrossProduct( vecDelta, Vector( 0, 0, 1 ), vecAcross );
  
  	float flStitchLength = AIM_BEHIND_DEFAULT_STITCH_LENGTH;
  
  	Vector vecEndPoint1, vecEndPoint2;
  	VectorMA( vecShootAt, -flStitchLength * 0.5f, vecAcross, vecEndPoint1 );
  	VectorMA( vecShootAt, flStitchLength * 0.5f, vecAcross, vecEndPoint2 );
  	
  	m_vecBurstTargetPos = vecEndPoint1;
  	VectorSubtract( vecEndPoint2, vecEndPoint1, m_vecBurstDelta );
  
  	// This defines the line of death, which, when crossed, results in damage
  	m_vecBurstLineOfDeathOrigin = m_vecBurstTargetPos;
  	m_vecBurstLineOfDeathDelta = m_vecBurstDelta;
  	m_flBurstSteerDistance = AIM_BEHIND_STEER_DISTANCE;
  
  	// Make the visual representation of the line of death lie closest to the boat.
  	VectorMA( m_vecBurstTargetPos, -AIM_BEHIND_STEER_DISTANCE, vecDelta, m_vecBurstTargetPos );
  	m_vecBurstDelta /= (nShotCount - 1);
  
  //	VisualizeStitch( m_vecBurstTargetPos, m_vecBurstTargetPos + m_vecBurstDelta * (nShotCount - 1) );
  //	VisualizeLineOfDeath();
  }
  
  
  //-----------------------------------------------------------------------------
  // Burst mode!
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::AimBurstAlongSideOfEnemy( float flFollowTime )
  {
  	if ( !IsCurrentlyFiringBurst() )
  		return;
  
  	flFollowTime *= sk_metropolice_stitch_reaction.GetFloat();
  
  	// We want a certain amount of reaction time before the shots hit the boat
  	int nShotCount = SetupBurstShotRegulator( flFollowTime );
  
  	// Max number of times we can hit the player in the airboat
  	int nHitCount = sk_metropolice_stitch_along_hitcount.GetInt();
  	m_nMaxBurstHits = random->RandomInt( nHitCount, nHitCount + 1 );
  	m_nBurstMode = BURST_LOCK_ON_AFTER_HIT;
  	m_nBurstSteerMode = BURST_STEER_WITHIN_LINE_OF_DEATH;
  
  	Vector vecShootAt, vecShootAtVel, vecAcross;
  	PredictShootTargetPosition( AIM_ALONG_SIDE_LINE_OF_DEATH_LEAD_TIME, 225.0f, 0.0f, &vecShootAt, &vecShootAtVel );
  	CrossProduct( vecShootAtVel, Vector( 0, 0, 1 ), vecAcross );
  	VectorNormalize( vecAcross );
  
  	// Choose the side of the vehicle which is closer to the shooter
  	Vector vecSidePoint;		
  	Vector vecTargetToGun;
  	VectorSubtract( Weapon_ShootPosition(), vecShootAt, vecTargetToGun );
  	float flSign = ( DotProduct( vecTargetToGun, vecAcross ) > 0.0f ) ? 1.0f : -1.0f;
  	float flDist = AIM_ALONG_SIDE_LINE_OF_DEATH_DISTANCE + random->RandomFloat( 0.0f, 50.0f );
  	VectorMA( vecShootAt, flSign * flDist, vecAcross, vecSidePoint );
  
  	vecShootAtVel.z = 0.0f;
  	float flTargetSpeed = VectorNormalize( vecShootAtVel );
  	float flStitchLength = MAX( AIM_IN_FRONT_OF_DEFAULT_STITCH_LENGTH, flTargetSpeed * flFollowTime * 0.9 );
  
  	// This defines the line of death, which, when crossed, results in damage
  	m_vecBurstLineOfDeathOrigin = vecSidePoint;
  	VectorMultiply( vecShootAtVel, flStitchLength, m_vecBurstLineOfDeathDelta );
  
  	// Pull the endpoint a little toward the NPC firing it...
  	float flExtraDist = random->RandomFloat( 25.0f, 50.0f );
  	VectorNormalize( vecTargetToGun );
  	if ( flSign * DotProduct( vecTargetToGun, vecShootAtVel ) < 0.1f )
  	{
  		flExtraDist += 100.0f;
  	}
  	VectorMA( m_vecBurstLineOfDeathDelta, flSign * flExtraDist, vecAcross, m_vecBurstLineOfDeathDelta );
  
  	m_flBurstSteerDistance = AIM_ALONG_SIDE_STEER_DISTANCE;
  	m_vecBurstDelta = m_vecBurstLineOfDeathDelta;
  	m_vecBurstTargetPos = m_vecBurstLineOfDeathOrigin;
  
  	// Make the visual representation of the line of death lie closest to the boat.
  	VectorMA( m_vecBurstTargetPos, -flSign * AIM_ALONG_SIDE_STEER_DISTANCE, vecAcross, m_vecBurstTargetPos );
  	m_vecBurstDelta /= (nShotCount - 1);
  
  //	VisualizeStitch( m_vecBurstTargetPos, m_vecBurstTargetPos + m_vecBurstDelta * (nShotCount - 1) );
  //	VisualizeLineOfDeath();
  }
  
  
  //-----------------------------------------------------------------------------
  // Different burst steering modes
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::SteerBurstTowardTargetUseSpeedOnly( const Vector &vecShootAt, 
  	const Vector &vecShootAtVelocity, float flPredictTime, int nShotsTillPredict )
  {
  	// Only account for changes in *speed*; ignore all changes in velocity direction, etc.
  	// This one only hits the player if there is *no* steering, just acceleration or decceleration
  	Vector vecBurstDir = m_vecBurstPredictedVelocityDir;
  	float flActualSpeed = DotProduct( vecShootAtVelocity, vecBurstDir );
  
  	vecBurstDir *= (flActualSpeed - m_vecBurstPredictedSpeed) * flPredictTime;
  	vecBurstDir /= (nShotsTillPredict - 1);
  
  	m_vecBurstPredictedSpeed = flActualSpeed; 
  	m_vecBurstDelta += vecBurstDir;
  }
  
  void CNPC_MetroPolice::SteerBurstTowardTargetUseVelocity( const Vector &vecShootAt, const Vector &vecShootAtVelocity, int nShotsTillPredict )
  {
  	// Only account for all velocity changes
  	// This one looks scary in that it always gets near to the player,
  	// but it never usually hits actually.
  	Vector vecBurstDir = m_vecBurstLineOfDeathDelta;
  	m_vecBurstLineOfDeathDelta = vecShootAtVelocity;
  	vecBurstDir = vecShootAtVelocity - vecBurstDir;
  	vecBurstDir /= (nShotsTillPredict - 1);
  
  	m_vecBurstDelta += vecBurstDir;
  }
  
  void CNPC_MetroPolice::SteerBurstTowardTargetUsePosition( const Vector &vecShootAt, const Vector &vecShootAtVelocity, int nShotsTillPredict )
  {
  	// Account for velocity + position changes
  	// This method *always* hits
  	VectorSubtract( vecShootAt, m_vecBurstTargetPos, m_vecBurstDelta );
  	m_vecBurstDelta /= (nShotsTillPredict - 1);
  }
  
  void CNPC_MetroPolice::SteerBurstTowardPredictedPoint( const Vector &vecShootAt, const Vector &vecShootAtVelocity, int nShotsTillPredict )
  {
  	// Account for velocity + position changes, but only within a constrained cylinder
  	Vector vecConstrainedShootPosition;
  	CalcClosestPointOnLine( vecShootAt, m_vecBurstLineOfDeathOrigin, m_vecBurstLineOfDeathOrigin + m_vecBurstLineOfDeathDelta, vecConstrainedShootPosition );
  
  	Vector vecDelta;
  	VectorSubtract( vecShootAt, vecConstrainedShootPosition, vecDelta );
  	if ( vecDelta.LengthSqr( ) <= m_flBurstSteerDistance * m_flBurstSteerDistance )
  	{
  		vecConstrainedShootPosition = vecShootAt;
  	}
  	else
  	{
  		VectorNormalize( vecDelta );
  		VectorMA( vecConstrainedShootPosition, m_flBurstSteerDistance, vecDelta, vecConstrainedShootPosition );
  	}
  
  	// This method *always* hits if the entity is within the cylinder
  	VectorSubtract( vecConstrainedShootPosition, m_vecBurstTargetPos, m_vecBurstDelta );
  	if ( nShotsTillPredict >= 2 )
  	{
  		m_vecBurstDelta /= (nShotsTillPredict - 1);
  	}
  }
  
  #define STEER_LINE_OF_DEATH_MAX_DISTANCE	250.0f
  
  void CNPC_MetroPolice::SteerBurstWithinLineOfDeath( )
  {
  	// Account for velocity + position changes, but only within a constrained cylinder
  	Vector vecShootAt;
  	vecShootAt = StitchAimTarget( GetAbsOrigin(), false );
  
  	// If the target close to the current point the shot is on,
  	// move the shot toward the point
  	Vector vecPointOnLineOfDeath;
  	CalcClosestPointOnLine( m_vecBurstTargetPos, m_vecBurstLineOfDeathOrigin, m_vecBurstLineOfDeathOrigin + m_vecBurstLineOfDeathDelta, vecPointOnLineOfDeath );
  
  	Vector vecDelta;
  	VectorSubtract( vecShootAt, vecPointOnLineOfDeath, vecDelta );
  	if ( vecDelta.LengthSqr( ) <= m_flBurstSteerDistance * m_flBurstSteerDistance )
  	{
  		VectorSubtract( vecShootAt, m_vecBurstTargetPos, m_vecBurstDelta );
  		if ( m_vecBurstDelta.LengthSqr() > (STEER_LINE_OF_DEATH_MAX_DISTANCE * STEER_LINE_OF_DEATH_MAX_DISTANCE) )
  		{
  			VectorNormalize( m_vecBurstDelta );
  			m_vecBurstDelta *= STEER_LINE_OF_DEATH_MAX_DISTANCE;
  		}
  	}
  	else
  	{
  		// Just make the burst go back and forth alont the line of death...
  		Vector vecNext = m_vecBurstTargetPos + m_vecBurstDelta;
  
  		float t;
  		CalcClosestPointOnLine( vecNext, m_vecBurstLineOfDeathOrigin, m_vecBurstLineOfDeathOrigin + m_vecBurstLineOfDeathDelta, vecPointOnLineOfDeath, &t );
  		if (( t < -0.1f ) || ( t > 1.1f ))
  		{
  			m_vecBurstDelta *= -1.0f;
  
  			// This is necessary to make it not look like a machine is firing the gun
  			Vector vecBurstDir = m_vecBurstDelta;
  			float flLength = VectorNormalize( vecBurstDir );
  			vecBurstDir *= random->RandomFloat( -flLength * 0.5f, flLength * 0.5f );
  
  			m_vecBurstTargetPos += vecBurstDir;
  		}
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Burst mode!
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::SteerBurstTowardTarget( )
  {
  	switch ( m_nBurstSteerMode )
  	{
  	case BURST_STEER_NONE:
  		return;
  
  	case BURST_STEER_EXACTLY_TOWARD_TARGET:
  		// Necessary to get the cop looking at the target
  		m_vecBurstTargetPos = GetEnemy()->WorldSpaceCenter();
  		return;
   
  	case BURST_STEER_ADJUST_FOR_SPEED_CHANGES:
  		{
  			// Predict the airboat position at the point where we were expecting to hit them
  			if ( m_flBurstPredictTime <= gpGlobals->curtime )
  				return;
  
  			float flPredictTime = m_flBurstPredictTime - gpGlobals->curtime;
  			int nShotsTillPredict = CountShotsInTime( flPredictTime );
  			if ( nShotsTillPredict <= 1 )
  				return;
  
  			Vector vecShootAt, vecShootAtVelocity;
  			PredictShootTargetPosition( flPredictTime, 0.0f, 0.0f, &vecShootAt, &vecShootAtVelocity );
  			SteerBurstTowardTargetUseSpeedOnly( vecShootAt, vecShootAtVelocity, flPredictTime, nShotsTillPredict );
  		}
  		break;
  
  	case BURST_STEER_TOWARD_PREDICTED_POINT:
  		// Don't course-correct until the predicted time
  		if ( m_flBurstPredictTime >= gpGlobals->curtime )
  			return;
  
  		// fall through!
  
  	case BURST_STEER_WITHIN_LINE_OF_DEATH:
  		break;
  	}
  
  	SteerBurstWithinLineOfDeath( );
  }
  
  
  //-----------------------------------------------------------------------------
  // Various burst trajectory methods
  //-----------------------------------------------------------------------------
  Vector CNPC_MetroPolice::ComputeBurstLockOnTrajectory( const Vector &shootOrigin )
  {
  	Vector vecTrajectory;
  	VectorSubtract( GetEnemy()->WorldSpaceCenter(), shootOrigin, vecTrajectory );
  	VectorNormalize( vecTrajectory );
  	return vecTrajectory;
  }
  
  Vector CNPC_MetroPolice::ComputeBurstDeliberatelyMissTrajectory( const Vector &shootOrigin )
  {
  	m_vecBurstTargetPos.z += 8.0f;
  
  	Vector vecTrajectory;
  	VectorSubtract( m_vecBurstTargetPos, shootOrigin, vecTrajectory );
  	VectorNormalize( vecTrajectory );
  	return vecTrajectory;
  }
  
  Vector CNPC_MetroPolice::ComputeBurstTrajectory( const Vector &shootOrigin )
  {
  	// Perform the stitch
  	Vector vecPos = m_vecBurstTargetPos;
  
  	// For players, don't let them jump over the burst.
  	CBaseEntity *pEnemy = GetEnemy();
  	bool bIsPlayerOnFoot = pEnemy && pEnemy->IsPlayer() && !IsEnemyInAnAirboat();
  	if ( bIsPlayerOnFoot )
  	{
  		Vector vecNormalizedPt;
  		pEnemy->CollisionProp()->WorldToNormalizedSpace( vecPos, &vecNormalizedPt );
  		if ( (vecNormalizedPt.x >= -0.1f) && (vecNormalizedPt.x <= 1.1f) &&
  			(vecNormalizedPt.y >= -0.1f) && (vecNormalizedPt.y <= 1.1f) &&
  			(vecNormalizedPt.z >= -0.7f) && (vecNormalizedPt.z < 1.1f) )
  		{
   			vecPos.z = pEnemy->WorldSpaceCenter().z;
  		}
  	}
  
  	vecPos -= shootOrigin;
  
  	// Add a little noise. Even though it's non-physical, it looks better
  	// to have the same amount of noise regardless of distance from the shooter
  	// Always make the noise perpendicular to the burst direction
  	float flNoise = bIsPlayerOnFoot ? 16.0f : 32.0f;
  	Vector vecNoise;
  	CrossProduct( m_vecBurstDelta, Vector( 0, 0, 1 ), vecNoise );
  	VectorNormalize( vecNoise );
  	vecNoise *= random->RandomFloat( -flNoise, flNoise );
  	vecPos += vecNoise;
  
  	VectorNormalize( vecPos );
  
  	// X360BUG: Was causing compiler crash in release, still?
  //	if ( IsPC() )
  	{
  		// Allow for steering towards the target.
  		SteerBurstTowardTarget();
  	}
  	
  	// Update the burst target position
  	m_vecBurstTargetPos += m_vecBurstDelta;
  	
  //	NDebugOverlay::Cross3D( m_vecBurstTargetPos, -Vector(32,32,32), Vector(32,32,32), 255, 0, 255, false, 1.0f );
  
  	return vecPos;
  }
  
  
  //-----------------------------------------------------------------------------
  // Deliberately aims as close as possible w/o hitting
  //-----------------------------------------------------------------------------
  Vector CNPC_MetroPolice::AimCloseToTargetButMiss( CBaseEntity *pTarget, const Vector &shootOrigin )
  {
  	Vector vecNormalizedSpace;
  	pTarget->CollisionProp()->WorldToNormalizedSpace( shootOrigin, &vecNormalizedSpace );
  	vecNormalizedSpace -= Vector( 0.5f, 0.5f, 0.5f );
  	float flDist = VectorNormalize( vecNormalizedSpace );
  	float flMinRadius = flDist * sqrt(3.0) / sqrt( flDist * flDist - 3 );
  
  	// Choose random points in a plane perpendicular to the shoot origin.
  	Vector vecRandomDir;
  	vecRandomDir.Random( -1.0f, 1.0f );
  	VectorMA( vecRandomDir, -DotProduct( vecNormalizedSpace, vecRandomDir ), vecNormalizedSpace, vecRandomDir );
  	VectorNormalize( vecRandomDir );
  	vecRandomDir *= flMinRadius;
  
  	vecRandomDir *= 0.5f;
  	vecRandomDir += Vector( 0.5f, 0.5f, 0.5f );
  
  	Vector vecBodyTarget;
  	pTarget->CollisionProp()->NormalizedToWorldSpace( vecRandomDir, &vecBodyTarget );
  	vecBodyTarget -= shootOrigin;
  	return vecBodyTarget;
  }
  
  
  //-----------------------------------------------------------------------------
  // A burst that goes right at the enemy
  //-----------------------------------------------------------------------------
  #define MIN_TIGHT_BURST_DIST 1000.0f
  #define MAX_TIGHT_BURST_DIST 2000.0f
  
  Vector CNPC_MetroPolice::ComputeTightBurstTrajectory( const Vector &shootOrigin )
  {
  	CBaseEntity *pEnemy = GetEnemy();
  	if ( !pEnemy )
  	{
  		return BaseClass::GetActualShootTrajectory( shootOrigin );
  	}
  
  	// Aim around the player...
  	if ( m_nBurstHits >= m_nMaxBurstHits )
  	{
  		return AimCloseToTargetButMiss( pEnemy, shootOrigin );
  	}
  
  	float flDist = shootOrigin.DistTo( pEnemy->WorldSpaceCenter() );
  	float flMin = -0.2f;
  	float flMax = 1.2f;
  	if ( flDist > MIN_TIGHT_BURST_DIST )
  	{
  		flDist = clamp( flDist, MIN_TIGHT_BURST_DIST, MAX_TIGHT_BURST_DIST );
  		flMin = SimpleSplineRemapVal( flDist, MIN_TIGHT_BURST_DIST, MAX_TIGHT_BURST_DIST, -0.2f, -0.7f );
  		flMax = SimpleSplineRemapVal( flDist, MIN_TIGHT_BURST_DIST, MAX_TIGHT_BURST_DIST, 1.2f, 1.7f );
  	}
  
  	// Aim randomly at the player. Since body target uses the vehicle body target,
  	// we instead are going to not use it
  	Vector vecBodyTarget;
  	pEnemy->CollisionProp()->RandomPointInBounds( Vector( flMin, flMin, flMin ), Vector( flMax, flMax, flMax * 0.75f ), &vecBodyTarget );
  	vecBodyTarget -= shootOrigin;
  	return vecBodyTarget;
  }
  
  
  //-----------------------------------------------------------------------------
  // Burst mode!
  //-----------------------------------------------------------------------------
  Vector CNPC_MetroPolice::GetActualShootTrajectory( const Vector &shootOrigin )
  {
  	switch ( m_nBurstMode )
  	{
  	case BURST_NOT_ACTIVE:
  		return BaseClass::GetActualShootTrajectory( shootOrigin );
  
  	case BURST_LOCKED_ON:
  		if ( m_nBurstHits < m_nMaxBurstHits )
  		{
  			return ComputeBurstLockOnTrajectory( shootOrigin );
  		}
  
  		// Start shooting over the head of the enemy
  		GetShootTarget()->CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.5f, 1.0f ), &m_vecBurstTargetPos );
  		m_nBurstMode = BURST_DELIBERATELY_MISS;
  		// NOTE: Fall through to BURST_DELIBERATELY_MISS!!
  
  	case BURST_DELIBERATELY_MISS:
  		return ComputeBurstDeliberatelyMissTrajectory( shootOrigin );
  
  	case BURST_LOCK_ON_AFTER_HIT:
  		// See if our target is within the bounds of the enemy
  		if ( GetShootTarget()->CollisionProp()->IsPointInBounds( m_vecBurstTargetPos ) )
  		{
  			// Now raytrace against only the world + (good for cops on bridges)
  			trace_t tr;
  			CTraceFilterWorldOnly traceFilter;
  			UTIL_TraceLine( Weapon_ShootPosition(), m_vecBurstTargetPos, MASK_SOLID, &traceFilter, &tr );
  			if ( tr.fraction == 1.0f )
  			{
  				m_nBurstMode = BURST_LOCKED_ON;
  			}
  		}
  		// NOTE: Fall through to BURST_ACTIVE!
  
  	case BURST_ACTIVE:
  		// Stitch toward the target, we haven't hit it yet
  		return ComputeBurstTrajectory( shootOrigin );
  
  	case BURST_TIGHT_GROUPING:
  		// This one goes right at the enemy
  		return ComputeTightBurstTrajectory( shootOrigin );
  	}
  
  	Assert(0);
  	return vec3_origin;
  }
  
  
  //-----------------------------------------------------------------------------
  // Burst mode!
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::FireBullets( const FireBulletsInfo_t &info )
  {
  	CBaseEntity *pEnemy = GetEnemy();
  	bool bIsPlayer = pEnemy && pEnemy->IsPlayer();
  	if ( bIsPlayer && IsCurrentlyFiringBurst() )
  	{
  		FireBulletsInfo_t actualInfo = info;
  		if ( m_nBurstHits < m_nMaxBurstHits )
  		{
  			CBasePlayer *pPlayer = assert_cast<CBasePlayer*>(pEnemy);
  
  			// This makes it so that if the player gets hit underwater, 
  			// he won't take damage if his viewpoint is above water.
  			if ( !IsEnemyInAnAirboat() && ( pPlayer->GetWaterLevel() != 3 ) )
  			{
  				actualInfo.m_nFlags |= FIRE_BULLETS_DONT_HIT_UNDERWATER;
  			}
  
  			// This test is here to see if we've damaged the player
  			int nPrevHealth = pPlayer->GetHealth();
  			int nPrevArmor = pPlayer->ArmorValue();
  
  			BaseClass::FireBullets( actualInfo );
  
  			if (( pPlayer->GetHealth() < nPrevHealth ) || ( pPlayer->ArmorValue() < nPrevArmor ))
  			{
  				++m_nBurstHits;
  			}
  		}
  		else
  		{
  			actualInfo.m_pAdditionalIgnoreEnt = pEnemy;
  			BaseClass::FireBullets( actualInfo ); 
  		}
  	}
  	else
  	{
  		BaseClass::FireBullets( info );
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Behaviors! Lovely behaviors
  //-----------------------------------------------------------------------------
  bool CNPC_MetroPolice::CreateBehaviors()
  {
  	AddBehavior( &m_RappelBehavior );
  	AddBehavior( &m_FollowBehavior );
  	AddBehavior( &m_PolicingBehavior );
  	AddBehavior( &m_ActBusyBehavior );
  	AddBehavior( &m_AssaultBehavior );
  	AddBehavior( &m_StandoffBehavior );
  	AddBehavior( &m_FuncTankBehavior );
  	
  	return BaseClass::CreateBehaviors();
  }
  
  void CNPC_MetroPolice::InputEnableManhackToss( inputdata_t &inputdata )
  {
  	if ( HasSpawnFlags( SF_METROPOLICE_NO_MANHACK_DEPLOY ) )
  	{
  		RemoveSpawnFlags( SF_METROPOLICE_NO_MANHACK_DEPLOY );
  	}
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  // Input  : &inputdata - 
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::InputSetPoliceGoal( inputdata_t &inputdata )
  {
  	CBaseEntity *pGoal = gEntList.FindEntityByName( NULL, inputdata.value.String() );
  
  	if ( pGoal == NULL )
  	{
  		DevMsg( "SetPoliceGoal: %s (%s) unable to find ai_goal_police: %s\n", GetClassname(), GetDebugName(), inputdata.value.String() );
  		return;
  	}
  
  	CAI_PoliceGoal *pPoliceGoal = dynamic_cast<CAI_PoliceGoal *>(pGoal);
  
  	if ( pPoliceGoal == NULL )
  	{
  		DevMsg( "SetPoliceGoal: %s (%s)'s target %s is not an ai_goal_police entity!\n", GetClassname(), GetDebugName(), inputdata.value.String() );
  		return;
  	}
  
  	m_PolicingBehavior.Enable( pPoliceGoal );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  // Input  : &inputdata - 
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::InputActivateBaton( inputdata_t &inputdata )
  {
  	SetBatonState( inputdata.value.Bool() );
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::AlertSound( void )
  {
  	m_Sentences.Speak( "METROPOLICE_GO_ALERT" );
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::DeathSound( const CTakeDamageInfo &info )
  {
  	if ( IsOnFire() )
  		return;
  
  	m_Sentences.Speak( "METROPOLICE_DIE", SENTENCE_PRIORITY_INVALID, SENTENCE_CRITERIA_ALWAYS );
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: implemented by subclasses to give them an opportunity to make
  //			a sound when they lose their enemy
  // Input  :
  // Output :
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::LostEnemySound( void)
  {
  	// Don't announce enemies when the player isn't a criminal
  	if ( !PlayerIsCriminal() )
  		return;
  
  	if ( gpGlobals->curtime <= m_flNextLostSoundTime )
  		return;
  
  	const char *pSentence;
  	if (!(CBaseEntity*)GetEnemy() || gpGlobals->curtime - GetEnemyLastTimeSeen() > 10)
  	{
  		pSentence = "METROPOLICE_LOST_LONG"; 
  	}
  	else
  	{
  		pSentence = "METROPOLICE_LOST_SHORT";
  	}
  
  	if ( m_Sentences.Speak( pSentence ) >= 0 )
  	{
  		m_flNextLostSoundTime = gpGlobals->curtime + random->RandomFloat(5.0,15.0);
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: implemented by subclasses to give them an opportunity to make
  //			a sound when they lose their enemy
  // Input  :
  // Output :
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::FoundEnemySound( void)
  {
  	// Don't announce enemies when I'm in arrest behavior
  	if ( HasSpawnFlags( SF_METROPOLICE_ARREST_ENEMY ) )
  		return;
  
  	m_Sentences.Speak( "METROPOLICE_REFIND_ENEMY", SENTENCE_PRIORITY_HIGH );
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: Indicates whether or not this npc should play an idle sound now.
  //-----------------------------------------------------------------------------
  bool CNPC_MetroPolice::ShouldPlayIdleSound( void )
  {
  	// If someone is waiting for a response, then respond!
  	if ( ( m_NPCState == NPC_STATE_IDLE ) || ( m_NPCState == NPC_STATE_ALERT ) )
  	{
  		if ( m_nIdleChatterType >= METROPOLICE_CHATTER_RESPONSE )
  			return FOkToMakeSound();
  	}
  
  	return BaseClass::ShouldPlayIdleSound();
  }
  
  
  //-----------------------------------------------------------------------------
  // IdleSound 
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::IdleSound( void )
  {
  	bool bIsCriminal = PlayerIsCriminal();
  
  	// This happens when the NPC is waiting for his buddies to respond to him
  	switch( m_nIdleChatterType )
  	{
  	case METROPOLICE_CHATTER_WAIT_FOR_RESPONSE:
  		break;
  
  	case METROPOLICE_CHATTER_ASK_QUESTION:
  		{
  			if ( m_bPlayerIsNear && !HasMemory(bits_MEMORY_PLAYER_HARASSED) )
  			{
  				if ( m_Sentences.Speak( "METROPOLICE_IDLE_HARASS_PLAYER", SENTENCE_PRIORITY_NORMAL, SENTENCE_CRITERIA_NORMAL ) >= 0 )
  				{
  					Remember( bits_MEMORY_PLAYER_HARASSED );
  					if ( GetSquad() )
  					{
  						GetSquad()->SquadRemember(bits_MEMORY_PLAYER_HARASSED);
  					}
  				}
  				return;
  			}
  
  			if ( !random->RandomInt(0,1) )
  				break;
  
  			int nQuestionType = random->RandomInt( 0, METROPOLICE_CHATTER_RESPONSE_TYPE_COUNT );
  			if ( !IsInSquad() || ( nQuestionType == METROPOLICE_CHATTER_RESPONSE_TYPE_COUNT ) )
  			{
  				m_Sentences.Speak( bIsCriminal ? "METROPOLICE_IDLE_CR" : "METROPOLICE_IDLE" );
  				break;
  			}
  
  			static const char *pQuestion[2][METROPOLICE_CHATTER_RESPONSE_TYPE_COUNT] = 
  			{
  				{ "METROPOLICE_IDLE_CHECK",		"METROPOLICE_IDLE_QUEST" },
  				{ "METROPOLICE_IDLE_CHECK_CR",	"METROPOLICE_IDLE_QUEST_CR" },
  			};
  
  			if ( m_Sentences.Speak( pQuestion[bIsCriminal][nQuestionType] ) >= 0 )
  			{
  				GetSquad()->BroadcastInteraction( g_interactionMetrocopIdleChatter, (void*)(METROPOLICE_CHATTER_RESPONSE + nQuestionType), this );
  				m_nIdleChatterType = METROPOLICE_CHATTER_WAIT_FOR_RESPONSE;
  			}
  		}
  		break;
  
  	default:
  		{
  			int nResponseType = m_nIdleChatterType - METROPOLICE_CHATTER_RESPONSE;
  
  			static const char *pResponse[2][METROPOLICE_CHATTER_RESPONSE_TYPE_COUNT] = 
  			{
  				{ "METROPOLICE_IDLE_CLEAR",		"METROPOLICE_IDLE_ANSWER" },
  				{ "METROPOLICE_IDLE_CLEAR_CR",	"METROPOLICE_IDLE_ANSWER_CR" },
  			};
  
  			if ( m_Sentences.Speak( pResponse[bIsCriminal][nResponseType] ) >= 0 )
  			{
  				GetSquad()->BroadcastInteraction( g_interactionMetrocopIdleChatter, (void*)(METROPOLICE_CHATTER_ASK_QUESTION), this );
  				m_nIdleChatterType = METROPOLICE_CHATTER_ASK_QUESTION;
  			}
  		}
  		break;
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::PainSound( const CTakeDamageInfo &info )
  {
  	if ( gpGlobals->curtime < m_flNextPainSoundTime )
  		return;
  
  	// Don't make pain sounds if I'm on fire. The looping sound will take care of that for us.
  	if ( IsOnFire() )
  		return;
  
  	float healthRatio = (float)GetHealth() / (float)GetMaxHealth();
  	if ( healthRatio > 0.0f )
  	{
  		const char *pSentenceName = "METROPOLICE_PAIN";
  		if ( !HasMemory(bits_MEMORY_PAIN_HEAVY_SOUND) && (healthRatio < 0.25f) )
  		{
  			Remember( bits_MEMORY_PAIN_HEAVY_SOUND | bits_MEMORY_PAIN_LIGHT_SOUND );
  			pSentenceName = "METROPOLICE_PAIN_HEAVY";
  		}
  		else if ( !HasMemory(bits_MEMORY_PAIN_LIGHT_SOUND) && healthRatio > 0.8f )
  		{
  			Remember( bits_MEMORY_PAIN_LIGHT_SOUND );
  			pSentenceName = "METROPOLICE_PAIN_LIGHT";
  		}
  		
  		// This causes it to speak it no matter what; doesn't bother with setting sounds.
  		m_Sentences.Speak( pSentenceName, SENTENCE_PRIORITY_INVALID, SENTENCE_CRITERIA_ALWAYS );
  		m_flNextPainSoundTime = gpGlobals->curtime + 1;
  	}
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  int CNPC_MetroPolice::GetSoundInterests( void )
  {
  	return SOUND_WORLD | SOUND_COMBAT | SOUND_PLAYER | SOUND_PLAYER_VEHICLE | SOUND_DANGER | 
  		SOUND_PHYSICS_DANGER | SOUND_BULLET_IMPACT | SOUND_MOVE_AWAY;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  float CNPC_MetroPolice::MaxYawSpeed( void )
  {
  	switch( GetActivity() )
  	{
  	case ACT_TURN_LEFT:
  	case ACT_TURN_RIGHT:
  		return 120;
  
  	case ACT_RUN:
  	case ACT_RUN_HURT:
  		return 15;
  
  	case ACT_WALK:
  	case ACT_WALK_CROUCH:
  	case ACT_RUN_CROUCH:
  		return 25;
  
  	default:
  		return 45;
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //
  //
  //-----------------------------------------------------------------------------
  Class_T	CNPC_MetroPolice::Classify ( void )
  {
  	return CLASS_METROPOLICE;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  // Output : Returns true on success, false on failure.
  //-----------------------------------------------------------------------------
  bool CNPC_MetroPolice::PlayerIsCriminal( void )
  {
  	if ( m_PolicingBehavior.IsEnabled() && m_PolicingBehavior.TargetIsHostile() )
  		return true;
  
  	if ( GlobalEntity_GetState( "gordon_precriminal" ) == GLOBAL_ON )
  		return false;
  
  	return true;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Overridden because if the player is a criminal, we hate them.
  // Input  : pTarget - Entity with which to determine relationship.
  // Output : Returns relationship value.
  //-----------------------------------------------------------------------------
  Disposition_t CNPC_MetroPolice::IRelationType(CBaseEntity *pTarget)
  {
  	Disposition_t disp = BaseClass::IRelationType(pTarget);
  
  	if ( pTarget == NULL )
  		return disp;
  
  	// If the player's not a criminal, then we don't necessary hate him
  	if ( pTarget->Classify() == CLASS_PLAYER )
  	{
  		if ( !PlayerIsCriminal() && (disp == D_HT) )
  		{
  			// If we're pissed at the player, we're allowed to hate them.
  			if ( m_flChasePlayerTime && m_flChasePlayerTime > gpGlobals->curtime )
  				return D_HT;
  			return D_NU;
  		}
  	}
  
  	return disp;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  // Input  : *pEvent - 
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::OnAnimEventStartDeployManhack( void )
  {
  	Assert( m_iManhacks );
  	
  	if ( m_iManhacks <= 0 )
  	{
  		DevMsg( "Error: Throwing manhack but out of manhacks!\n" );
  		return;
  	}
  
  	m_iManhacks--;
  
  	// Turn off the manhack on our body
  	if ( m_iManhacks <= 0 )
  	{
  		SetBodygroup( METROPOLICE_BODYGROUP_MANHACK, false );
  	}
  
  	// Create the manhack to throw
  	CNPC_Manhack *pManhack = (CNPC_Manhack *)CreateEntityByName( "npc_manhack" );
  	
  	Vector	vecOrigin;
  	QAngle	vecAngles;
  
  	int handAttachment = LookupAttachment( "LHand" );
  	GetAttachment( handAttachment, vecOrigin, vecAngles );
  
  	pManhack->SetLocalOrigin( vecOrigin );
  	pManhack->SetLocalAngles( vecAngles );
  	pManhack->AddSpawnFlags( (SF_MANHACK_PACKED_UP|SF_MANHACK_CARRIED|SF_NPC_WAIT_FOR_SCRIPT) );
  	
  	// Also fade if our parent is marked to do it
  	if ( HasSpawnFlags( SF_NPC_FADE_CORPSE ) )
  	{
  		pManhack->AddSpawnFlags( SF_NPC_FADE_CORPSE );
  	}
  
  	pManhack->Spawn();
  
  	// Make us move with his hand until we're deployed
  	pManhack->SetParent( this, handAttachment );
  
  	m_hManhack = pManhack;
  }
  
  //-----------------------------------------------------------------------------
  // Anim event handlers
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::OnAnimEventDeployManhack( animevent_t *pEvent )
  {
  	// Let it go
  	ReleaseManhack();
  
  	Vector forward, right;
  	GetVectors( &forward, &right, NULL );
  
  	IPhysicsObject *pPhysObj = m_hManhack->VPhysicsGetObject();
  
  	if ( pPhysObj )
  	{
  		Vector	yawOff = right * random->RandomFloat( -1.0f, 1.0f );
  
  		Vector	forceVel = ( forward + yawOff * 16.0f ) + Vector( 0, 0, 250 );
  		Vector	forceAng = vec3_origin;
  
  		// Give us velocity
  		pPhysObj->AddVelocity( &forceVel, &forceAng );
  	}
  
  	// Stop dealing with this manhack
  	m_hManhack = NULL;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::OnAnimEventShove( void )
  {
  	CBaseEntity *pHurt = CheckTraceHullAttack( 16, Vector(-16,-16,-16), Vector(16,16,16), 15, DMG_CLUB, 1.0f, false );
  
  	if ( pHurt )
  	{
  		Vector vecForceDir = ( pHurt->WorldSpaceCenter() - WorldSpaceCenter() );
  
  		CBasePlayer *pPlayer = ToBasePlayer( pHurt );
  
  		if ( pPlayer != NULL )
  		{
  			//Kick the player angles
  			pPlayer->ViewPunch( QAngle( 8, 14, 0 ) );
  
  			Vector	dir = pHurt->GetAbsOrigin() - GetAbsOrigin();
  			VectorNormalize(dir);
  
  			QAngle angles;
  			VectorAngles( dir, angles );
  			Vector forward, right;
  			AngleVectors( angles, &forward, &right, NULL );
  
  			//If not on ground, then don't make them fly!
  			if ( !(pHurt->GetFlags() & FL_ONGROUND ) )
  				  forward.z = 0.0f;
  
  			//Push the target back
  			pHurt->ApplyAbsVelocityImpulse( forward * 250.0f );
  
  			// Force the player to drop anyting they were holding
  			pPlayer->ForceDropOfCarriedPhysObjects();
  		}
  
  		// Play a random attack hit sound
  		EmitSound( "NPC_Metropolice.Shove" );
  	}
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::OnAnimEventBatonOn( void )
  {
  #ifndef HL2MP
  
  	CWeaponStunStick *pStick = dynamic_cast<CWeaponStunStick *>(GetActiveWeapon());
  
  	if ( pStick )
  	{
  		pStick->SetStunState( true );
  	}
  #endif
  
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::OnAnimEventBatonOff( void )
  {
  #ifndef HL2MP
  
  	CWeaponStunStick *pStick = dynamic_cast<CWeaponStunStick *>(GetActiveWeapon());
  	
  	if ( pStick )
  	{
  		pStick->SetStunState( false );
  	}
  #endif
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //
  // Input  : *pEvent - 
  //
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::HandleAnimEvent( animevent_t *pEvent )
  {
  	// Shove!
  	if ( pEvent->event == AE_METROPOLICE_SHOVE )
  	{
  		OnAnimEventShove();
  		return;
  	}
  
  	if ( pEvent->event == AE_METROPOLICE_BATON_ON )
  	{
  		OnAnimEventBatonOn();
  		return;
  	}
  
  	if ( pEvent->event == AE_METROPOLICE_BATON_OFF )
  	{
  		OnAnimEventBatonOff();
  		return;
  	}
  
  	if ( pEvent->event == AE_METROPOLICE_START_DEPLOY )
  	{
  		OnAnimEventStartDeployManhack();
  		return;
  	}
  
  	if ( pEvent->event == AE_METROPOLICE_DRAW_PISTOL )
  	{
  		m_fWeaponDrawn = true;
  		if( GetActiveWeapon() )
  		{
  			GetActiveWeapon()->RemoveEffects( EF_NODRAW );
  		}
  		return;
  	}
  
  	if ( pEvent->event == AE_METROPOLICE_DEPLOY_MANHACK )
  	{
  		OnAnimEventDeployManhack( pEvent );
  		return;
  	}
  
  	BaseClass::HandleAnimEvent( pEvent );
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose:  This is a generic function (to be implemented by sub-classes) to
  //			 handle specific interactions between different types of characters
  //			 (For example the barnacle grabbing an NPC)
  // Input  :  Constant for the type of interaction
  // Output :	 true  - if sub-class has a response for the interaction
  //			 false - if sub-class has no response
  //-----------------------------------------------------------------------------
  bool CNPC_MetroPolice::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt)
  {
  	if ( interactionType == g_interactionMetrocopStartedStitch )
  	{
  		// If anybody in our squad started a stitch, we can't for a little while
  		m_flValidStitchTime = gpGlobals->curtime + random->RandomFloat( METROPOLICE_SQUAD_STITCH_MIN_INTERVAL, METROPOLICE_SQUAD_STITCH_MAX_INTERVAL );
  		return true;
  	}
  
  	if ( interactionType == g_interactionMetrocopIdleChatter )
  	{
  		m_nIdleChatterType = (int)data;
  		return true;
  	}
  
  	if ( interactionType == g_interactionMetrocopClearSentenceQueues )
  	{
  			m_Sentences.ClearQueue();
  			return true;
  	}
  
  	// React to being hit by physics objects
  	if ( interactionType == g_interactionHitByPlayerThrownPhysObj )
  	{
  		// Ignore if I'm in scripted state
  		if ( !IsInAScript() && (m_NPCState != NPC_STATE_SCRIPT) )
  		{
  			SetCondition( COND_METROPOLICE_PHYSOBJECT_ASSAULT );
  		}
  		else
  		{
  			AdministerJustice();
  		}
  
  		// See if the object is the cupcop can. If so, fire the output (for x360 achievement)
  		CBaseProp *pProp = (CBaseProp*)data;
  		if( pProp != NULL )
  		{
  			if( pProp->NameMatches("cupcop_can") )
  				m_OnCupCopped.FireOutput( this, NULL );
  		}
  
  		return true;
  	}
  
  	return BaseClass::HandleInteraction( interactionType, data, sourceEnt );
  }
  
  
  //-----------------------------------------------------------------------------
  //-----------------------------------------------------------------------------
  Activity CNPC_MetroPolice::NPC_TranslateActivity( Activity newActivity )
  {
  	if( IsOnFire() && newActivity == ACT_RUN )
  	{
  		return ACT_RUN_ON_FIRE;
  	}
  
  	// If we're shoving, see if we should be more forceful in doing so
  	if ( newActivity == ACT_PUSH_PLAYER )
  	{
  		if ( m_nNumWarnings >= METROPOLICE_MAX_WARNINGS )
  			return ACT_MELEE_ATTACK1;
  	}
  
  	newActivity = BaseClass::NPC_TranslateActivity( newActivity );
  
  	// This will put him into an angry idle, which will then be translated
  	// by the weapon to the appropriate type. 
  	if ( m_fWeaponDrawn && newActivity == ACT_IDLE && ( GetState() == NPC_STATE_COMBAT || BatonActive() ) )
  	{
  		newActivity = ACT_IDLE_ANGRY;
  	}
  
  	return newActivity;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Makes the held manhack solid
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::ReleaseManhack( void )
  {
  	Assert( m_hManhack );
  
  	// Make us physical
  	m_hManhack->RemoveSpawnFlags( SF_MANHACK_CARRIED );
  	m_hManhack->CreateVPhysics();
  
  	// Release us
  	m_hManhack->RemoveSolidFlags( FSOLID_NOT_SOLID );
  	m_hManhack->SetMoveType( MOVETYPE_VPHYSICS );
  	m_hManhack->SetParent( NULL );
  
  	// Make us active
  	m_hManhack->RemoveSpawnFlags( SF_NPC_WAIT_FOR_SCRIPT );
  	m_hManhack->ClearSchedule( "Manhack released by metropolice" );
  	
  	// Start him with knowledge of our current enemy
  	if ( GetEnemy() )
  	{
  		m_hManhack->SetEnemy( GetEnemy() );
  		m_hManhack->SetState( NPC_STATE_COMBAT );
  
  		m_hManhack->UpdateEnemyMemory( GetEnemy(), GetEnemy()->GetAbsOrigin() );
  	}
  
  	// Place him into our squad so we can communicate
  	if ( m_pSquad )
  	{
  		m_pSquad->AddToSquad( m_hManhack );
  	}
  }
  
  //-----------------------------------------------------------------------------
  // 
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::Event_Killed( const CTakeDamageInfo &info )
  {
  	// Release the manhack if we're in the middle of deploying him
  	if ( m_hManhack && m_hManhack->IsAlive() )
  	{
  		ReleaseManhack();
  		m_hManhack = NULL;
  	}
  
  	CBasePlayer *pPlayer = ToBasePlayer( info.GetAttacker() );
  
  	if ( pPlayer != NULL )
  	{
  		CHalfLife2 *pHL2GameRules = static_cast<CHalfLife2 *>(g_pGameRules);
  
  		// Attempt to drop health
  		if ( pHL2GameRules->NPC_ShouldDropHealth( pPlayer ) )
  		{
  			DropItem( "item_healthvial", WorldSpaceCenter()+RandomVector(-4,4), RandomAngle(0,360) );
  			pHL2GameRules->NPC_DroppedHealth();
  		}
  	}
  
  	BaseClass::Event_Killed( info );
  }
  
  //-----------------------------------------------------------------------------
  // Try to enter a slot where we shoot a pistol 
  //-----------------------------------------------------------------------------
  bool CNPC_MetroPolice::TryToEnterPistolSlot( int nSquadSlot )
  {
  	// This logic here will not allow us to occupy the a squad slot
  	// too soon after we already were in it.
  	if ( ( m_LastShootSlot != nSquadSlot || !m_TimeYieldShootSlot.Expired() ) &&
  			OccupyStrategySlot( nSquadSlot ) )
  	{
  		if ( m_LastShootSlot != nSquadSlot )
  		{
  			m_TimeYieldShootSlot.Reset();
  			m_LastShootSlot = nSquadSlot;
  		}
  		return true;
  	}
  
  	return false;
  }
  
  
  //-----------------------------------------------------------------------------
  // Combat schedule selection 
  //-----------------------------------------------------------------------------
  int CNPC_MetroPolice::SelectRangeAttackSchedule()
  {
  	if ( HasSpawnFlags( SF_METROPOLICE_ALWAYS_STITCH ) )
  	{
  		int nSched = SelectMoveToLedgeSchedule();
  		if ( nSched != SCHED_NONE )
  			return nSched;
  	}
  
  	// Range attack if we're able
  	if( TryToEnterPistolSlot( SQUAD_SLOT_ATTACK1 ) || TryToEnterPistolSlot( SQUAD_SLOT_ATTACK2 ))
  		return SCHED_RANGE_ATTACK1;
  	
  	// We're not in a shoot slot... so we've allowed someone else to grab it
  	m_LastShootSlot = SQUAD_SLOT_NONE;
  
  	if( CanDeployManhack() && OccupyStrategySlot( SQUAD_SLOT_POLICE_DEPLOY_MANHACK ) )
  	{
  		return SCHED_METROPOLICE_DEPLOY_MANHACK;
  	}
  
  	return SCHED_METROPOLICE_ADVANCE;
  }
  
  
  //-----------------------------------------------------------------------------
  // How many squad members are trying to arrest the player?
  //-----------------------------------------------------------------------------
  int CNPC_MetroPolice::SquadArrestCount()
  {
  	int nCount = 0;
  
  	AISquadIter_t iter;
  	CAI_BaseNPC *pSquadmate = m_pSquad->GetFirstMember( &iter );
  	while ( pSquadmate )
  	{
  		if ( pSquadmate->IsCurSchedule(	SCHED_METROPOLICE_ARREST_ENEMY ) ||
  			pSquadmate->IsCurSchedule( SCHED_METROPOLICE_WARN_AND_ARREST_ENEMY ) )
  		{
  			++nCount;
  		}
  
  		pSquadmate = m_pSquad->GetNextMember( &iter );
  	}
  
  	return nCount;
  }
  
  
  //-----------------------------------------------------------------------------
  // Arrest schedule selection 
  //-----------------------------------------------------------------------------
  int CNPC_MetroPolice::SelectScheduleArrestEnemy()
  {
  	if ( !HasSpawnFlags( SF_METROPOLICE_ARREST_ENEMY ) || !IsInSquad() )
  		return SCHED_NONE;
  
  	if ( !HasCondition( COND_SEE_ENEMY ) )
  		return SCHED_NONE;
  
  	if ( !m_fWeaponDrawn )
  		return SCHED_METROPOLICE_DRAW_PISTOL;
  
  	// First guy that sees the enemy will tell him to freeze
  	if ( OccupyStrategySlot( SQUAD_SLOT_POLICE_ARREST_ENEMY ) )
  		return SCHED_METROPOLICE_WARN_AND_ARREST_ENEMY;
  
  	// Squad members 1 -> n will simply gain a line of sight
  	return SCHED_METROPOLICE_ARREST_ENEMY;
  }
  
  
  //-----------------------------------------------------------------------------
  // Combat schedule selection 
  //-----------------------------------------------------------------------------
  int CNPC_MetroPolice::SelectScheduleNewEnemy()
  {
  	int nSched = SelectScheduleArrestEnemy();
  	if ( nSched != SCHED_NONE )
  		return nSched;
  
  	if ( HasCondition( COND_NEW_ENEMY ) )
  	{
  		m_flNextLedgeCheckTime = gpGlobals->curtime;
  
  		if( CanDeployManhack() && OccupyStrategySlot( SQUAD_SLOT_POLICE_DEPLOY_MANHACK ) )
  			return SCHED_METROPOLICE_DEPLOY_MANHACK;
  	}
  
  	if ( !m_fWeaponDrawn )
  		return SCHED_METROPOLICE_DRAW_PISTOL;
  
  	// Switch our baton on, if it's not already
  	if ( HasBaton() && BatonActive() == false && IsCurSchedule( SCHED_METROPOLICE_ACTIVATE_BATON ) == false )
  	{
  		SetTarget( GetEnemy() );
  		SetBatonState( true );
  		m_flBatonDebounceTime = gpGlobals->curtime + random->RandomFloat( 2.5f, 4.0f );
  		return SCHED_METROPOLICE_ACTIVATE_BATON;
  	}
  
  	return SCHED_NONE;
  }
  
  
  //-----------------------------------------------------------------------------
  // Sound investigation 
  //-----------------------------------------------------------------------------
  int CNPC_MetroPolice::SelectScheduleInvestigateSound()
  {
  	// SEE_ENEMY is set if LOS is available *and* we're looking the right way
  	// Don't investigate if the player's not a criminal.
  	if ( PlayerIsCriminal() && !HasCondition( COND_SEE_ENEMY ) )
  	{
  		if ( HasCondition( COND_HEAR_COMBAT ) || HasCondition( COND_HEAR_PLAYER ) )
  		{
  			if ( m_pSquad && OccupyStrategySlot( SQUAD_SLOT_INVESTIGATE_SOUND ) )
  			{
  				return SCHED_METROPOLICE_INVESTIGATE_SOUND;
  			}
  		}
  	}
  
  	return SCHED_NONE;
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  bool CNPC_MetroPolice::OnObstructionPreSteer( AILocalMoveGoal_t *pMoveGoal, float distClear, AIMoveResult_t *pResult )
  {
  	if ( pMoveGoal->directTrace.pObstruction )
  	{
  		// Is it a physics prop? Store it off as the last thing to block me
  		CPhysicsProp *pProp = dynamic_cast<CPhysicsProp*>( pMoveGoal->directTrace.pObstruction );
  		if ( pProp && pProp->GetHealth() )
  		{
  			m_hBlockingProp = pProp;
  		}
  		else
  		{
  			m_hBlockingProp = NULL;
  		}
  	}
  
  	return BaseClass::OnObstructionPreSteer( pMoveGoal, distClear, pResult );
  }
  
  //-----------------------------------------------------------------------------
  // Combat schedule selection 
  //-----------------------------------------------------------------------------
  int CNPC_MetroPolice::SelectScheduleNoDirectEnemy()
  {
  	// If you can't attack, but you can deploy a manhack, do it!
  	if( CanDeployManhack() && OccupyStrategySlot( SQUAD_SLOT_POLICE_DEPLOY_MANHACK ) )
  		return SCHED_METROPOLICE_DEPLOY_MANHACK;
  
  	// If you can't attack, but you have a baton & there's a physics object in front of you, swat it
  	if ( m_hBlockingProp && HasBaton() )
  	{
  		SetTarget( m_hBlockingProp );
  		m_hBlockingProp = NULL;
  		return SCHED_METROPOLICE_SMASH_PROP;
  	}
  
  	return SCHED_METROPOLICE_CHASE_ENEMY;
  }
  
  
  //-----------------------------------------------------------------------------
  // Combat schedule selection 
  //-----------------------------------------------------------------------------
  int CNPC_MetroPolice::SelectCombatSchedule()
  {
  	// Announce a new enemy
  	if ( HasCondition( COND_NEW_ENEMY ) )
  	{
  		AnnounceEnemyType( GetEnemy() );
  	}
  
  	int nResult = SelectScheduleNewEnemy();
  	if ( nResult != SCHED_NONE )
  		return nResult;
  
  	if( !m_fWeaponDrawn )
  	{
  		return SCHED_METROPOLICE_DRAW_PISTOL;
  	}
  
  	if (!HasBaton() && ((float)m_nRecentDamage / (float)GetMaxHealth()) > RECENT_DAMAGE_THRESHOLD)
  	{
  		m_nRecentDamage = 0;
  		m_flRecentDamageTime = 0;
  		m_Sentences.Speak( "METROPOLICE_COVER_HEAVY_DAMAGE", SENTENCE_PRIORITY_MEDIUM, SENTENCE_CRITERIA_NORMAL );
  
  		return SCHED_TAKE_COVER_FROM_ENEMY;
  	}
  
  	if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
  	{
  		if ( !GetShotRegulator()->IsInRestInterval() )
  			return SelectRangeAttackSchedule();
  		else
  			return SCHED_METROPOLICE_ADVANCE;
  	}
  
  	if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
  	{
  		if ( m_BatonSwingTimer.Expired() )
  		{
  			// Stop chasing the player now that we've taken a swing at them
  			m_flChasePlayerTime = 0;
  			m_BatonSwingTimer.Set( 1.0, 1.75 );
  			return SCHED_MELEE_ATTACK1;
  		}
  		else
  			return SCHED_COMBAT_FACE;
  	}
  
  	if ( HasCondition( COND_TOO_CLOSE_TO_ATTACK ) )
  	{
  		return SCHED_BACK_AWAY_FROM_ENEMY;
  	}
  	
  	if ( HasCondition( COND_LOW_PRIMARY_AMMO ) || HasCondition( COND_NO_PRIMARY_AMMO ) )
  	{
  		AnnounceOutOfAmmo( );
  		return SCHED_HIDE_AND_RELOAD;
  	}
  
  	if ( HasCondition(COND_WEAPON_SIGHT_OCCLUDED) && !HasBaton() )
  	{
  		// If they are hiding behind something that we can destroy, start shooting at it.
  		CBaseEntity *pBlocker = GetEnemyOccluder();
  		if ( pBlocker && pBlocker->GetHealth() > 0 && OccupyStrategySlotRange( SQUAD_SLOT_POLICE_ATTACK_OCCLUDER1, SQUAD_SLOT_POLICE_ATTACK_OCCLUDER2 ) )
  		{
  			m_Sentences.Speak( "METROPOLICE_SHOOT_COVER" );
  			return SCHED_SHOOT_ENEMY_COVER;
  		}
  	}
  
  	if (HasCondition(COND_ENEMY_OCCLUDED))
  	{
  		if ( GetEnemy() && !(GetEnemy()->GetFlags() & FL_NOTARGET) )
  		{
  			// Charge in and break the enemy's cover!
  			return SCHED_ESTABLISH_LINE_OF_FIRE;
  		}
  	}
  
  	nResult = SelectScheduleNoDirectEnemy();
  	if ( nResult != SCHED_NONE )
  		return nResult;
  
  	return SCHED_NONE;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: This is a bridge between stunstick, NPC and its behavior
  // Output : Returns true on success, false on failure.
  //-----------------------------------------------------------------------------
  bool CNPC_MetroPolice::ShouldKnockOutTarget( CBaseEntity *pTarget )
  {
  	if ( m_PolicingBehavior.IsEnabled() && m_PolicingBehavior.ShouldKnockOutTarget( pTarget ) )
  		return true;
  
  	return false;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: This is a bridge between stunstick, NPC and its behavior
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::KnockOutTarget( CBaseEntity *pTarget )
  {
  	if ( m_PolicingBehavior.IsEnabled() )
  	{
  		m_PolicingBehavior.KnockOutTarget( pTarget );
  	}
  }
  
  //-----------------------------------------------------------------------------
  // Can me enemy see me? 
  //-----------------------------------------------------------------------------
  bool CNPC_MetroPolice::CanEnemySeeMe( )
  {
  	if ( GetEnemy()->IsPlayer() )
  	{
  		if ( static_cast<CBasePlayer*>(GetEnemy())->FInViewCone( this ) )
  		{
  			return true;
  		}
  	}
  	return false;
  }
  
  
  //-----------------------------------------------------------------------------
  // Choose weights about where we can use particular stitching behaviors 
  //-----------------------------------------------------------------------------
  #define STITCH_MIN_DISTANCE 1000.0f
  #define STITCH_MIN_DISTANCE_SLOW 1250.0f
  
  #define STITCH_AT_CONE	0.866f	// cos(30)
  #define STITCH_AT_CONE_WHEN_VISIBLE_MAX	0.3f	// cos(?)
  #define STITCH_AT_CONE_WHEN_VISIBLE_MIN	0.707f	// cos(45)
  #define STITCH_AT_COS_MIN_SPIN_ANGLE	0.2f
  
  float CNPC_MetroPolice::StitchAtWeight( float flDist, float flSpeed, float flDot, float flReactionTime, const Vector &vecTargetToGun )
  {
  	// Can't do an 'attacking' stitch if it's too soon
  	if ( m_flValidStitchTime > gpGlobals->curtime )
  		return 0.0f;
  
  	// No squad slots? no way.
  	if( IsStrategySlotRangeOccupied( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) )
  		return false;
  
  	// Don't do it if the player doesn't have enough time to react
  	if ( flDist < STITCH_MIN_DISTANCE )
  		return 0.0f;
  
  	// Don't do it if the player is farther but really slow
  	if ( ( flDist < STITCH_MIN_DISTANCE_SLOW ) && ( flSpeed < 150.0f ) )
  		return 0.0f;
  
  	// Does the predicted stitch position cross the plane from me to the target's initial position?
  	// If so, it'll look really dumb. Disallow that.
  	Vector vecGunToPredictedTarget, vecShootAtVel;
  	PredictShootTargetPosition( flReactionTime, 0.0f, 0.0f, &vecGunToPredictedTarget, &vecShootAtVel );
  	vecGunToPredictedTarget -= Weapon_ShootPosition();
  	vecGunToPredictedTarget.z = 0.0f;
  	VectorNormalize( vecGunToPredictedTarget );
  
  	Vector2D vecGunToTarget;
  	Vector2DMultiply( vecTargetToGun.AsVector2D(), -1.0f, vecGunToTarget );
  	Vector2DNormalize( vecGunToTarget );
  	if ( DotProduct2D( vecGunToTarget, vecGunToPredictedTarget.AsVector2D() ) <= STITCH_AT_COS_MIN_SPIN_ANGLE )
  		return 0.0f;
  
  	// If the cop is in the view cone, then up the cone in which the stitch will occur 
  	float flConeAngle = STITCH_AT_CONE;
  	if ( CanEnemySeeMe() )
  	{
  		flDist = clamp( flDist, 1500.0f, 2500.0f );
  		flConeAngle = RemapVal( flDist, 1500.0f, 2500.0f, STITCH_AT_CONE_WHEN_VISIBLE_MIN, STITCH_AT_CONE_WHEN_VISIBLE_MAX );
  	}
  
  	flDot = clamp( flDot, -1.0f, flConeAngle );
  	return RemapVal( flDot, -1.0f, flConeAngle, 0.5f, 1.0f );
  }
  
  
  #define STITCH_ACROSS_CONE	0.5f	// cos(60)
  
  float CNPC_MetroPolice::StitchAcrossWeight( float flDist, float flSpeed, float flDot, float flReactionTime )
  {
  	return 0.0f;
  
  	// Can't do an 'attacking' stitch if it's too soon
  	if ( m_flValidStitchTime > gpGlobals->curtime )
  		return 0.0f;
  
  	// No squad slots? no way.
  	if( IsStrategySlotRangeOccupied( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) )
  		return 0.0f;
  
  	if ( flDist < STITCH_MIN_DISTANCE )
  		return 0.0f;
  
  	// Don't do it if the player doesn't have enough time to react
  	if ( flDist < flSpeed * flReactionTime )
  		return 0.0f;
  
  	// We want to stitch across if we're within the stitch across cone
  	if ( flDot < STITCH_ACROSS_CONE )
  		return 0.0f;
  
  	return 1.0f;
  }
  
  
  #define STITCH_ALONG_MIN_CONE	0.866f	// cos(30)
  #define STITCH_ALONG_MIN_CONE_WHEN_VISIBLE	0.707f	// cos(45)
  #define STITCH_ALONG_MAX_CONE	-0.4f	//
  #define STITCH_ALONG_MIN_SPEED	300.0f
  
  float CNPC_MetroPolice::StitchAlongSideWeight( float flDist, float flSpeed, float flDot )
  {
  	// No squad slots? no way.
  	if( IsStrategySlotRangeOccupied( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) &&
  		IsStrategySlotRangeOccupied( SQUAD_SLOT_POLICE_COVERING_FIRE1, SQUAD_SLOT_POLICE_COVERING_FIRE2 ) )
  		return 0.0f;
  
  	if ( flDist < (AIM_ALONG_SIDE_LINE_OF_DEATH_DISTANCE + AIM_ALONG_SIDE_STEER_DISTANCE + 100.0f) )
  		return 0.0f;
  
  	if ( flSpeed < STITCH_ALONG_MIN_SPEED )
  		return 0.0f;
  
  	// We want to stitch across if we're within the stitch across cone
  	float flMinConeAngle = STITCH_ALONG_MIN_CONE;
  	bool bCanEnemySeeMe = CanEnemySeeMe( );
  	if ( bCanEnemySeeMe )
  	{
  		flMinConeAngle = STITCH_ALONG_MIN_CONE_WHEN_VISIBLE;
  	}
  
  	if (( flDot > flMinConeAngle ) || ( flDot < STITCH_ALONG_MAX_CONE ))
  		return 0.0f;
  
  	return bCanEnemySeeMe ? 1.0f : 2.0f;
  }
  
  #define STITCH_BEHIND_MIN_CONE	0.0f	// cos(90)
  
  float CNPC_MetroPolice::StitchBehindWeight( float flDist, float flSpeed, float flDot )
  {
  	return 0.0f;
  
  	// No squad slots? no way.
  	if( IsStrategySlotRangeOccupied( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) &&
  		IsStrategySlotRangeOccupied( SQUAD_SLOT_POLICE_COVERING_FIRE1, SQUAD_SLOT_POLICE_COVERING_FIRE2 ) )
  		return 0.0f;
  
  	if ( flDist < AIM_BEHIND_MINIMUM_DISTANCE )
  		return 0.0f;
  
  	// We want to stitch across if we're within the stitch across cone
  	if ( flDot > STITCH_BEHIND_MIN_CONE )
  		return 0.0f;
  
  	// If we're close, reduce the chances of this if we're also slow
  	if ( flDist < STITCH_MIN_DISTANCE )
  	{
  		flSpeed = clamp( flSpeed, 300.0f, 450.0f );
  		float flWeight = RemapVal( flSpeed, 300.0f, 450.0f, 0.0f, 1.0f );
  		return flWeight;
  	}
  
  	return 1.0f;
  }
  
  float CNPC_MetroPolice::StitchTightWeight( float flDist, float flSpeed, const Vector &vecTargetToGun, const Vector &vecVelocity )
  {
  	// No squad slots? no way.
  	if( IsStrategySlotRangeOccupied( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) &&
  		IsStrategySlotRangeOccupied( SQUAD_SLOT_POLICE_COVERING_FIRE1, SQUAD_SLOT_POLICE_COVERING_FIRE2 ) )
  		return 0.0f;
  
  	if ( flDist > STITCH_MIN_DISTANCE )
  	{
  		if ( flDist > 2000.0f )
  			return 0.0f;
  
  		// We can stitch tight if they are close and no other rules apply.
  		return 0.0001f;
  	}
  
  	// If we're heading right at him, them fire it!
  	Vector vecTargetToGunDir = vecTargetToGun;
  	Vector vecVelocityDir = vecVelocity;
  	VectorNormalize( vecTargetToGunDir );
  	VectorNormalize( vecVelocityDir );
  
  	if ( DotProduct( vecTargetToGunDir, vecVelocityDir ) > 0.95f )
  		return 8.0f;
  
  	// If we're on the same level, fire at him!
  	if ( ( fabs(vecTargetToGun.z) < 50.0f ) && ( flDist < STITCH_MIN_DISTANCE ) )
  		return 1.0f;
  
  	flSpeed = clamp( flSpeed, 300.0f, 450.0f );
  	float flWeight = RemapVal( flSpeed, 300.0f, 450.0f, 1.0f, 0.0f );
  	return flWeight;
  }
  
  
  //-----------------------------------------------------------------------------
  // Combat schedule selection 
  //-----------------------------------------------------------------------------
  #define STITCH_REACTION_TIME 2.0f
  #define STITCH_SCHEDULE_COUNT 5
  
  int CNPC_MetroPolice::SelectStitchSchedule()
  {
  	// If the boat is very close to us, we're going to stitch at it
  	// even if the squad slot is full..
  	Vector vecTargetToGun;
  	Vector vecTarget = StitchAimTarget( GetAbsOrigin(), false );
  	VectorSubtract( Weapon_ShootPosition(), vecTarget, vecTargetToGun );
  
  	Vector2D vecTargetToGun2D = vecTargetToGun.AsVector2D();
  	float flDist = Vector2DNormalize( vecTargetToGun2D );
  
  	if ( HasSpawnFlags( SF_METROPOLICE_NO_FAR_STITCH ) )
  	{
  		if ( flDist > 6000.0f )
  			return SCHED_NONE;
  	}
  
  	float flReactionTime = STITCH_REACTION_TIME * sk_metropolice_stitch_reaction.GetFloat();
  	Vector vecVelocity;
  	PredictShootTargetVelocity( flReactionTime, &vecVelocity );
  
  	Vector2D vecVelocity2D = vecVelocity.AsVector2D();
  	float flSpeed = Vector2DNormalize( vecVelocity2D );
  	float flDot = DotProduct2D( vecTargetToGun2D, vecVelocity2D );
  
  	float flWeight[STITCH_SCHEDULE_COUNT];
  	flWeight[0] = StitchAtWeight( flDist, flSpeed, flDot, flReactionTime, vecTargetToGun );
  	flWeight[1] = flWeight[0] + StitchAcrossWeight( flDist, flSpeed, flDot, flReactionTime );
  	flWeight[2] = flWeight[1] + StitchTightWeight( flDist, flSpeed, vecTargetToGun, vecVelocity );
  	flWeight[3] = flWeight[2] + StitchAlongSideWeight( flDist, flSpeed, flDot );
  	flWeight[4] = flWeight[3] + StitchBehindWeight( flDist, flSpeed, flDot );
  
  	if ( flWeight[STITCH_SCHEDULE_COUNT - 1] == 0.0f )
  		return SCHED_NONE;
  
  	int pSched[STITCH_SCHEDULE_COUNT] =
  	{
  		SCHED_METROPOLICE_AIM_STITCH_AT_AIRBOAT,
  		SCHED_METROPOLICE_AIM_STITCH_IN_FRONT_OF_AIRBOAT,
  		SCHED_METROPOLICE_AIM_STITCH_TIGHTLY,
  		SCHED_METROPOLICE_AIM_STITCH_ALONG_SIDE_OF_AIRBOAT,
  		SCHED_METROPOLICE_AIM_STITCH_BEHIND_AIRBOAT,
  	};
  
  	int i;
  	float flRand = random->RandomFloat( 0.0f, flWeight[STITCH_SCHEDULE_COUNT - 1] );
  	for ( i = 0; i < STITCH_SCHEDULE_COUNT; ++i )
  	{
  		if ( flRand <= flWeight[i] )
  			break;
  	}
  
  	// If we're basically a covering activity, take up that slot
  	if ( i >= 3 )
  	{
  		if( OccupyStrategySlotRange( SQUAD_SLOT_POLICE_COVERING_FIRE1, SQUAD_SLOT_POLICE_COVERING_FIRE2 ) )
  		{
  			return pSched[i];
  		}
  	}
  
  	if( OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) )
  	{
  		if ( IsInSquad() && (i < 2) )
  		{
  			GetSquad()->BroadcastInteraction( g_interactionMetrocopStartedStitch, NULL );
  		}
  
  		return pSched[i];
  	}
  
  	return SCHED_NONE;
  }
  
  
  //-----------------------------------------------------------------------------
  // Combat schedule selection 
  //-----------------------------------------------------------------------------
  int CNPC_MetroPolice::SelectMoveToLedgeSchedule()
  {
  	// Prevent a bunch of unnecessary raycasts.
  	if ( m_flNextLedgeCheckTime > gpGlobals->curtime )
  		return SCHED_NONE;
  
  	// If the NPC is above the airboat (say, on a bridge), make sure he
  	// goes to the closest ledge. (may need a spawnflag for this)
  	if ( (GetAbsOrigin().z - GetShootTarget()->GetAbsOrigin().z) >= 150.0f )
  	{
  		m_flNextLedgeCheckTime = gpGlobals->curtime + 3.0f;
  
  		// We need to be able to shoot downward at a 60 degree angle.
  		Vector vecDelta;
  		VectorSubtract( GetShootTarget()->WorldSpaceCenter(), Weapon_ShootPosition(), vecDelta );
  		vecDelta.z = 0.0f;
  		VectorNormalize( vecDelta );
  
  		// At this point, vecDelta is 45 degrees below horizontal.
  		vecDelta.z = -1;
  		vecDelta *= 100.0f;
  
  		trace_t tr;
  		CTraceFilterWorldOnly traceFilter;
  		UTIL_TraceLine( Weapon_ShootPosition(), Weapon_ShootPosition() + vecDelta, MASK_SOLID, &traceFilter, &tr );
  
  		if (tr.endpos.z >= GetAbsOrigin().z - 25.0f )
  			return SCHED_METROPOLICE_ESTABLISH_STITCH_LINE_OF_FIRE;
  	}
  	
  	return SCHED_NONE;
  }
  
  
  //-----------------------------------------------------------------------------
  // Combat schedule selection 
  //-----------------------------------------------------------------------------
  int CNPC_MetroPolice::SelectAirboatRangeAttackSchedule()
  {
  	// Move to a ledge, if we need to.
  	int nSched = SelectMoveToLedgeSchedule();
  	if ( nSched != SCHED_NONE )
  		return nSched;
  
  	if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
  	{
  		nSched = SelectStitchSchedule();
  		if ( nSched != SCHED_NONE )
  		{
  			m_LastShootSlot = SQUAD_SLOT_NONE;
  			return nSched;
  		}
  	}
  
  	if( CanDeployManhack() && OccupyStrategySlot( SQUAD_SLOT_POLICE_DEPLOY_MANHACK ) )
  	{
  		return SCHED_METROPOLICE_DEPLOY_MANHACK;
  	}
  
  	return SCHED_METROPOLICE_ESTABLISH_LINE_OF_FIRE;
  }
  
  
  //-----------------------------------------------------------------------------
  // Combat schedule selection for when the enemy is in an airboat
  //-----------------------------------------------------------------------------
  int CNPC_MetroPolice::SelectAirboatCombatSchedule()
  {
  	int nResult = SelectScheduleNewEnemy();
  	if ( nResult != SCHED_NONE )
  		return nResult;
  
  	// We're assuming here that the cops who attack airboats have SMGs
  //	Assert( Weapon_OwnsThisType( "weapon_smg1" ) );
  
  	if ( HasCondition( COND_SEE_ENEMY ) )
  	{
  		return SelectAirboatRangeAttackSchedule();
  	}
  	
  	if ( HasCondition( COND_WEAPON_SIGHT_OCCLUDED ) )
  	{
  		// If they are hiding behind something also attack. Don't bother
  		// shooting the destroyable thing; it'll happen anyways with the SMG
  		CBaseEntity *pBlocker = GetEnemyOccluder();
  		if ( pBlocker && pBlocker->GetHealth() > 0 && OccupyStrategySlotRange( SQUAD_SLOT_POLICE_ATTACK_OCCLUDER1, SQUAD_SLOT_POLICE_ATTACK_OCCLUDER2 ) )
  		{
  			return SelectAirboatRangeAttackSchedule();
  		}
  	}
  
  	nResult = SelectScheduleNoDirectEnemy();
  	if ( nResult != SCHED_NONE )
  		return nResult;
  
  	return SCHED_NONE;
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  // Input  : &info - 
  // Output : Returns true on success, false on failure.
  //-----------------------------------------------------------------------------
  bool CNPC_MetroPolice::IsHeavyDamage( const CTakeDamageInfo &info )
  {
  	// Metropolice considers bullet fire heavy damage
  	if ( info.GetDamageType() & DMG_BULLET )
  		return true;
  
  	return BaseClass::IsHeavyDamage( info );
  }
  
  //-----------------------------------------------------------------------------
  // TraceAttack
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
  {
  	// This is needed so we can keep track of the direction of the shot
  	// because we're going to use it to choose the flinch animation
  	if ( m_bSimpleCops )
  	{
  		if ( m_takedamage == DAMAGE_YES )
  		{
  			Vector vecLastHitDirection;
  			VectorIRotate( vecDir, EntityToWorldTransform(), vecLastHitDirection );
  
  			// Point *at* the shooter
  			vecLastHitDirection *= -1.0f;
  
  			QAngle lastHitAngles;
  			VectorAngles( vecLastHitDirection, lastHitAngles );
  			m_flLastHitYaw	= lastHitAngles.y;
  		}
  	}
  
  	BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
  }
  
  //-----------------------------------------------------------------------------
  // Determines the best type of flinch anim to play.
  //-----------------------------------------------------------------------------
  Activity CNPC_MetroPolice::GetFlinchActivity( bool bHeavyDamage, bool bGesture )
  {
  	if ( !bGesture && m_bSimpleCops )
  	{
  		// Version for getting shot from behind
  		if ( ( m_flLastHitYaw > 90 ) && ( m_flLastHitYaw < 270 ) )
  		{
  			Activity flinchActivity = (Activity)ACT_METROPOLICE_FLINCH_BEHIND;
  			if ( SelectWeightedSequence ( flinchActivity ) != ACTIVITY_NOT_AVAILABLE )
  				return flinchActivity;
  		}
  
  		if ( ( LastHitGroup() == HITGROUP_CHEST ) ||
  			( LastHitGroup() == HITGROUP_LEFTLEG ) ||
  			( LastHitGroup() == HITGROUP_RIGHTLEG ) )
  		{
  			Activity flinchActivity = ACT_FLINCH_STOMACH;
  			if ( SelectWeightedSequence ( ACT_FLINCH_STOMACH ) != ACTIVITY_NOT_AVAILABLE )
  				return flinchActivity;
  		}
  	}
  
  	return BaseClass::GetFlinchActivity( bHeavyDamage, bGesture );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::PlayFlinchGesture( void )
  {
  	BaseClass::PlayFlinchGesture();
  
  	// To ensure old playtested difficulty stays the same, stop cops shooting for a bit after gesture flinches
  	GetShotRegulator()->FireNoEarlierThan( gpGlobals->curtime + 0.5 );
  }
  
  //-----------------------------------------------------------------------------
  // We're taking cover from danger
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::AnnounceHarrassment( void )
  {
  	static const char *pWarnings[3] = 
  	{
  		"METROPOLICE_BACK_UP_A",
  		"METROPOLICE_BACK_UP_B",
  		"METROPOLICE_BACK_UP_C",
  	};
  
  	m_Sentences.Speak( pWarnings[ random->RandomInt( 0, ARRAYSIZE(pWarnings)-1 ) ], SENTENCE_PRIORITY_MEDIUM, SENTENCE_CRITERIA_NORMAL );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::IncrementPlayerCriminalStatus( void )
  {
  	CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
  
  	if ( pPlayer )
  	{
  		AddLookTarget( pPlayer, 0.8f, 5.0f );
  
  		if ( m_nNumWarnings < METROPOLICE_MAX_WARNINGS )
  		{
  			m_nNumWarnings++;
  		}
  
  		if ( m_nNumWarnings >= (METROPOLICE_MAX_WARNINGS-1) )
  		{
  			SetTarget( pPlayer );
  			SetBatonState( true );
  		}
  	}
  
  	m_flBatonDebounceTime = gpGlobals->curtime + random->RandomFloat( 2.0f, 4.0f );
  
  	AnnounceHarrassment();
  
  	m_bKeepFacingPlayer = true;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  // Output : int
  //-----------------------------------------------------------------------------
  int CNPC_MetroPolice::SelectShoveSchedule( void )
  {
  	IncrementPlayerCriminalStatus();
  
  	// Stop chasing the player now that we've taken a swing at them
  	m_flChasePlayerTime = 0;
  	return SCHED_METROPOLICE_SHOVE;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  // Output : float
  //-----------------------------------------------------------------------------
  float CNPC_MetroPolice::GetIdealAccel( void ) const
  {
  	return GetIdealSpeed() * 2.0f;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Chase after a player who's just pissed us off, and hit him
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::AdministerJustice( void )
  {
  	if ( !AI_IsSinglePlayer() )
  		return;
  
  	// If we're allowed to chase the player, do so. Otherwise, just threaten.
  	if ( !IsInAScript() && (m_NPCState != NPC_STATE_SCRIPT) && HasSpawnFlags( SF_METROPOLICE_ALLOWED_TO_RESPOND ) )
  	{
  		if ( m_vecPreChaseOrigin == vec3_origin )
  		{
  			m_vecPreChaseOrigin = GetAbsOrigin();
  			m_flPreChaseYaw = GetAbsAngles().y;
  		}
  		m_flChasePlayerTime = gpGlobals->curtime + RandomFloat( 3, 7 );
  
  		// Attack the target
  		CBasePlayer *pPlayer = UTIL_PlayerByIndex(1);
  		SetEnemy( pPlayer );
  		SetState( NPC_STATE_COMBAT );
  		UpdateEnemyMemory( pPlayer, pPlayer->GetAbsOrigin() );
  	}
  	else
  	{
  		// Watch the player for a time.
  		m_bKeepFacingPlayer = true;
  
  		// Try and find a nearby cop to administer justice
  		CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs();
  		int nAIs = g_AI_Manager.NumAIs();
  		for ( int i = 0; i < nAIs; i++ )
  		{
  			if ( ppAIs[i] == this )
  				continue;
  
  			if ( ppAIs[i]->Classify() == CLASS_METROPOLICE && FClassnameIs( ppAIs[i], "npc_metropolice" ) )
  			{
  				CNPC_MetroPolice *pNPC = assert_cast<CNPC_MetroPolice*>(ppAIs[i]);
  				if ( pNPC->HasSpawnFlags( SF_METROPOLICE_ALLOWED_TO_RESPOND ) )
  				{
  					// Is he within site & range?
  					if ( FVisible(pNPC) && pNPC->FVisible( UTIL_PlayerByIndex(1) ) && 
  						UTIL_DistApprox( WorldSpaceCenter(), pNPC->WorldSpaceCenter() ) < 512 )
  					{
  						pNPC->AdministerJustice();
  						break;
  					}
  				}
  			}
  		}
  	}
  }
  
  //-----------------------------------------------------------------------------
  // Schedule selection 
  //-----------------------------------------------------------------------------
  int CNPC_MetroPolice::SelectSchedule( void )
  {
  	if ( !GetEnemy() && HasCondition( COND_IN_PVS ) && AI_GetSinglePlayer() && !AI_GetSinglePlayer()->IsAlive() )
  	{
  		return SCHED_PATROL_WALK;
  	}
  
  	if ( HasCondition(COND_METROPOLICE_ON_FIRE) )
  	{
  		m_Sentences.Speak( "METROPOLICE_ON_FIRE", SENTENCE_PRIORITY_INVALID, SENTENCE_CRITERIA_ALWAYS );
  		return SCHED_METROPOLICE_BURNING_STAND;
  	}
  
  	// React to being struck by a physics object
  	if ( HasCondition( COND_METROPOLICE_PHYSOBJECT_ASSAULT ) )
  	{
  		ClearCondition( COND_METROPOLICE_PHYSOBJECT_ASSAULT );
  
  		// See which state our player relationship is in
  		if ( PlayerIsCriminal() == false )
  		{
  			m_Sentences.Speak( "METROPOLICE_HIT_BY_PHYSOBJECT", SENTENCE_PRIORITY_INVALID, SENTENCE_CRITERIA_ALWAYS );
  			m_nNumWarnings = METROPOLICE_MAX_WARNINGS;
  			AdministerJustice();
  		}
  		else if ( GlobalEntity_GetState( "gordon_precriminal" ) == GLOBAL_ON )
  		{
  			// We're not allowed to respond, but warn them
  			m_Sentences.Speak( "METROPOLICE_IDLE_HARASS_PLAYER", SENTENCE_PRIORITY_INVALID, SENTENCE_CRITERIA_ALWAYS );
  		}
  	}
  
  	int nSched = SelectFlinchSchedule();
  	if ( nSched != SCHED_NONE )
  		return nSched;
  
  	if ( HasBaton() )
  	{
  		// See if we're being told to activate our baton
  		if ( m_bShouldActivateBaton && BatonActive() == false && IsCurSchedule( SCHED_METROPOLICE_ACTIVATE_BATON ) == false )
  			return SCHED_METROPOLICE_ACTIVATE_BATON;
  
  		if ( m_bShouldActivateBaton == false && BatonActive() && IsCurSchedule( SCHED_METROPOLICE_DEACTIVATE_BATON ) == false )
  			return SCHED_METROPOLICE_DEACTIVATE_BATON;
  
  		if( metropolice_chase_use_follow.GetBool() )
  		{
  			if( GetEnemy() )
  			{
  				AI_FollowParams_t params;
  				params.formation = AIF_TIGHT;
  				m_FollowBehavior.SetParameters( params );
  				m_FollowBehavior.SetFollowTarget( GetEnemy() );
  			}
  		}
  	}
  
  	// See if the player is in our face (unless we're scripting)
  	if ( PlayerIsCriminal() == false )
  	{
  		if ( !IsInAScript() && (HasCondition( COND_METROPOLICE_PLAYER_TOO_CLOSE ) || m_bPlayerTooClose) )
  		{
  			// Don't hit the player too many times in a row, unless he's trying to push a cop who hasn't moved
  			if ( m_iNumPlayerHits < 3 || m_vecPreChaseOrigin == vec3_origin )
  			{
  				ClearCondition( COND_METROPOLICE_PLAYER_TOO_CLOSE );
  				m_bPlayerTooClose = false;
  				
  				return SelectShoveSchedule();
  			}
  		}
  		else if ( m_iNumPlayerHits )
  		{
  			// If we're not in combat, and we've got a pre-chase origin, move back to it
  			if ( ( m_NPCState != NPC_STATE_COMBAT ) && 
  				 ( m_vecPreChaseOrigin != vec3_origin ) && 
  				 ( m_flChasePlayerTime < gpGlobals->curtime ) )
  			{
  				return SCHED_METROPOLICE_RETURN_TO_PRECHASE;
  			}
  		}
  	}
  
  	// Cower when physics objects are thrown at me
  	if ( HasCondition( COND_HEAR_PHYSICS_DANGER ) )
  	{
  		if ( m_flLastPhysicsFlinchTime + 4.0f <= gpGlobals->curtime )
  		{
  			m_flLastPhysicsFlinchTime = gpGlobals->curtime;
  			return SCHED_FLINCH_PHYSICS;
  		}
  	}
  
  	// Always run for cover from danger sounds
  	if ( HasCondition(COND_HEAR_DANGER) )
  	{
  		CSound *pSound;
  		pSound = GetBestSound();
  
  		Assert( pSound != NULL );
  		if ( pSound )
  		{
  			if (pSound->m_iType & SOUND_DANGER)
  			{
  				AnnounceTakeCoverFromDanger( pSound );
  				return SCHED_TAKE_COVER_FROM_BEST_SOUND;
  			}
  			if (!HasCondition( COND_SEE_ENEMY ) && ( pSound->m_iType & (SOUND_PLAYER | SOUND_PLAYER_VEHICLE | SOUND_COMBAT) ))
  			{
  				GetMotor()->SetIdealYawToTarget( pSound->GetSoundReactOrigin() );
  			}
  		}
  	}
  
  	bool bHighHealth = ((float)GetHealth() / (float)GetMaxHealth() > 0.75f);
  
  	// This will cause the cops to run backwards + shoot at the same time
  	if ( !bHighHealth && !HasBaton() )
  	{
  		if ( GetActiveWeapon() && (GetActiveWeapon()->m_iClip1 <= 5) )
  		{
  			m_Sentences.Speak( "METROPOLICE_COVER_LOW_AMMO" );
  			return SCHED_HIDE_AND_RELOAD;
  		}
  	}
  
  	if( HasCondition( COND_NO_PRIMARY_AMMO ) )
  	{
  		if ( bHighHealth )
  			return SCHED_RELOAD;
  
  		AnnounceOutOfAmmo( );
  		return SCHED_HIDE_AND_RELOAD;
  	}
  
  	// If we're clubbing someone who threw something at us. chase them
  	if ( m_NPCState == NPC_STATE_COMBAT && m_flChasePlayerTime > gpGlobals->curtime )
  		return SCHED_CHASE_ENEMY;
  
  	if ( !BehaviorSelectSchedule() )
  	{
  		// If we've warned the player at all, watch him like a hawk
  		if ( m_bKeepFacingPlayer && !PlayerIsCriminal() )
  			return SCHED_TARGET_FACE;
  
  		switch( m_NPCState )
  		{
  		case NPC_STATE_IDLE:
  			{
  				nSched = SelectScheduleInvestigateSound();
  				if ( nSched != SCHED_NONE )
  					return nSched;
  				break;
  			}
  
  		case NPC_STATE_ALERT:
  			{
  				nSched = SelectScheduleInvestigateSound();
  				if ( nSched != SCHED_NONE )
  					return nSched;
  			}
  			break;
  
  		case NPC_STATE_COMBAT:
  			if (!IsEnemyInAnAirboat() || !Weapon_OwnsThisType( "weapon_smg1" ) )
  			{
  				int nResult = SelectCombatSchedule();
  				if ( nResult != SCHED_NONE )
  					return nResult;
  			}
  			else
  			{
  				int nResult = SelectAirboatCombatSchedule();
  				if ( nResult != SCHED_NONE )
  					return nResult;
  			}
  			break;
  		}
  	}
  
  	// If we're not in combat, and we've got a pre-chase origin, move back to it
  	if ( ( m_NPCState != NPC_STATE_COMBAT ) && 
  		 ( m_vecPreChaseOrigin != vec3_origin ) && 
  		 ( m_flChasePlayerTime < gpGlobals->curtime ) )
  	{
  		return SCHED_METROPOLICE_RETURN_TO_PRECHASE;
  	}
  
  	return BaseClass::SelectSchedule();
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  // Input  : failedSchedule - 
  //			failedTask - 
  //			taskFailCode - 
  // Output : int
  //-----------------------------------------------------------------------------
  int CNPC_MetroPolice::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
  {
  	if ( failedSchedule == SCHED_METROPOLICE_CHASE_ENEMY )
  	{
  		return SCHED_METROPOLICE_ESTABLISH_LINE_OF_FIRE;
  	}
  
  	return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode );
  }
  
  
  //-----------------------------------------------------------------------------
  //-----------------------------------------------------------------------------
  int CNPC_MetroPolice::TranslateSchedule( int scheduleType )
  {
  	switch( scheduleType )
  	{
  	case SCHED_ALERT_FACE_BESTSOUND:
  		if ( !IsCurSchedule( SCHED_METROPOLICE_ALERT_FACE_BESTSOUND, false ) )
  		{
  			return SCHED_METROPOLICE_ALERT_FACE_BESTSOUND;
  		}
  		return SCHED_ALERT_FACE_BESTSOUND;
  
  	case SCHED_CHASE_ENEMY:
  		
  		if ( !IsRunningBehavior() )
  		{
  			return SCHED_METROPOLICE_CHASE_ENEMY;
  		}
  		
  		break;
  
  	case SCHED_ESTABLISH_LINE_OF_FIRE:
  	case SCHED_METROPOLICE_ESTABLISH_LINE_OF_FIRE:
  		if ( IsEnemyInAnAirboat() )
  		{
  			int nSched = SelectMoveToLedgeSchedule();
  			if ( nSched != SCHED_NONE )
  				return nSched;
  		}
  		return SCHED_METROPOLICE_ESTABLISH_LINE_OF_FIRE;		
  		
  	case SCHED_WAKE_ANGRY:
  		return SCHED_METROPOLICE_WAKE_ANGRY;
  
  	case SCHED_FAIL_TAKE_COVER:
  		
  		if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
  		{
  			// Must be able to shoot now
  			if( TryToEnterPistolSlot( SQUAD_SLOT_ATTACK1 ) || TryToEnterPistolSlot( SQUAD_SLOT_ATTACK2 ) )
  				return SCHED_RANGE_ATTACK1;
  		}
  
  		if ( HasCondition( COND_NO_PRIMARY_AMMO ) )
  			return SCHED_RELOAD;
  		return SCHED_RUN_RANDOM;
  
  	case SCHED_RANGE_ATTACK1:
  		Assert( !HasCondition( COND_NO_PRIMARY_AMMO ) );
  
  		if( !m_fWeaponDrawn )
  		{
  			return SCHED_METROPOLICE_DRAW_PISTOL;
  		}
  
  		if( Weapon_OwnsThisType( "weapon_smg1" ) )
  		{
  			if ( IsEnemyInAnAirboat() )
  			{
  				int nSched = SelectStitchSchedule();
  				if ( nSched != SCHED_NONE )
  					return nSched;
  			}
  
  			if ( ShouldAttemptToStitch() )
  			{
  				return SCHED_METROPOLICE_SMG_BURST_ATTACK;
  			}
  			else
  			{
  				return SCHED_METROPOLICE_SMG_NORMAL_ATTACK;
  			}
  		}
  		break;
  	case SCHED_METROPOLICE_ADVANCE:
  		if ( m_NextChargeTimer.Expired() && metropolice_charge.GetBool() )
  		{	
  			if ( Weapon_OwnsThisType( "weapon_pistol" ) )
  			{
  				if (  GetEnemy() && GetEnemy()->GetAbsOrigin().DistToSqr( GetAbsOrigin() ) > 300*300 )
  				{
  					if ( OccupyStrategySlot( SQUAD_SLOT_POLICE_CHARGE_ENEMY ) )
  					{
  						m_NextChargeTimer.Set( 3, 7 );
  						return SCHED_METROPOLICE_CHARGE;
  					}
  				}
  			}
  			else
  			{
  				m_NextChargeTimer.Set( 99999 );
  			}
  		}
  		break;
  	}
  
  
  	return BaseClass::TranslateSchedule( scheduleType );
  }
  
  
  //-----------------------------------------------------------------------------
  // Can't move and shoot when the enemy is an airboat
  //-----------------------------------------------------------------------------
  bool CNPC_MetroPolice::ShouldMoveAndShoot()
  {
  	if ( HasSpawnFlags( SF_METROPOLICE_ARREST_ENEMY ) )
  		return false;
  
  	if ( ShouldAttemptToStitch() )
  		return false;
  
  	return BaseClass::ShouldMoveAndShoot();
  }
  
  
  //-----------------------------------------------------------------------------
  // Only move and shoot when attacking
  //-----------------------------------------------------------------------------
  bool CNPC_MetroPolice::OnBeginMoveAndShoot()
  {
  	if ( BaseClass::OnBeginMoveAndShoot() )
  	{
  		if( HasStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) )
  			return true; // already have the slot I need
  
  		if( OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) )
  			return true;
  	}
  
  	return false;
  }
  
  //-----------------------------------------------------------------------------
  // Only move and shoot when attacking
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::OnEndMoveAndShoot()
  {
  	VacateStrategySlot();
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  // Input  : pTask - 
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::StartTask( const Task_t *pTask )
  {
  	switch (pTask->iTask)
  	{
  	case TASK_METROPOLICE_WAIT_FOR_SENTENCE:
  		{
  			if ( FOkToMakeSound( pTask->flTaskData ) )
  			{
  				TaskComplete();
  			}
  		}
  		break;
  
  	case TASK_METROPOLICE_GET_PATH_TO_PRECHASE:
  		{
  			Assert( m_vecPreChaseOrigin != vec3_origin );
  			if ( GetNavigator()->SetGoal( m_vecPreChaseOrigin ) )
  			{
  				QAngle vecAngles( 0, m_flPreChaseYaw, 0 );
  				GetNavigator()->SetArrivalDirection( vecAngles );
  				TaskComplete();
  			}
  			else
  			{
  				TaskFail( FAIL_NO_ROUTE );
  			}
  			break;
  		}
  
  	case TASK_METROPOLICE_CLEAR_PRECHASE:
  		{
  			m_vecPreChaseOrigin = vec3_origin;
  			m_flPreChaseYaw = 0;
  			TaskComplete();
  			break;
  		}
  
  	case TASK_METROPOLICE_ACTIVATE_BATON:
  		{
  			// Simply early out if we're in here without a baton
  			if ( HasBaton() == false )
  			{
  				TaskComplete();
  				break;
  			}
  
  			bool activate = ( pTask->flTaskData != 0 );
  
  			if ( activate )
  			{
  				if ( BatonActive() || m_bShouldActivateBaton == false )
  				{
  					TaskComplete();
  					break;
  				}
  
  				m_Sentences.Speak( "METROPOLICE_ACTIVATE_BATON", SENTENCE_PRIORITY_NORMAL, SENTENCE_CRITERIA_NORMAL );
  				SetIdealActivity( (Activity) ACT_ACTIVATE_BATON );
  			}
  			else
  			{
  				if ( BatonActive() == false || m_bShouldActivateBaton )
  				{
  					TaskComplete();
  					break;
  				}
  
  				m_Sentences.Speak( "METROPOLICE_DEACTIVATE_BATON", SENTENCE_PRIORITY_NORMAL, SENTENCE_CRITERIA_NORMAL );
  				SetIdealActivity( (Activity) ACT_DEACTIVATE_BATON );
  			}
  		}
  		break;
  
  	case TASK_METROPOLICE_DIE_INSTANTLY:
  		{
  			CTakeDamageInfo info;
  
  			info.SetAttacker( this );
  			info.SetInflictor( this );
  			info.SetDamage( m_iHealth );
  			info.SetDamageType( pTask->flTaskData );
  			info.SetDamageForce( Vector( 0.1, 0.1, 0.1 ) );
  
  			TakeDamage( info );
  
  			TaskComplete();
  		}
  		break;
  
  	case TASK_METROPOLICE_RESET_LEDGE_CHECK_TIME:
  		m_flNextLedgeCheckTime = gpGlobals->curtime;
  		TaskComplete();
  		break;
  
  	case TASK_METROPOLICE_LEAD_ARREST_ENEMY:
  	case TASK_METROPOLICE_ARREST_ENEMY:
  		m_flTaskCompletionTime = gpGlobals->curtime + pTask->flTaskData;
  		break;
  
  	case TASK_METROPOLICE_SIGNAL_FIRING_TIME:
  		EnemyResistingArrest();
  		TaskComplete();
  		break;
  
  	case TASK_METROPOLICE_GET_PATH_TO_STITCH:
  		{
  			if ( !ShouldAttemptToStitch() )
  			{
  				TaskFail( FAIL_NO_ROUTE );
  				break;
  			}
  
  			Vector vecTarget, vecTargetVel;
  			PredictShootTargetPosition( 0.5f, 0.0f, 0.0f, &vecTarget, &vecTargetVel );
  
  			vecTarget -= GetAbsOrigin();
  			vecTarget.z = 0.0f;
  			float flDist = VectorNormalize( vecTarget );
  			if ( GetNavigator()->SetVectorGoal( vecTarget, flDist ) )
  			{
  				TaskComplete();
  			}
  			else
  			{
  				TaskFail( FAIL_NO_ROUTE );
  			}
  		}
  		break;
  
  	// Stitching aiming
  	case TASK_METROPOLICE_AIM_STITCH_TIGHTLY:
  		SetBurstMode( true );
  		AimBurstTightGrouping( pTask->flTaskData );
  		TaskComplete();
  		break;
  
  	case TASK_METROPOLICE_AIM_STITCH_AT_PLAYER:
  		SetBurstMode( true );
  		AimBurstAtEnemy( pTask->flTaskData );
  		TaskComplete();
  		break;
  
  	case TASK_METROPOLICE_AIM_STITCH_AT_AIRBOAT:
  		if ( IsEnemyInAnAirboat() )
  		{
  			SetBurstMode( true );
  			AimBurstAtEnemy( pTask->flTaskData );
  			TaskComplete();
  		}
  		else
  		{
  			TaskFail(FAIL_NO_TARGET);
  		}
  		break;
  
  	case TASK_METROPOLICE_AIM_STITCH_IN_FRONT_OF_AIRBOAT:
  		if ( IsEnemyInAnAirboat() )
  		{
  			SetBurstMode( true );
  			AimBurstInFrontOfEnemy( pTask->flTaskData );
  			TaskComplete();
  		}
  		else
  		{
  			TaskFail(FAIL_NO_TARGET);
  		}
  		break;
  
  	case TASK_METROPOLICE_AIM_STITCH_ALONG_SIDE_OF_AIRBOAT:
  		if ( IsEnemyInAnAirboat() )
  		{
  			SetBurstMode( true );
  			AimBurstAlongSideOfEnemy( pTask->flTaskData );
  			TaskComplete();
  		}
  		else
  		{
  			TaskFail(FAIL_NO_TARGET);
  		}
  		break;
  
  	case TASK_METROPOLICE_AIM_STITCH_BEHIND_AIRBOAT:
  		if ( IsEnemyInAnAirboat() )
  		{
  			SetBurstMode( true );
  			AimBurstBehindEnemy( pTask->flTaskData );
  			TaskComplete();
  		}
  		else
  		{
  			TaskFail(FAIL_NO_TARGET);
  		}
  		break;
  
  	case TASK_METROPOLICE_BURST_ATTACK:
  		ResetIdealActivity( ACT_RANGE_ATTACK1 );
  		break;
  
  	case TASK_METROPOLICE_STOP_FIRE_BURST:
  		{
  			SetBurstMode( false );
  			TaskComplete();
  		}
  		break;
  
  	case TASK_METROPOLICE_HARASS:
  		{
  			if( !( m_spawnflags & SF_METROPOLICE_NOCHATTER ) )
  			{
  				if( GetEnemy() && GetEnemy()->GetWaterLevel() > 0 )
  				{
  					EmitSound( "NPC_MetroPolice.WaterSpeech" );
  				}
  				else
  				{
  					EmitSound( "NPC_MetroPolice.HidingSpeech" );
  				}
  			}
  
  			TaskComplete();
  		}
  		break;
  
  	case TASK_METROPOLICE_RELOAD_FOR_BURST:
  		{
  			if (GetActiveWeapon())
  			{
  				int nDesiredShotCount = CountShotsInTime( pTask->flTaskData );
  
  				// Do our fake reload to simulate a bigger clip without having to change the SMG1
  				int nAddCount = nDesiredShotCount - GetActiveWeapon()->Clip1();
  				if ( nAddCount > 0 )
  				{
  					if ( m_nBurstReloadCount >= nAddCount )
  					{
  						GetActiveWeapon()->m_iClip1 += nAddCount;
  						m_nBurstReloadCount -= nAddCount;
  					}
  				}
  
  				if ( nDesiredShotCount <= GetActiveWeapon()->Clip1() )
  				{
  					TaskComplete();
  					break;
  				}
  			}
  
  			// Fake a TASK_RELOAD to make sure we've got a full clip...
  			Task_t reloadTask;
  			reloadTask.iTask = TASK_RELOAD;
  			reloadTask.flTaskData = 0.0f;
  			StartTask( &reloadTask );
  		}
  		break;
  
  	case TASK_RELOAD:
  		m_nBurstReloadCount = METROPOLICE_BURST_RELOAD_COUNT;
  		BaseClass::StartTask( pTask );
  		break;
  
  	case TASK_METROPOLICE_GET_PATH_TO_BESTSOUND_LOS:
  		{
  		}
  		break;
  
  	default:
  		BaseClass::StartTask( pTask );
  		break;
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  //
  // Run tasks!
  //
  //-----------------------------------------------------------------------------
  
  
  //-----------------------------------------------------------------------------
  // He's resisting arrest!
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::EnemyResistingArrest()
  {
  	// Prevent any other arrest from being made in this squad
  	// and tell them all that the player is resisting arrest!
  	
  	if ( m_pSquad != NULL )
  	{
  		AISquadIter_t iter;
  		CAI_BaseNPC *pSquadmate = m_pSquad->GetFirstMember( &iter );
  		while ( pSquadmate )
  		{
  			pSquadmate->RemoveSpawnFlags( SF_METROPOLICE_ARREST_ENEMY );
  			pSquadmate->SetCondition( COND_METROPOLICE_ENEMY_RESISTING_ARREST );
  			pSquadmate = m_pSquad->GetNextMember( &iter );
  		}
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  // Input  : pTask - 
  //-----------------------------------------------------------------------------
  #define FLEEING_DISTANCE_SQR (100 * 100)
  
  void CNPC_MetroPolice::RunTask( const Task_t *pTask )
  {
  	switch( pTask->iTask )
  	{
  	case TASK_WAIT_FOR_MOVEMENT:
  		BaseClass::RunTask( pTask );
  		break;
  
  	case TASK_METROPOLICE_WAIT_FOR_SENTENCE:
  		{
  			if ( FOkToMakeSound( pTask->flTaskData ) )
  			{
  				TaskComplete();
  			}
  		}
  		break;
  
  	case TASK_METROPOLICE_ACTIVATE_BATON:
  		AutoMovement();
  		
  		if ( IsActivityFinished() )
  		{
  			TaskComplete();
  		}	
  		break;
  
  	case TASK_METROPOLICE_BURST_ATTACK:
  		{
  			AutoMovement( );
  
  			Vector vecAimPoint;
  			GetMotor()->SetIdealYawToTargetAndUpdate( m_vecBurstTargetPos, AI_KEEP_YAW_SPEED );
  
  			if ( IsActivityFinished() )
  			{
  				if ( GetShotRegulator()->IsInRestInterval() )
  				{
  					TaskComplete();
  				}
  				else
  				{
  					OnRangeAttack1();
  					ResetIdealActivity( ACT_RANGE_ATTACK1 );
  				}
  			}
  		}
  		break;
  
  	case TASK_METROPOLICE_RELOAD_FOR_BURST:
  		{
  			// Fake a TASK_RELOAD
  			Task_t reloadTask;
  			reloadTask.iTask = TASK_RELOAD;
  			reloadTask.flTaskData = 0.0f;
  			RunTask( &reloadTask );
  		}
  		break;
  
  	case TASK_METROPOLICE_LEAD_ARREST_ENEMY:
  	case TASK_METROPOLICE_ARREST_ENEMY:
  		{
  			if ( !GetEnemy() )
  			{
  				TaskComplete();
  				break;
  			}
  
  			if ( gpGlobals->curtime >= m_flTaskCompletionTime )
  			{
  				TaskComplete();
  				break;
  			}
  
  			// Complete the arrest after the last squad member has a bead on the enemy
  			// But only if you're the first guy who saw him
  			if ( pTask->iTask == TASK_METROPOLICE_LEAD_ARREST_ENEMY )
  			{
  				int nArrestCount = SquadArrestCount();
  				if ( nArrestCount == m_pSquad->NumMembers() )
  				{
  					TaskComplete();
  					break;
  				}
  
  				// Do a distance check of the enemy from his initial position.
  				// Shoot if he gets too far.
  				if ( m_vSavePosition.DistToSqr( GetEnemy()->GetAbsOrigin() ) > FLEEING_DISTANCE_SQR )
  				{
  					SpeakSentence( METROPOLICE_SENTENCE_HES_RUNNING );
  					EnemyResistingArrest();
  					break;
  				}
  			}
  
  			// Keep aiming at the enemy
  			if ( GetEnemy() && FacingIdeal() )
  			{
  				float flNewIdealYaw = CalcIdealYaw( GetEnemy()->EyePosition() );
  				if ( fabs(UTIL_AngleDiff( GetMotor()->GetIdealYaw(), flNewIdealYaw )) >= 45.0f )
  				{
  					GetMotor()->SetIdealYawToTarget( GetEnemy()->EyePosition() );
  					SetTurnActivity(); 
  				}
  			}
  			GetMotor()->UpdateYaw();
  		}
  		break;
  
  	case TASK_METROPOLICE_GET_PATH_TO_BESTSOUND_LOS:
  		{
  			switch( GetTaskInterrupt() )
  			{
  			case 0:
  				{
  					CSound *pSound = GetBestSound();
  					if (!pSound)
  					{
  						TaskFail(FAIL_NO_SOUND);
  					}
  					else
  					{
  						float flMaxRange = 2000;
  						float flMinRange = 0;
  						if ( GetActiveWeapon() )
  						{
  							flMaxRange = MAX( GetActiveWeapon()->m_fMaxRange1, GetActiveWeapon()->m_fMaxRange2 );
  							flMinRange = MIN( GetActiveWeapon()->m_fMinRange1, GetActiveWeapon()->m_fMinRange2 );
  						}
  
  						// Check against NPC's max range
  						if (flMaxRange > m_flDistTooFar)
  						{
  							flMaxRange = m_flDistTooFar;
  						}
  
  						// Why not doing lateral LOS first?
  
  						Vector losTarget = pSound->GetSoundReactOrigin();
  						if ( GetTacticalServices()->FindLos( pSound->GetSoundReactOrigin(), losTarget, flMinRange, flMaxRange, 1.0, &m_vInterruptSavePosition ) )
  						{
  							TaskInterrupt();
  						}
  						else
  						{
  							TaskFail(FAIL_NO_SHOOT);
  						}
  					}
  				}
  				break;
  
  			case 1:
  				{
  					AI_NavGoal_t goal( m_vInterruptSavePosition, ACT_RUN, AIN_HULL_TOLERANCE );
  					GetNavigator()->SetGoal( goal );
  				}
  				break;
  			}
  		}
  		break;
  
  	default:
  		BaseClass::RunTask( pTask );
  		break;
  	}
  }
  
  
  		
  //-----------------------------------------------------------------------------
  // Purpose: 
  // Input  : pevInflictor - 
  //			pAttacker - 
  //			flDamage - 
  //			bitsDamageType - 
  // Output : int
  //-----------------------------------------------------------------------------
  int CNPC_MetroPolice::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
  {
  	CTakeDamageInfo info = inputInfo;
  
  	if ( HasSpawnFlags( SF_METROPOLICE_ARREST_ENEMY ) )
  	{
  		EnemyResistingArrest();
  	}
  
  #if 0
  	// Die instantly from a hit in idle/alert states
  	if( m_NPCState == NPC_STATE_IDLE || m_NPCState == NPC_STATE_ALERT )
  	{
  		info.SetDamage( m_iHealth );
  	}
  #endif //0
  
  	if (info.GetAttacker() == GetEnemy())
  	{
  		// Keep track of recent damage by my attacker. If it seems like we're
  		// being killed, consider running off and hiding.
  		m_nRecentDamage += info.GetDamage();
  		m_flRecentDamageTime = gpGlobals->curtime;
  	}
  
  	return BaseClass::OnTakeDamage_Alive( info ); 
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: I want to deploy a manhack. Can I?
  //-----------------------------------------------------------------------------
  bool CNPC_MetroPolice::CanDeployManhack( void )
  {
  	if ( HasSpawnFlags( SF_METROPOLICE_NO_MANHACK_DEPLOY ) )
  		return false;
  
  	// Nope, already have one out.
  	if( m_hManhack != NULL )
  		return false;
  
  	// Nope, don't have any!
  	if( m_iManhacks < 1 )
  		return false;
  
  	return true;
  }
   
  //-----------------------------------------------------------------------------
  // Purpose: Allows for modification of the interrupt mask for the current schedule.
  //			In the most cases the base implementation should be called first.
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::BuildScheduleTestBits( void )
  {
  	BaseClass::BuildScheduleTestBits();
  
  	if ( PlayerIsCriminal() == false )
  	{
  		SetCustomInterruptCondition( COND_METROPOLICE_PHYSOBJECT_ASSAULT );
  	}
  
  	//FIXME: Always interrupt for now
  	if ( !IsInAScript() && 
  		 !IsCurSchedule( SCHED_METROPOLICE_SHOVE ) &&
  		 !IsCurSchedule( SCHED_MELEE_ATTACK1 ) &&
  		 !IsCurSchedule( SCHED_RELOAD ) && 
  		 !IsCurSchedule( SCHED_METROPOLICE_ACTIVATE_BATON ) )
  	{
  		SetCustomInterruptCondition( COND_METROPOLICE_PLAYER_TOO_CLOSE );
  	}
  
  	if ( !IsCurSchedule( SCHED_METROPOLICE_BURNING_RUN ) && !IsCurSchedule( SCHED_METROPOLICE_BURNING_STAND ) && !IsMoving() )
  	{
  		SetCustomInterruptCondition( COND_METROPOLICE_ON_FIRE );
  	}
  
  	if (IsCurSchedule(SCHED_TAKE_COVER_FROM_ENEMY))
  	{
  		ClearCustomInterruptCondition( COND_LIGHT_DAMAGE );
  		ClearCustomInterruptCondition( COND_HEAVY_DAMAGE );
  	}
  
  	if ( !IsCurSchedule( SCHED_CHASE_ENEMY ) &&
  		 !IsCurSchedule( SCHED_METROPOLICE_ACTIVATE_BATON ) &&
  		 !IsCurSchedule( SCHED_METROPOLICE_DEACTIVATE_BATON ) &&
  		 !IsCurSchedule( SCHED_METROPOLICE_SHOVE ) && 
  		 !IsCurSchedule( SCHED_METROPOLICE_RETURN_TO_PRECHASE ) )
  	{
  		SetCustomInterruptCondition( COND_METROPOLICE_CHANGE_BATON_STATE );
  	}
  
  	if ( IsCurSchedule( SCHED_MELEE_ATTACK1 ) )
  	{
  		if ( gpGlobals->curtime - m_flLastDamageFlinchTime < 10.0 )
  		{
  			ClearCustomInterruptCondition( COND_LIGHT_DAMAGE );
  			ClearCustomInterruptCondition( COND_HEAVY_DAMAGE );
  		}
  	}
  	else if ( HasBaton() && IsCurSchedule( SCHED_COMBAT_FACE ) && !m_BatonSwingTimer.Expired() )
  	{
  		ClearCustomInterruptCondition( COND_CAN_MELEE_ATTACK1 );
  	}
  }
  
  //-----------------------------------------------------------------------------
  //-----------------------------------------------------------------------------
  WeaponProficiency_t CNPC_MetroPolice::CalcWeaponProficiency( CBaseCombatWeapon *pWeapon )
  {
  	if( FClassnameIs( pWeapon, "weapon_pistol" ) )
  	{
  		return WEAPON_PROFICIENCY_POOR;
  	}
  
  	if( FClassnameIs( pWeapon, "weapon_smg1" ) )
  	{
  		return WEAPON_PROFICIENCY_VERY_GOOD;
  	}
  
  	return BaseClass::CalcWeaponProficiency( pWeapon );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::GatherConditions( void )
  {
  	BaseClass::GatherConditions();
  
  	if ( m_bPlayerTooClose == false )
  	{
  		ClearCondition( COND_METROPOLICE_PLAYER_TOO_CLOSE );
  	}
  
  	CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
  	
  	// FIXME: Player can be NULL here during level transitions.
  	if ( !pPlayer )
  		return;
  
  	float distToPlayerSqr = ( pPlayer->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
  	
  	// See if we're too close
  	if ( pPlayer->GetGroundEntity() == this )
  	{
  		// Always beat a player on our head
  		m_iNumPlayerHits = 0;
  		SetCondition( COND_METROPOLICE_PLAYER_TOO_CLOSE );
  	}
  	else if ( (distToPlayerSqr < (42.0f*42.0f) && FVisible(pPlayer)) )
  	{
  		// Ignore the player if we've been beating him, but not if we haven't moved
  		if ( m_iNumPlayerHits < 3 || m_vecPreChaseOrigin == vec3_origin )
  		{
  			SetCondition( COND_METROPOLICE_PLAYER_TOO_CLOSE );
  		}
  	}
  	else
  	{
  		ClearCondition( COND_METROPOLICE_PLAYER_TOO_CLOSE );
  
  		// Don't clear out the player hit count for a few seconds after we last hit him
  		// This avoids states where two metropolice have the player pinned between them.
  		if ( (gpGlobals->curtime - GetLastAttackTime()) > 3 )
  		{
  			m_iNumPlayerHits = 0;
  		}
  
  		m_bPlayerTooClose = false;
  	}
  
  	if( metropolice_move_and_melee.GetBool() )
  	{
  		if( IsMoving() && HasCondition(COND_CAN_MELEE_ATTACK1) && HasBaton() )
  		{
  			if ( m_BatonSwingTimer.Expired() )
  			{
  				m_BatonSwingTimer.Set( 1.0, 1.75 );
  
  				Activity activity = TranslateActivity( ACT_MELEE_ATTACK_SWING_GESTURE );
  				Assert( activity != ACT_INVALID );
  				AddGesture( activity );
  			}
  		}
  	}
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  // Output : Returns true on success, false on failure.
  //-----------------------------------------------------------------------------
  bool CNPC_MetroPolice::HasBaton( void )
  {
  	CBaseCombatWeapon *pWeapon = GetActiveWeapon();
  
  	if ( pWeapon )
  		return FClassnameIs( pWeapon, "weapon_stunstick" );
  
  	return false;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  // Output : Returns true on success, false on failure.
  //-----------------------------------------------------------------------------
  bool CNPC_MetroPolice::BatonActive( void )
  {
  #ifndef HL2MP
  
  	CWeaponStunStick *pStick = dynamic_cast<CWeaponStunStick *>(GetActiveWeapon());
  
  	if ( pStick )
  		return pStick->GetStunState();
  #endif
  
  	return false;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  // Input  : state - 
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::SetBatonState( bool state )
  {
  	if ( !HasBaton() )
  		return;
  
  	if ( m_bShouldActivateBaton != state )
  	{
  		m_bShouldActivateBaton = state;
  		SetCondition( COND_METROPOLICE_CHANGE_BATON_STATE );
  	}
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  // Input  : *pSound - 
  // Output : Returns true on success, false on failure.
  //-----------------------------------------------------------------------------
  bool CNPC_MetroPolice::QueryHearSound( CSound *pSound )
  {
  	// Only behave differently if the player is pre-criminal
  	if ( PlayerIsCriminal() == false )
  	{
  		// If the person making the sound was a friend, don't respond
  		if ( pSound->IsSoundType( SOUND_DANGER ) && pSound->m_hOwner && IRelationType( pSound->m_hOwner ) == D_NU )
  			return false;
  	}
  
  	return BaseClass::QueryHearSound( pSound );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  // Input  : index - 
  //			*pEvent - 
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
  {
  	BaseClass::VPhysicsCollision( index, pEvent );
  
  	int otherIndex = !index;
  	
  	CBaseEntity *pHitEntity = pEvent->pEntities[otherIndex];
  
  	if ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
  	{
  		CHL2_Player *pPlayer = dynamic_cast<CHL2_Player *>(UTIL_PlayerByIndex( 1 ));
  
  		// See if it's being held by the player
  		if ( pPlayer != NULL && pPlayer->IsHoldingEntity( pHitEntity ) )
  		{
  			//TODO: Play an angry sentence, "Get that outta here!"
  
  			if ( IsCurSchedule( SCHED_METROPOLICE_SHOVE ) == false )
  			{
  				SetCondition( COND_METROPOLICE_PLAYER_TOO_CLOSE );
  				m_bPlayerTooClose = true;
  			}
  		}
  	}
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  // Input  : *pTarget - 
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::StunnedTarget( CBaseEntity *pTarget )
  {
  	SetLastAttackTime( gpGlobals->curtime );
  
  	if ( pTarget && pTarget->IsPlayer() )
  	{
  		m_OnStunnedPlayer.FireOutput( this, this );
  		m_iNumPlayerHits++;
  	}
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Use response for when the player is pre-criminal
  //-----------------------------------------------------------------------------
  void CNPC_MetroPolice::PrecriminalUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  {
  	if ( IsInAScript() )
  		return;
  	// Don't respond if I'm busy hating the player
  	if ( IRelationType( pActivator ) == D_HT || ((GetState() != NPC_STATE_ALERT) && (GetState() != NPC_STATE_IDLE)) )
  		return;
  	if ( PlayerIsCriminal() )
  		return;
  
  	// Treat it like the player's bothered the cop
  	IncrementPlayerCriminalStatus();
  
  	// If we've hit max warnings, and we're allowed to chase, go for it
  	if ( m_nNumWarnings == METROPOLICE_MAX_WARNINGS )
  	{
  		AdministerJustice();
  	}
  }
  
  //-----------------------------------------------------------------------------
  //
  // Schedules
  //
  //-----------------------------------------------------------------------------
  AI_BEGIN_CUSTOM_NPC( npc_metropolice, CNPC_MetroPolice )
  
  	gm_flTimeLastSpokePeek = 0;
  
  	DECLARE_ANIMEVENT( AE_METROPOLICE_BATON_ON );
  	DECLARE_ANIMEVENT( AE_METROPOLICE_BATON_OFF );
  	DECLARE_ANIMEVENT( AE_METROPOLICE_SHOVE );
  	DECLARE_ANIMEVENT( AE_METROPOLICE_START_DEPLOY );
  	DECLARE_ANIMEVENT( AE_METROPOLICE_DRAW_PISTOL );
  	DECLARE_ANIMEVENT( AE_METROPOLICE_DEPLOY_MANHACK );
  
  	DECLARE_SQUADSLOT( SQUAD_SLOT_POLICE_CHARGE_ENEMY );
  	DECLARE_SQUADSLOT( SQUAD_SLOT_POLICE_HARASS );
  	DECLARE_SQUADSLOT( SQUAD_SLOT_POLICE_DEPLOY_MANHACK );
  	DECLARE_SQUADSLOT( SQUAD_SLOT_POLICE_ATTACK_OCCLUDER1 );
  	DECLARE_SQUADSLOT( SQUAD_SLOT_POLICE_ATTACK_OCCLUDER2 );
  	DECLARE_SQUADSLOT( SQUAD_SLOT_POLICE_ARREST_ENEMY );
  
  	DECLARE_ACTIVITY( ACT_METROPOLICE_DRAW_PISTOL );
  	DECLARE_ACTIVITY( ACT_METROPOLICE_DEPLOY_MANHACK );
  	DECLARE_ACTIVITY( ACT_METROPOLICE_FLINCH_BEHIND );
  	DECLARE_ACTIVITY( ACT_PUSH_PLAYER );
  	DECLARE_ACTIVITY( ACT_MELEE_ATTACK_THRUST );
  	DECLARE_ACTIVITY( ACT_ACTIVATE_BATON );
  	DECLARE_ACTIVITY( ACT_DEACTIVATE_BATON );
  	DECLARE_ACTIVITY( ACT_WALK_BATON );
  	DECLARE_ACTIVITY( ACT_IDLE_ANGRY_BATON );
  
  	DECLARE_INTERACTION( g_interactionMetrocopStartedStitch );	
  	DECLARE_INTERACTION( g_interactionMetrocopIdleChatter );	
  	DECLARE_INTERACTION( g_interactionMetrocopClearSentenceQueues );
  
  	DECLARE_TASK( TASK_METROPOLICE_HARASS );
  	DECLARE_TASK( TASK_METROPOLICE_DIE_INSTANTLY );
  	DECLARE_TASK( TASK_METROPOLICE_BURST_ATTACK );
  	DECLARE_TASK( TASK_METROPOLICE_STOP_FIRE_BURST );
  	DECLARE_TASK( TASK_METROPOLICE_AIM_STITCH_AT_PLAYER );
  	DECLARE_TASK( TASK_METROPOLICE_AIM_STITCH_AT_AIRBOAT );
  	DECLARE_TASK( TASK_METROPOLICE_AIM_STITCH_IN_FRONT_OF_AIRBOAT );
  	DECLARE_TASK( TASK_METROPOLICE_AIM_STITCH_TIGHTLY );
  	DECLARE_TASK( TASK_METROPOLICE_AIM_STITCH_ALONG_SIDE_OF_AIRBOAT );
  	DECLARE_TASK( TASK_METROPOLICE_AIM_STITCH_BEHIND_AIRBOAT );
  	DECLARE_TASK( TASK_METROPOLICE_RELOAD_FOR_BURST );
  	DECLARE_TASK( TASK_METROPOLICE_GET_PATH_TO_STITCH );
  	DECLARE_TASK( TASK_METROPOLICE_RESET_LEDGE_CHECK_TIME );
  	DECLARE_TASK( TASK_METROPOLICE_GET_PATH_TO_BESTSOUND_LOS );
  	DECLARE_TASK( TASK_METROPOLICE_ARREST_ENEMY );
  	DECLARE_TASK( TASK_METROPOLICE_LEAD_ARREST_ENEMY );
  	DECLARE_TASK( TASK_METROPOLICE_SIGNAL_FIRING_TIME );
  	DECLARE_TASK( TASK_METROPOLICE_ACTIVATE_BATON );
  	DECLARE_TASK( TASK_METROPOLICE_WAIT_FOR_SENTENCE );
  	DECLARE_TASK( TASK_METROPOLICE_GET_PATH_TO_PRECHASE );
  	DECLARE_TASK( TASK_METROPOLICE_CLEAR_PRECHASE );
  
  	DECLARE_CONDITION( COND_METROPOLICE_ON_FIRE );
  	DECLARE_CONDITION( COND_METROPOLICE_ENEMY_RESISTING_ARREST );
  //	DECLARE_CONDITION( COND_METROPOLICE_START_POLICING );
  	DECLARE_CONDITION( COND_METROPOLICE_PLAYER_TOO_CLOSE );
  	DECLARE_CONDITION( COND_METROPOLICE_CHANGE_BATON_STATE );
  	DECLARE_CONDITION( COND_METROPOLICE_PHYSOBJECT_ASSAULT );
  
  
  	//=========================================================
  //=========================================================
  DEFINE_SCHEDULE
  (
  	SCHED_METROPOLICE_WAKE_ANGRY,
  
  	"	Tasks"
  	"		TASK_STOP_MOVING				0"
  	"		TASK_SET_ACTIVITY				ACTIVITY:ACT_IDLE"
  	"		TASK_FACE_ENEMY					0"
  	"	"
  	"	Interrupts"
  );
  
  
  //=========================================================
  // > InvestigateSound
  //
  //	sends a monster to the location of the
  //	sound that was just heard to check things out.
  //=========================================================
  DEFINE_SCHEDULE
  (
  	SCHED_METROPOLICE_INVESTIGATE_SOUND,
  
  	"	Tasks"
  	"		TASK_STOP_MOVING				0"
  	"		TASK_STORE_LASTPOSITION			0"
  	"		TASK_METROPOLICE_GET_PATH_TO_BESTSOUND_LOS		0"
  	"		TASK_FACE_IDEAL					0"
  //	"		TASK_SET_TOLERANCE_DISTANCE		32"
  	"		TASK_RUN_PATH					0"
  	"		TASK_WAIT_FOR_MOVEMENT			0"
  	"		TASK_STOP_MOVING				0"
  	"		TASK_WAIT						5"
  	"		TASK_GET_PATH_TO_LASTPOSITION	0"
  	"		TASK_WALK_PATH					0"
  	"		TASK_WAIT_FOR_MOVEMENT			0"
  	"		TASK_STOP_MOVING				0"
  	"		TASK_CLEAR_LASTPOSITION			0"
  	"		TASK_FACE_REASONABLE			0"
  	""
  	"	Interrupts"
  	"		COND_NEW_ENEMY"
  	"		COND_SEE_FEAR"
  	"		COND_SEE_ENEMY"
  	"		COND_LIGHT_DAMAGE"
  	"		COND_HEAVY_DAMAGE"
  	"		COND_HEAR_DANGER"
  );
  
  
  //=========================================================
  //=========================================================
  DEFINE_SCHEDULE
  (
  	SCHED_METROPOLICE_HARASS,
  
  	"	Tasks"
  	"		TASK_STOP_MOVING				0"
  	"		TASK_FACE_ENEMY					0"
  	"		TASK_WAIT_FACE_ENEMY			6"
  	"		TASK_METROPOLICE_HARASS			0"
  	"		TASK_WAIT_PVS					0"
  	"	"
  	"	Interrupts"
  	"	"
  	"		COND_CAN_RANGE_ATTACK1"
  	"		COND_NEW_ENEMY"
  );
  
  
  //=========================================================
  //=========================================================
  DEFINE_SCHEDULE
  (
  	SCHED_METROPOLICE_DRAW_PISTOL,
  
  	"	Tasks"
  	"		TASK_STOP_MOVING				0"
  	"		TASK_PLAY_SEQUENCE_FACE_ENEMY	ACTIVITY:ACT_METROPOLICE_DRAW_PISTOL"
  	"		TASK_WAIT_FACE_ENEMY			0.1"
  	"	"
  	"	Interrupts"
  	"	"
  );
  
  
  //=========================================================
  // > ChaseEnemy
  //=========================================================
  DEFINE_SCHEDULE
  (
  	SCHED_METROPOLICE_CHASE_ENEMY,
  
  	"	Tasks"
  	"		TASK_STOP_MOVING				0"
  	"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_METROPOLICE_ESTABLISH_LINE_OF_FIRE"
  	"		TASK_SET_TOLERANCE_DISTANCE		24"
  	"		TASK_GET_CHASE_PATH_TO_ENEMY	300"
  	"		TASK_SPEAK_SENTENCE				6"	// METROPOLICE_SENTENCE_MOVE_INTO_POSITION
  	"		TASK_RUN_PATH					0"
  	"		TASK_METROPOLICE_RESET_LEDGE_CHECK_TIME 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_MELEE_ATTACK1"
  	"		COND_CAN_RANGE_ATTACK2"
  	"		COND_CAN_MELEE_ATTACK2"
  	"		COND_TOO_CLOSE_TO_ATTACK"
  	"		COND_TASK_FAILED"
  	"		COND_LOST_ENEMY"
  	"		COND_BETTER_WEAPON_AVAILABLE"
  	"		COND_HEAR_DANGER"
  );
  
  
  DEFINE_SCHEDULE
  (
  	SCHED_METROPOLICE_ESTABLISH_LINE_OF_FIRE,
  
  	"	Tasks "
  	"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_FAIL_ESTABLISH_LINE_OF_FIRE"
  	"		TASK_FACE_ENEMY					0"
  	"		TASK_SET_TOLERANCE_DISTANCE		48"
  	"		TASK_GET_PATH_TO_ENEMY_LKP_LOS	0"
  	"		TASK_SPEAK_SENTENCE				6"	// METROPOLICE_SENTENCE_MOVE_INTO_POSITION
  	"		TASK_RUN_PATH					0"
  	"		TASK_METROPOLICE_RESET_LEDGE_CHECK_TIME 0"
  	"		TASK_WAIT_FOR_MOVEMENT			0"
  	"		TASK_SET_SCHEDULE				SCHEDULE:SCHED_COMBAT_FACE"
  	"	"
  	"	Interrupts "
  	"		COND_NEW_ENEMY"
  	"		COND_ENEMY_DEAD"
  	"		COND_CAN_RANGE_ATTACK1"
  	"		COND_CAN_RANGE_ATTACK2"
  	"		COND_CAN_MELEE_ATTACK1"
  	"		COND_CAN_MELEE_ATTACK2"
  	"		COND_HEAR_DANGER"
  	"		COND_HEAVY_DAMAGE"
  );
  
  
  DEFINE_SCHEDULE
  (
  	SCHED_METROPOLICE_ESTABLISH_STITCH_LINE_OF_FIRE,
  
  	"	Tasks "
  	"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_FAIL_ESTABLISH_LINE_OF_FIRE"
  	"		TASK_FACE_ENEMY					0"
  	"		TASK_SET_TOLERANCE_DISTANCE		48"
  	"		TASK_METROPOLICE_GET_PATH_TO_STITCH	0"
  	"		TASK_RUN_PATH					0"
  	"		TASK_WAIT_FOR_MOVEMENT			0"
  	"		TASK_SET_SCHEDULE				SCHEDULE:SCHED_COMBAT_FACE"
  	"	"
  	"	Interrupts "
  	"		COND_NEW_ENEMY"
  	"		COND_ENEMY_DEAD"
  	"		COND_HEAR_DANGER"
  	"		COND_HEAVY_DAMAGE"
  );
  
  
  //=========================================================
  // The uninterruptible portion of this behavior, whereupon 
  // the police actually releases the manhack.
  //=========================================================
  DEFINE_SCHEDULE
  (
  	SCHED_METROPOLICE_DEPLOY_MANHACK,
  
  	"	Tasks"
  	"		TASK_SPEAK_SENTENCE					5"	// METROPOLICE_SENTENCE_DEPLOY_MANHACK
  	"		TASK_PLAY_SEQUENCE					ACTIVITY:ACT_METROPOLICE_DEPLOY_MANHACK"
  	"	"
  	"	Interrupts"
  	"	"
  );
  
  
  //===============================================
  //===============================================
  
  DEFINE_SCHEDULE
  (
  	SCHED_METROPOLICE_ADVANCE,
  
  	"	Tasks"
  	"		TASK_STOP_MOVING					0"
  	"		TASK_SET_ACTIVITY					ACTIVITY:ACT_IDLE_ANGRY"
  	"		TASK_FACE_ENEMY						0"
  	"		TASK_WAIT_FACE_ENEMY				1" // give the guy some time to come out on his own
  	"		TASK_WAIT_FACE_ENEMY_RANDOM			3" 
  	"		TASK_GET_PATH_TO_ENEMY_LOS			0"
  	"		TASK_RUN_PATH						0"
  	"		TASK_WAIT_FOR_MOVEMENT				0"
  	"		TASK_SET_ACTIVITY					ACTIVITY:ACT_IDLE_ANGRY"
  	"		TASK_FACE_ENEMY						0"
  	""
  	"	Interrupts"
  	"		COND_CAN_RANGE_ATTACK1"
  	"		COND_ENEMY_DEAD"
  	""
  );
  
  //===============================================
  //===============================================
  
  DEFINE_SCHEDULE
  (
  	SCHED_METROPOLICE_CHARGE,
  
  	"	Tasks"
  	"		TASK_STOP_MOVING				0"
  	"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_METROPOLICE_ADVANCE"
  //	"		TASK_SET_TOLERANCE_DISTANCE		24"
  	"		TASK_STORE_LASTPOSITION			0"
  	"		TASK_GET_CHASE_PATH_TO_ENEMY	300"
  	"		TASK_RUN_PATH_FOR_UNITS			150"
  	"		TASK_STOP_MOVING				1"
  	"		TASK_FACE_ENEMY			0"
  	""
  	"	Interrupts"
  	"		COND_NEW_ENEMY"
  	"		COND_ENEMY_DEAD"
  	"		COND_LOST_ENEMY"
  	"		COND_CAN_MELEE_ATTACK1"
  	"		COND_CAN_MELEE_ATTACK2"
  	"		COND_HEAR_DANGER"
  	"		COND_METROPOLICE_PLAYER_TOO_CLOSE"
  );
  
  //=========================================================
  //=========================================================
  DEFINE_SCHEDULE
  (
  	SCHED_METROPOLICE_BURNING_RUN,
  
  	"	Tasks"
  	"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_METROPOLICE_BURNING_STAND"
  	"		TASK_SET_TOLERANCE_DISTANCE		24"
  	"		TASK_GET_PATH_TO_ENEMY			0"
  	"		TASK_RUN_PATH_TIMED				10"	
  	"		TASK_METROPOLICE_DIE_INSTANTLY	0"
  	"	"
  	"	Interrupts"
  );
  
  //=========================================================
  //=========================================================
  DEFINE_SCHEDULE
  (
  	SCHED_METROPOLICE_BURNING_STAND,
  
  	"	Tasks"
  	"		TASK_SET_ACTIVITY				ACTIVITY:ACT_IDLE_ON_FIRE"
  	"		TASK_WAIT						1.5"
  	"		TASK_METROPOLICE_DIE_INSTANTLY	DMG_BURN"
  	"		TASK_WAIT						1.0"
  	"	"
  	"	Interrupts"
  );
  
  //=========================================================
  //=========================================================
  DEFINE_SCHEDULE
  (
  	SCHED_METROPOLICE_RETURN_TO_PRECHASE,
  
  	"	Tasks"
  	"		TASK_WAIT_RANDOM						1"
  	"		TASK_METROPOLICE_GET_PATH_TO_PRECHASE	0"
  	"		TASK_WALK_PATH							0"
  	"		TASK_WAIT_FOR_MOVEMENT					0"
  	"		TASK_STOP_MOVING						0"
  	"		TASK_METROPOLICE_CLEAR_PRECHASE			0"
  	"	"
  	"	Interrupts"
  	"		COND_NEW_ENEMY"
  	"		COND_CAN_MELEE_ATTACK1"
  	"		COND_CAN_MELEE_ATTACK2"
  	"		COND_TASK_FAILED"
  	"		COND_LOST_ENEMY"
  	"		COND_HEAR_DANGER"
  );
  
  //===============================================
  //===============================================
  DEFINE_SCHEDULE
  (
  	SCHED_METROPOLICE_ALERT_FACE_BESTSOUND,
  
  	"	Tasks"
  	"		TASK_SPEAK_SENTENCE		7"	// METROPOLICE_SENTENCE_HEARD_SOMETHING
  	"		TASK_SET_SCHEDULE		SCHEDULE:SCHED_ALERT_FACE_BESTSOUND"
  	""
  	"	Interrupts"
  	""
  )
  
  
  //===============================================
  //===============================================
  DEFINE_SCHEDULE
  (
  	SCHED_METROPOLICE_ENEMY_RESISTING_ARREST,
  
  	"	Tasks"
  	"		TASK_METROPOLICE_SIGNAL_FIRING_TIME		0"
  	""
  	"	Interrupts"
  	""
  )
  
  
  //===============================================
  //===============================================
  DEFINE_SCHEDULE
  (
  	SCHED_METROPOLICE_WARN_AND_ARREST_ENEMY,
  
  	"	Tasks"
  	"		TASK_SET_FAIL_SCHEDULE				SCHEDULE:SCHED_METROPOLICE_ENEMY_RESISTING_ARREST"
  	"		TASK_STOP_MOVING					0"
  	"		TASK_PLAY_SEQUENCE_FACE_ENEMY		ACTIVITY:ACT_IDLE_ANGRY"
  	"		TASK_SPEAK_SENTENCE					0"	// "Freeze!"
  	"		TASK_METROPOLICE_ARREST_ENEMY		0.5"
  	"		TASK_STORE_ENEMY_POSITION_IN_SAVEPOSITION	0"
  	"		TASK_METROPOLICE_ARREST_ENEMY		1"
  	"		TASK_METROPOLICE_WAIT_FOR_SENTENCE	1"
  	"		TASK_SPEAK_SENTENCE					1"	// "He's over here!"
  	"		TASK_METROPOLICE_LEAD_ARREST_ENEMY	5"
  	"		TASK_METROPOLICE_ARREST_ENEMY		2"
  	"		TASK_METROPOLICE_WAIT_FOR_SENTENCE	1"
  	"		TASK_SPEAK_SENTENCE					3"	// "Take him down!"
  	"		TASK_METROPOLICE_ARREST_ENEMY		1.5"
  	"		TASK_METROPOLICE_WAIT_FOR_SENTENCE	2"
  	"		TASK_METROPOLICE_SIGNAL_FIRING_TIME	0"
  	""
  	"	Interrupts"
  	"		COND_NEW_ENEMY"
  	"		COND_LIGHT_DAMAGE"
  	"		COND_HEAVY_DAMAGE"
  	"		COND_HEAR_DANGER"
  	"		COND_ENEMY_DEAD"
  	"		COND_METROPOLICE_ENEMY_RESISTING_ARREST"
  	""
  );
  
  //===============================================
  //===============================================
  DEFINE_SCHEDULE
  (
  	SCHED_METROPOLICE_ARREST_ENEMY,
  
  	"	Tasks"
  	"		TASK_SET_FAIL_SCHEDULE				SCHEDULE:SCHED_METROPOLICE_ENEMY_RESISTING_ARREST"
  	"		TASK_GET_PATH_TO_ENEMY_LOS			0"
  	"		TASK_RUN_PATH						0"
  	"		TASK_WAIT_FOR_MOVEMENT				0"
  	"		TASK_STOP_MOVING					0"
  	"		TASK_PLAY_SEQUENCE_FACE_ENEMY		ACTIVITY:ACT_IDLE_ANGRY"
  	"		TASK_METROPOLICE_WAIT_FOR_SENTENCE	0"
  	"		TASK_SPEAK_SENTENCE					4"
  	"		TASK_METROPOLICE_ARREST_ENEMY		30"
  	""
  	"	Interrupts"
  	"		COND_NEW_ENEMY"
  	"		COND_LIGHT_DAMAGE"
  	"		COND_HEAVY_DAMAGE"
  	"		COND_HEAR_DANGER"
  	"		COND_ENEMY_DEAD"
  	"		COND_METROPOLICE_ENEMY_RESISTING_ARREST"
  	"		COND_WEAPON_BLOCKED_BY_FRIEND"
  	"		COND_WEAPON_SIGHT_OCCLUDED"
  	""
  );
  
  
  //===============================================
  //===============================================
  DEFINE_SCHEDULE
  (
  	SCHED_METROPOLICE_SMG_NORMAL_ATTACK,
  
  	"	Tasks"
  	"		TASK_STOP_MOVING		0"
  	"		TASK_FACE_ENEMY			0"
  	"		TASK_ANNOUNCE_ATTACK	1"	// 1 = primary attack
  	"		TASK_METROPOLICE_STOP_FIRE_BURST	0"
  	"		TASK_RANGE_ATTACK1		0"
  	""
  	"	Interrupts"
  	"		COND_NEW_ENEMY"
  	"		COND_ENEMY_DEAD"
  	"		COND_LIGHT_DAMAGE"
  	"		COND_HEAVY_DAMAGE"
  	"		COND_ENEMY_OCCLUDED"
  	"		COND_NO_PRIMARY_AMMO"
  	"		COND_HEAR_DANGER"
  	"		COND_WEAPON_BLOCKED_BY_FRIEND"
  	"		COND_WEAPON_SIGHT_OCCLUDED"
  );
  
  
  //===============================================
  //===============================================
  DEFINE_SCHEDULE
  (
  	SCHED_METROPOLICE_SMG_BURST_ATTACK,
  
  	"	Tasks"
  	"		TASK_STOP_MOVING		0"
  	"		TASK_FACE_ENEMY			0"
  	"		TASK_ANNOUNCE_ATTACK	1"	// 1 = primary attack
  	"		TASK_METROPOLICE_RELOAD_FOR_BURST	1.4"
  	"		TASK_METROPOLICE_AIM_STITCH_AT_PLAYER	1.4"
  	"		TASK_METROPOLICE_BURST_ATTACK		0"
  	"		TASK_FACE_ENEMY			0"
  	""
  	"	Interrupts"
  	"		COND_LIGHT_DAMAGE"
  	"		COND_HEAVY_DAMAGE"
  	"		COND_NO_PRIMARY_AMMO"
  	"		COND_HEAR_DANGER"
  	"		COND_WEAPON_BLOCKED_BY_FRIEND"
  
  );
  
  //===============================================
  //===============================================
  DEFINE_SCHEDULE
  (
  	SCHED_METROPOLICE_AIM_STITCH_TIGHTLY,
  
  	"	Tasks"
  	"		TASK_STOP_MOVING		0"
  	"		TASK_FACE_ENEMY			0"
  	"		TASK_ANNOUNCE_ATTACK	1"	// 1 = primary attack
  	"		TASK_METROPOLICE_RELOAD_FOR_BURST	1.0"
  	"		TASK_METROPOLICE_AIM_STITCH_TIGHTLY	1.0"
  	"		TASK_METROPOLICE_BURST_ATTACK		0"
  	"		TASK_FACE_ENEMY			0"
  	""
  	"	Interrupts"
  	"		COND_LIGHT_DAMAGE"
  	"		COND_HEAVY_DAMAGE"
  	"		COND_NO_PRIMARY_AMMO"
  	"		COND_HEAR_DANGER"
  	"		COND_WEAPON_BLOCKED_BY_FRIEND"
  
  );
  
  
  //===============================================
  //===============================================
  DEFINE_SCHEDULE
  (
  	SCHED_METROPOLICE_AIM_STITCH_AT_AIRBOAT,
  
  	"	Tasks"
  	"		TASK_STOP_MOVING		0"
  	"		TASK_FACE_ENEMY			0"
  	"		TASK_ANNOUNCE_ATTACK	1"	// 1 = primary attack
  	"		TASK_METROPOLICE_RELOAD_FOR_BURST		2.5"
  	"		TASK_METROPOLICE_AIM_STITCH_AT_AIRBOAT	2.5"
  	"		TASK_METROPOLICE_BURST_ATTACK		0"
  	"		TASK_FACE_ENEMY			0"
  	""
  	"	Interrupts"
  	"		COND_LIGHT_DAMAGE"
  	"		COND_HEAVY_DAMAGE"
  	"		COND_NO_PRIMARY_AMMO"
  	"		COND_HEAR_DANGER"
  	"		COND_WEAPON_BLOCKED_BY_FRIEND"
  
  );
  
  //===============================================
  //===============================================
  DEFINE_SCHEDULE
  (
  	SCHED_METROPOLICE_AIM_STITCH_IN_FRONT_OF_AIRBOAT,
  
  	"	Tasks"
  	"		TASK_STOP_MOVING		0"
  	"		TASK_FACE_ENEMY			0"
  	"		TASK_ANNOUNCE_ATTACK	1"	// 1 = primary attack
  	"		TASK_METROPOLICE_RELOAD_FOR_BURST		2.5"
  	"		TASK_METROPOLICE_AIM_STITCH_IN_FRONT_OF_AIRBOAT	2.5"
  	"		TASK_METROPOLICE_BURST_ATTACK		0"
  	"		TASK_FACE_ENEMY			0"
  	""
  	"	Interrupts"
  	"		COND_LIGHT_DAMAGE"
  	"		COND_HEAVY_DAMAGE"
  	"		COND_NO_PRIMARY_AMMO"
  	"		COND_HEAR_DANGER"
  	"		COND_WEAPON_BLOCKED_BY_FRIEND"
  
  );
  
  //===============================================
  //===============================================
  DEFINE_SCHEDULE
  (
  	SCHED_METROPOLICE_AIM_STITCH_ALONG_SIDE_OF_AIRBOAT,
  
  	"	Tasks"
  	"		TASK_STOP_MOVING		0"
  	"		TASK_FACE_ENEMY			0"
  	"		TASK_ANNOUNCE_ATTACK	1"	// 1 = primary attack
  	"		TASK_METROPOLICE_RELOAD_FOR_BURST		2.5"
  	"		TASK_METROPOLICE_AIM_STITCH_ALONG_SIDE_OF_AIRBOAT	2.5"
  	"		TASK_METROPOLICE_BURST_ATTACK		0"
  	"		TASK_FACE_ENEMY			0"
  	""
  	"	Interrupts"
  	"		COND_LIGHT_DAMAGE"
  	"		COND_HEAVY_DAMAGE"
  	"		COND_NO_PRIMARY_AMMO"
  	"		COND_HEAR_DANGER"
  	"		COND_WEAPON_BLOCKED_BY_FRIEND"
  
  );
  
  //===============================================
  //===============================================
  DEFINE_SCHEDULE
  (
  	SCHED_METROPOLICE_AIM_STITCH_BEHIND_AIRBOAT,
  
  	"	Tasks"
  	"		TASK_STOP_MOVING		0"
  	"		TASK_FACE_ENEMY			0"
  	"		TASK_ANNOUNCE_ATTACK	1"	// 1 = primary attack
  	"		TASK_METROPOLICE_RELOAD_FOR_BURST		2.5"
  	"		TASK_METROPOLICE_AIM_STITCH_BEHIND_AIRBOAT	2.5"
  	"		TASK_METROPOLICE_BURST_ATTACK		0"
  	"		TASK_FACE_ENEMY			0"
  	""
  	"	Interrupts"
  	"		COND_LIGHT_DAMAGE"
  	"		COND_HEAVY_DAMAGE"
  	"		COND_NO_PRIMARY_AMMO"
  	"		COND_HEAR_DANGER"
  	"		COND_WEAPON_BLOCKED_BY_FRIEND"
  
  );
  
  DEFINE_SCHEDULE
  (
  	SCHED_METROPOLICE_SHOVE,
  
  	"	Tasks"
  	"		TASK_STOP_MOVING				0"
  	"		TASK_FACE_PLAYER				0.1"	//FIXME: This needs to be the target or enemy
  	"		TASK_METROPOLICE_ACTIVATE_BATON	1"
  	"		TASK_PLAY_SEQUENCE				ACTIVITY:ACT_PUSH_PLAYER"
  	""
  	"	Interrupts"
  );
  
  DEFINE_SCHEDULE
  (
  	SCHED_METROPOLICE_ACTIVATE_BATON,
  
  	"	Tasks"
  	"		TASK_STOP_MOVING				0"
  	"		TASK_FACE_TARGET				0"
  	"		TASK_METROPOLICE_ACTIVATE_BATON	1"
  	""
  	"	Interrupts"
  );
  
  DEFINE_SCHEDULE
  (
  	SCHED_METROPOLICE_DEACTIVATE_BATON,
  
  	"	Tasks"
  	"		TASK_STOP_MOVING				0"
  	"		TASK_METROPOLICE_ACTIVATE_BATON	0"
  	""
  	"	Interrupts"
  );
  
  DEFINE_SCHEDULE
  (
  	SCHED_METROPOLICE_SMASH_PROP,
  
  	"	Tasks"
  	"		TASK_GET_PATH_TO_TARGET		0"
  	"		TASK_MOVE_TO_TARGET_RANGE	50"
  	"		TASK_STOP_MOVING			0"
  	"		TASK_FACE_TARGET			0"
  	"		TASK_ANNOUNCE_ATTACK		1"	// 1 = primary attack
  	"		TASK_PLAY_SEQUENCE			ACTIVITY:ACT_MELEE_ATTACK1"
  	""
  	"	Interrupts"
  	"		COND_NEW_ENEMY"
  	"		COND_ENEMY_DEAD"
  );
  
  AI_END_CUSTOM_NPC()
  
  