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

#include "cbase.h"
#include "ammodef.h"
#include "AI_Hint.h"
#include "AI_Navigator.h"
#include "npc_Assassin.h"
#include "game.h"
#include "NPCEvent.h"
#include "engine/IEngineSound.h"
#include "AI_Squad.h"
#include "AI_SquadSlot.h"
#include "ai_moveprobe.h"

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

ConVar	sk_assassin_health( "sk_assassin_health","150");
ConVar	g_debug_assassin( "g_debug_assassin", "0" );

//=========================================================
// Anim Events	
//=========================================================
#define	ASSASSIN_AE_FIRE_PISTOL_RIGHT	1
#define	ASSASSIN_AE_FIRE_PISTOL_LEFT	2
#define	ASSASSIN_AE_KICK_HIT			3

int AE_ASSASIN_FIRE_PISTOL_RIGHT;
int AE_ASSASIN_FIRE_PISTOL_LEFT;
int AE_ASSASIN_KICK_HIT;

//=========================================================
// Assassin activities
//=========================================================
int ACT_ASSASSIN_FLIP_LEFT;
int ACT_ASSASSIN_FLIP_RIGHT;
int ACT_ASSASSIN_FLIP_BACK;
int ACT_ASSASSIN_FLIP_FORWARD;
int ACT_ASSASSIN_PERCH;

//=========================================================
// Flip types
//=========================================================
enum 
{
	FLIP_LEFT,
	FLIP_RIGHT,
	FLIP_FORWARD,
	FLIP_BACKWARD,
	NUM_FLIP_TYPES,
};

//=========================================================
// Private conditions
//=========================================================
enum Assassin_Conds
{
	COND_ASSASSIN_ENEMY_TARGETTING_ME = LAST_SHARED_CONDITION,
};

//=========================================================
// Assassin schedules
//=========================================================
enum
{
	SCHED_ASSASSIN_FIND_VANTAGE_POINT = LAST_SHARED_SCHEDULE,
	SCHED_ASSASSIN_EVADE,
	SCHED_ASSASSIN_STALK_ENEMY,
	SCHED_ASSASSIN_LUNGE,
};

//=========================================================
// Assassin tasks
//=========================================================
enum 
{
	TASK_ASSASSIN_GET_PATH_TO_VANTAGE_POINT = LAST_SHARED_TASK,
	TASK_ASSASSIN_EVADE,
	TASK_ASSASSIN_SET_EYE_STATE,
	TASK_ASSASSIN_LUNGE,
};


//-----------------------------------------------------------------------------
// Purpose: Class Constructor
//-----------------------------------------------------------------------------
CNPC_Assassin::CNPC_Assassin( void )
{
}

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

LINK_ENTITY_TO_CLASS( npc_assassin, CNPC_Assassin );

#if 0
//---------------------------------------------------------
// Custom Client entity
//---------------------------------------------------------
IMPLEMENT_SERVERCLASS_ST(CNPC_Assassin, DT_NPC_Assassin)
END_SEND_TABLE()

#endif

//---------------------------------------------------------
// Save/Restore
//---------------------------------------------------------
BEGIN_DATADESC( CNPC_Assassin )
	DEFINE_FIELD( m_nNumFlips,	FIELD_INTEGER ),
	DEFINE_FIELD( m_nLastFlipType, FIELD_INTEGER ),
	DEFINE_FIELD( m_flNextFlipTime, FIELD_TIME ),
	DEFINE_FIELD( m_flNextLungeTime, FIELD_TIME ),
	DEFINE_FIELD( m_flNextShotTime, FIELD_TIME ),
	DEFINE_FIELD( m_bEvade,		FIELD_BOOLEAN ),
	DEFINE_FIELD( m_bAggressive, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_bBlinkState, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_pEyeSprite,	FIELD_CLASSPTR ),
	DEFINE_FIELD( m_pEyeTrail,	FIELD_CLASSPTR ),
END_DATADESC()

//-----------------------------------------------------------------------------
// Purpose: 
//
//
//-----------------------------------------------------------------------------
void CNPC_Assassin::Precache( void )
{
	PrecacheModel( "models/fassassin.mdl" );

	PrecacheScriptSound( "NPC_Assassin.ShootPistol" );
	PrecacheScriptSound( "Zombie.AttackHit" );
	PrecacheScriptSound( "Assassin.AttackMiss" );
	PrecacheScriptSound( "NPC_Assassin.Footstep" );

	PrecacheModel( "sprites/redglow1.vmt" );

	BaseClass::Precache();
}


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

	SetModel( "models/fassassin.mdl" );

	SetHullType(HULL_HUMAN);
	SetHullSizeNormal();

	SetSolid( SOLID_BBOX );
	AddSolidFlags( FSOLID_NOT_STANDABLE );
	SetMoveType( MOVETYPE_STEP );
	SetBloodColor( BLOOD_COLOR_RED );
	
	m_iHealth			= sk_assassin_health.GetFloat();
	m_flFieldOfView		= 0.1;
	m_NPCState			= NPC_STATE_NONE;

	CapabilitiesClear();
	CapabilitiesAdd( bits_CAP_MOVE_CLIMB | bits_CAP_MOVE_GROUND | bits_CAP_MOVE_JUMP );
	CapabilitiesAdd( bits_CAP_SQUAD | bits_CAP_USE_WEAPONS | bits_CAP_AIM_GUN | bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_INNATE_RANGE_ATTACK2 | bits_CAP_INNATE_MELEE_ATTACK1 );

	//Turn on our guns
	SetBodygroup( 1, 1 );

	int attachment = LookupAttachment( "Eye" );

	// Start up the eye glow
	m_pEyeSprite = CSprite::SpriteCreate( "sprites/redglow1.vmt", GetLocalOrigin(), false );

	if ( m_pEyeSprite != NULL )
	{
		m_pEyeSprite->SetAttachment( this, attachment );
		m_pEyeSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 200, kRenderFxNone );
		m_pEyeSprite->SetScale( 0.25f );
	}

	// Start up the eye trail
	m_pEyeTrail	= CSpriteTrail::SpriteTrailCreate( "sprites/bluelaser1.vmt", GetLocalOrigin(), false );

	if ( m_pEyeTrail != NULL )
	{
		m_pEyeTrail->SetAttachment( this, attachment );
		m_pEyeTrail->SetTransparency( kRenderTransAdd, 255, 0, 0, 200, kRenderFxNone );
		m_pEyeTrail->SetStartWidth( 8.0f );
		m_pEyeTrail->SetLifeTime( 0.75f );
	}

	NPCInit();

	m_bEvade = false;
	m_bAggressive = false;
}

//-----------------------------------------------------------------------------
// Purpose: Returns true if a reasonable jumping distance
// Input  :
// Output :
//-----------------------------------------------------------------------------
bool CNPC_Assassin::IsJumpLegal(const Vector &startPos, const Vector &apex, const Vector &endPos) const
{
	const float MAX_JUMP_RISE		= 256.0f;
	const float MAX_JUMP_DISTANCE	= 256.0f;
	const float MAX_JUMP_DROP		= 512.0f;

	return BaseClass::IsJumpLegal( startPos, apex, endPos, MAX_JUMP_RISE, MAX_JUMP_DROP, MAX_JUMP_DISTANCE );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : flDot - 
//			flDist - 
// Output : int CNPC_Assassin::MeleeAttack1Conditions
//-----------------------------------------------------------------------------
int CNPC_Assassin::MeleeAttack1Conditions ( float flDot, float flDist )
{
	if ( flDist > 84 )
		return COND_TOO_FAR_TO_ATTACK;
	
	if ( flDot < 0.7f )
		return 0;

	if ( GetEnemy() == NULL )
		return 0;

	return COND_CAN_MELEE_ATTACK1;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : flDot - 
//			flDist - 
// Output : int CNPC_Assassin::RangeAttack1Conditions
//-----------------------------------------------------------------------------
int CNPC_Assassin::RangeAttack1Conditions ( float flDot, float flDist )
{
	if ( flDist < 84 )
		return COND_TOO_CLOSE_TO_ATTACK;

	if ( flDist > 1024 )
		return COND_TOO_FAR_TO_ATTACK;

	if ( flDot < 0.5f )
		return COND_NOT_FACING_ATTACK;

	return COND_CAN_RANGE_ATTACK1;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : flDot - 
//			flDist - 
// Output : int CNPC_Assassin::RangeAttack1Conditions
//-----------------------------------------------------------------------------
int CNPC_Assassin::RangeAttack2Conditions ( float flDot, float flDist )
{
	if ( m_flNextLungeTime > gpGlobals->curtime )
		return 0;

	float lungeRange = GetSequenceMoveDist( SelectWeightedSequence( (Activity) ACT_ASSASSIN_FLIP_FORWARD ) );

	if ( flDist < lungeRange * 0.25f )
		return COND_TOO_CLOSE_TO_ATTACK;

	if ( flDist > lungeRange * 1.5f )
		return COND_TOO_FAR_TO_ATTACK;

	if ( flDot < 0.75f )
		return COND_NOT_FACING_ATTACK;

	if ( GetEnemy() == NULL )
		return 0;

	// Check for a clear path
	trace_t	tr;
	UTIL_TraceHull( GetAbsOrigin(), GetEnemy()->GetAbsOrigin(), GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
	
	if ( tr.fraction == 1.0f || tr.m_pEnt == GetEnemy() )
		return COND_CAN_RANGE_ATTACK2;

	return 0;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : hand - 
//-----------------------------------------------------------------------------
void CNPC_Assassin::FirePistol( int hand )
{
	if ( m_flNextShotTime > gpGlobals->curtime )
		return;

	m_flNextShotTime = gpGlobals->curtime + random->RandomFloat( 0.05f, 0.15f );

	Vector	muzzlePos;
	QAngle	muzzleAngle;

	const char *handName = ( hand ) ? "LeftMuzzle" : "RightMuzzle";

	GetAttachment( handName, muzzlePos, muzzleAngle );

	Vector	muzzleDir;
	
	if ( GetEnemy() == NULL )
	{
		AngleVectors( muzzleAngle, &muzzleDir );
	}
	else
	{
		muzzleDir = GetEnemy()->BodyTarget( muzzlePos ) - muzzlePos;
		VectorNormalize( muzzleDir );
	}

	int bulletType = GetAmmoDef()->Index( "Pistol" );

	FireBullets( 1, muzzlePos, muzzleDir, VECTOR_CONE_5DEGREES, 1024, bulletType, 2 );

	UTIL_MuzzleFlash( muzzlePos, muzzleAngle, 0.5f, 1 );

	CPASAttenuationFilter filter( this );
	EmitSound( filter, entindex(), "NPC_Assassin.ShootPistol" );
}

//---------------------------------------------------------
//---------------------------------------------------------
void CNPC_Assassin::HandleAnimEvent( animevent_t *pEvent )
{
	
	if ( pEvent->event == AE_ASSASIN_FIRE_PISTOL_RIGHT )
	{
		FirePistol( 0 );
		return;
	}

	if ( pEvent->event == AE_ASSASIN_FIRE_PISTOL_LEFT )
	{
		FirePistol( 1 );
		return;
	}
	
	if ( pEvent->event == AE_ASSASIN_KICK_HIT )
	{
		Vector	attackDir = BodyDirection2D();
		Vector	attackPos = WorldSpaceCenter() + ( attackDir * 64.0f );

		trace_t	tr;
		UTIL_TraceHull( WorldSpaceCenter(), attackPos, -Vector(8,8,8), Vector(8,8,8), MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, &tr );

		if ( ( tr.m_pEnt != NULL ) && ( tr.DidHitWorld() == false ) )
		{
			if ( tr.m_pEnt->m_takedamage != DAMAGE_NO )
			{
				CTakeDamageInfo info( this, this, 5, DMG_CLUB );
				CalculateMeleeDamageForce( &info, (tr.endpos - tr.startpos), tr.endpos );
				tr.m_pEnt->TakeDamage( info );

				CBasePlayer	*pPlayer = ToBasePlayer( tr.m_pEnt );

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

				EmitSound( "Zombie.AttackHit" );
				//EmitSound( "Assassin.AttackHit" );
			}
		}
		else
		{
			EmitSound( "Assassin.AttackMiss" );
			//EmitSound( "Assassin.AttackMiss" );
		}

		return;
	}

	BaseClass::HandleAnimEvent( pEvent );
}

//-----------------------------------------------------------------------------
// Purpose: Causes the assassin to prefer to run away, rather than towards her target
//-----------------------------------------------------------------------------
bool CNPC_Assassin::MovementCost( int moveType, const Vector &vecStart, const Vector &vecEnd, float *pCost )
{
	if ( GetEnemy() == NULL )
		return true;

	float	multiplier = 1.0f;

	Vector	moveDir = ( vecEnd - vecStart );
	VectorNormalize( moveDir );

	Vector	enemyDir = ( GetEnemy()->GetAbsOrigin() - vecStart );
	VectorNormalize( enemyDir );

	// If we're moving towards our enemy, then the cost is much higher than normal
	if ( DotProduct( enemyDir, moveDir ) > 0.5f )
	{
		multiplier = 16.0f;
	}

	*pCost *= multiplier;

	return ( multiplier != 1 );
}

//---------------------------------------------------------
//---------------------------------------------------------
int CNPC_Assassin::SelectSchedule ( void )
{
	switch	( m_NPCState )
	{
	case NPC_STATE_IDLE:
	case NPC_STATE_ALERT:
		{
			if ( HasCondition ( COND_HEAR_DANGER ) )
				 return SCHED_TAKE_COVER_FROM_BEST_SOUND;
				
			if ( HasCondition ( COND_HEAR_COMBAT ) )
				return SCHED_INVESTIGATE_SOUND;
		}
		break;

	case NPC_STATE_COMBAT:
		{
			// dead enemy
			if ( HasCondition( COND_ENEMY_DEAD ) )
			{
				// call base class, all code to handle dead enemies is centralized there.
				return BaseClass::SelectSchedule();
			}

			// Need to move
			if ( /*(	HasCondition( COND_SEE_ENEMY ) && HasCondition( COND_ASSASSIN_ENEMY_TARGETTING_ME ) && random->RandomInt( 0, 32 ) == 0 && m_flNextFlipTime < gpGlobals->curtime ) )*/
					( m_nNumFlips > 0 ) || 
					( ( HasCondition ( COND_LIGHT_DAMAGE ) && random->RandomInt( 0, 2 ) == 0 ) ) || ( HasCondition ( COND_HEAVY_DAMAGE ) ) )
			{
				if ( m_nNumFlips <= 0 )
				{
					m_nNumFlips = random->RandomInt( 1, 2 );
				}

				return SCHED_ASSASSIN_EVADE;
			}

			// Can kick
			if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
				return SCHED_MELEE_ATTACK1;

			// Can shoot
			if ( HasCondition( COND_CAN_RANGE_ATTACK2 ) )
			{
				m_flNextLungeTime	= gpGlobals->curtime + 2.0f;
				m_nLastFlipType		= FLIP_FORWARD;

				return SCHED_ASSASSIN_LUNGE;
			}

			// Can shoot
			if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
				return SCHED_RANGE_ATTACK1;

			// Face our enemy
			if ( HasCondition( COND_SEE_ENEMY ) )
				return SCHED_COMBAT_FACE;

			// new enemy
			if ( HasCondition( COND_NEW_ENEMY ) )
				return SCHED_TAKE_COVER_FROM_ENEMY;

			// ALERT( at_console, "stand\n");
			return SCHED_ASSASSIN_FIND_VANTAGE_POINT;
		}
		break;
	}

	return BaseClass::SelectSchedule();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_Assassin::PrescheduleThink( void )
{
	if ( GetActivity() == ACT_RUN || GetActivity() == ACT_WALK)
	{
		CPASAttenuationFilter filter( this );

		static int iStep = 0;
		iStep = ! iStep;
		if (iStep)
		{
			EmitSound( filter, entindex(), "NPC_Assassin.Footstep" );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : right - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_Assassin::CanFlip( int flipType, Activity &activity, const Vector *avoidPosition )
{
	Vector		testDir;
	Activity	act = ACT_INVALID;

	switch( flipType )
	{
	case FLIP_RIGHT:
		GetVectors( NULL, &testDir, NULL );
		act = NPC_TranslateActivity( (Activity) ACT_ASSASSIN_FLIP_RIGHT ); 
		break;

	case FLIP_LEFT:
		GetVectors( NULL, &testDir, NULL );
		testDir.Negate();
		act = NPC_TranslateActivity( (Activity) ACT_ASSASSIN_FLIP_LEFT );
		break;

	case FLIP_FORWARD:
		GetVectors( &testDir, NULL, NULL );
		act = NPC_TranslateActivity( (Activity) ACT_ASSASSIN_FLIP_FORWARD );
		break;
	
	case FLIP_BACKWARD:
		GetVectors( &testDir, NULL, NULL );
		testDir.Negate();
		act = NPC_TranslateActivity( (Activity) ACT_ASSASSIN_FLIP_BACK );
		break;

	default:
		assert(0); //NOTENOTE: Invalid flip type
		activity = ACT_INVALID;
		return false;
		break;
	}

	// Make sure we don't flip towards our avoidance position/
	if ( avoidPosition != NULL )
	{
		Vector	avoidDir = (*avoidPosition) - GetAbsOrigin();
		VectorNormalize( avoidDir );

		if ( DotProduct( avoidDir, testDir ) > 0.0f )
			return false;
	}

	int seq = SelectWeightedSequence( act );

	// Find out the length of this sequence
	float	testDist = GetSequenceMoveDist( seq );
	
	// Find the resulting end position from the sequence's movement
	Vector	endPos = GetAbsOrigin() + ( testDir * testDist );

	trace_t	tr;

	if ( ( flipType != FLIP_BACKWARD ) && ( avoidPosition != NULL ) )
	{
		UTIL_TraceLine( (*avoidPosition), endPos, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
		
		if ( tr.fraction == 1.0f )
			return false;
	}

	/*
	UTIL_TraceHull( GetAbsOrigin(), endPos, NAI_Hull::Mins(m_eHull) + Vector( 0, 0, StepHeight() ), NAI_Hull::Maxs(m_eHull), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );

	// See if we're hit an obstruction in that direction
	if ( tr.fraction < 1.0f )
	{
		if ( g_debug_assassin.GetBool() )
		{
			NDebugOverlay::BoxDirection( GetAbsOrigin(), NAI_Hull::Mins(m_eHull) + Vector( 0, 0, StepHeight() ), NAI_Hull::Maxs(m_eHull) + Vector( testDist, 0, StepHeight() ), testDir, 255, 0, 0, true, 2.0f );
		}

		return false;
	}

#define NUM_STEPS 2

	float	stepLength = testDist / NUM_STEPS;

	for ( int i = 1; i <= NUM_STEPS; i++ )
	{
		endPos = GetAbsOrigin() + ( testDir * (stepLength*i) );
		
		// Also check for a cliff edge
		UTIL_TraceHull( endPos, endPos - Vector( 0, 0, StepHeight() * 4.0f ), NAI_Hull::Mins(m_eHull) + Vector( 0, 0, StepHeight() ), NAI_Hull::Maxs(m_eHull), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );

		if ( tr.fraction == 1.0f )
		{
			if ( g_debug_assassin.GetBool() )
			{
				NDebugOverlay::BoxDirection( endPos, NAI_Hull::Mins(m_eHull) + Vector( 0, 0, StepHeight() ), NAI_Hull::Maxs(m_eHull) + Vector( StepHeight() * 4.0f, 0, StepHeight() ), Vector(0,0,-1), 255, 0, 0, true, 2.0f );
			}

			return false;
		}
	}

	if ( g_debug_assassin.GetBool() )
	{
		NDebugOverlay::BoxDirection( GetAbsOrigin(), NAI_Hull::Mins(m_eHull) + Vector( 0, 0, StepHeight() ), NAI_Hull::Maxs(m_eHull) + Vector( testDist, 0, StepHeight() ), testDir, 0, 255, 0, true, 2.0f );
	}
	*/
	
	AIMoveTrace_t moveTrace;
	GetMoveProbe()->TestGroundMove( GetAbsOrigin(), endPos, MASK_NPCSOLID, AITGM_DEFAULT, &moveTrace );

	if ( moveTrace.fStatus != AIMR_OK )
		return false;

	// Return the activity to use
	activity = (Activity) act;

	return true;
}

//---------------------------------------------------------
// Purpose: 
//---------------------------------------------------------
void CNPC_Assassin::StartTask( const Task_t *pTask )
{
	switch( pTask->iTask )
	{
	case TASK_ASSASSIN_SET_EYE_STATE:
		{
			SetEyeState( (eyeState_t) ( (int) pTask->flTaskData ) );
			TaskComplete();
		}
		break;

	case TASK_ASSASSIN_EVADE:
		{
			Activity flipAct = ACT_INVALID;

			const Vector *avoidPos = ( GetEnemy() != NULL ) ? &(GetEnemy()->GetAbsOrigin()) : NULL;

			for ( int i = FLIP_LEFT; i < NUM_FLIP_TYPES; i++ )
			{
				if ( CanFlip( i, flipAct, avoidPos ) )
				{
					// Don't flip back to where we just were
					if ( ( ( i == FLIP_LEFT ) && ( m_nLastFlipType == FLIP_RIGHT ) ) ||
						 ( ( i == FLIP_RIGHT ) && ( m_nLastFlipType == FLIP_LEFT ) ) ||
						 ( ( i == FLIP_FORWARD ) && ( m_nLastFlipType == FLIP_BACKWARD ) ) ||
						 ( ( i == FLIP_BACKWARD ) && ( m_nLastFlipType == FLIP_FORWARD ) ) )
					{
						flipAct = ACT_INVALID;
						continue;
					}

					m_nNumFlips--;
					ResetIdealActivity( flipAct );
					m_flNextFlipTime = gpGlobals->curtime + 2.0f;
					m_nLastFlipType = i;
					break;
				}
			}

			if ( flipAct == ACT_INVALID )
			{
				m_nNumFlips = 0;
				m_nLastFlipType = -1;
				m_flNextFlipTime = gpGlobals->curtime + 2.0f;
				TaskFail( "Unable to find flip evasion direction!\n" );
			}
		}
		break;

	case TASK_ASSASSIN_GET_PATH_TO_VANTAGE_POINT:
		{
			assert( GetEnemy() != NULL );
			if ( GetEnemy() == NULL )
				break;

			Vector	goalPos;

			CHintCriteria	hint;

			// Find a disadvantage node near the player, but away from ourselves
			hint.SetHintType( HINT_TACTICAL_ENEMY_DISADVANTAGED );
			hint.AddExcludePosition( GetAbsOrigin(), 256 );
			hint.AddExcludePosition( GetEnemy()->GetAbsOrigin(), 256 );

			if ( ( m_pSquad != NULL ) && ( m_pSquad->NumMembers() > 1 ) )
			{
				AISquadIter_t iter;
				for ( CAI_BaseNPC *pSquadMember = m_pSquad->GetFirstMember( &iter ); pSquadMember; pSquadMember = m_pSquad->GetNextMember( &iter ) )
				{
					if ( pSquadMember == NULL )
						continue;

					hint.AddExcludePosition( pSquadMember->GetAbsOrigin(), 128 );
				}
			}
	
			hint.SetFlag( bits_HINT_NODE_NEAREST );

			CAI_Hint *pHint = CAI_HintManager::FindHint( this, GetEnemy()->GetAbsOrigin(), &hint );

			if ( pHint == NULL )
			{
				TaskFail( "Unable to find vantage point!\n" );
				break;
			}

			pHint->GetPosition( this, &goalPos );

			AI_NavGoal_t goal( goalPos );
			
			//Try to run directly there
			if ( GetNavigator()->SetGoal( goal ) == false )
			{
				TaskFail( "Unable to find path to vantage point!\n" );
				break;
			}
			
			TaskComplete();
		}
		break;

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

//-----------------------------------------------------------------------------
// Purpose: 
//
//
//-----------------------------------------------------------------------------
float CNPC_Assassin::MaxYawSpeed( void )
{
	switch( GetActivity() )
	{
	case ACT_TURN_LEFT:
	case ACT_TURN_RIGHT:
		return 160;
		break;
	case ACT_RUN:
		return 900;
		break;
	case ACT_RANGE_ATTACK1:
		return 0;
		break;
	default:
		return 60;
		break;
	}
}


//---------------------------------------------------------
//---------------------------------------------------------
void CNPC_Assassin::RunTask( const Task_t *pTask )
{
	switch( pTask->iTask )
	{
	case TASK_ASSASSIN_EVADE:

		AutoMovement();

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

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


//---------------------------------------------------------
//---------------------------------------------------------
bool CNPC_Assassin::FValidateHintType ( CAI_Hint *pHint )
{
	switch( pHint->HintType() )
	{
	case HINT_TACTICAL_ENEMY_DISADVANTAGED:
		{
			Vector	hintPos;
			pHint->GetPosition( this, &hintPos );

			// Verify that we can see the target from that position
			hintPos += GetViewOffset();

			trace_t	tr;
			UTIL_TraceLine( hintPos, GetEnemy()->BodyTarget( hintPos, true ), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );

			// Check for seeing our target at the new location
			if ( ( tr.fraction == 1.0f ) || ( tr.m_pEnt == GetEnemy() ) )
				return false;

			return true;
			break;
		}

	default:
		return false;
		break;
	}

	return FALSE;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : const Vector
//-----------------------------------------------------------------------------
const Vector &CNPC_Assassin::GetViewOffset( void )
{
	static Vector eyeOffset;

	//FIXME: Use eye attachment?
	// If we're crouching, offset appropriately
	if ( ( GetActivity() == ACT_ASSASSIN_PERCH ) ||
		 ( GetActivity() == ACT_RANGE_ATTACK1 ) )
	{
		eyeOffset = Vector( 0, 0, 24.0f );
	}
	else
	{
		eyeOffset = BaseClass::GetViewOffset();
	}

	return eyeOffset;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_Assassin::OnScheduleChange( void )
{
	//TODO: Change eye state?

	BaseClass::OnScheduleChange();
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : state - 
//-----------------------------------------------------------------------------
void CNPC_Assassin::SetEyeState( eyeState_t state )
{
	//Must have a valid eye to affect
	if ( ( m_pEyeSprite == NULL ) || ( m_pEyeTrail == NULL ) )
		return;

	//Set the state
	switch( state )
	{
	default:
	case ASSASSIN_EYE_SEE_TARGET: //Fade in and scale up
		m_pEyeSprite->SetColor( 255, 0, 0 );
		m_pEyeSprite->SetBrightness( 164, 0.1f );
		m_pEyeSprite->SetScale( 0.4f, 0.1f );

		m_pEyeTrail->SetColor( 255, 0, 0 );
		m_pEyeTrail->SetScale( 8.0f );
		m_pEyeTrail->SetBrightness( 164 );

		break;

	case ASSASSIN_EYE_SEEKING_TARGET: //Ping-pongs
		
		//Toggle our state
		m_bBlinkState = !m_bBlinkState;
		m_pEyeSprite->SetColor( 255, 128, 0 );

		if ( m_bBlinkState )
		{
			//Fade up and scale up
			m_pEyeSprite->SetScale( 0.25f, 0.1f );
			m_pEyeSprite->SetBrightness( 164, 0.1f );
		}
		else
		{
			//Fade down and scale down
			m_pEyeSprite->SetScale( 0.2f, 0.1f );
			m_pEyeSprite->SetBrightness( 64, 0.1f );
		}

		break;

	case ASSASSIN_EYE_DORMANT: //Fade out and scale down
		m_pEyeSprite->SetScale( 0.5f, 0.5f );
		m_pEyeSprite->SetBrightness( 64, 0.5f );
		
		m_pEyeTrail->SetScale( 2.0f );
		m_pEyeTrail->SetBrightness( 64 );
		break;

	case ASSASSIN_EYE_DEAD: //Fade out slowly
		m_pEyeSprite->SetColor( 255, 0, 0 );
		m_pEyeSprite->SetScale( 0.1f, 5.0f );
		m_pEyeSprite->SetBrightness( 0, 5.0f );

		m_pEyeTrail->SetColor( 255, 0, 0 );
		m_pEyeTrail->SetScale( 0.1f );
		m_pEyeTrail->SetBrightness( 0 );
		break;

	case ASSASSIN_EYE_ACTIVE:
		m_pEyeSprite->SetColor( 255, 0, 0 );
		m_pEyeSprite->SetScale( 0.1f );
		m_pEyeSprite->SetBrightness( 0 );
		break;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_Assassin::GatherEnemyConditions( CBaseEntity *pEnemy )
{
	ClearCondition( COND_ASSASSIN_ENEMY_TARGETTING_ME );

	BaseClass::GatherEnemyConditions( pEnemy );

	// See if we're being targetted specifically
	if ( HasCondition( COND_ENEMY_FACING_ME ) )
	{
		Vector	enemyDir = GetAbsOrigin() - pEnemy->GetAbsOrigin();
		VectorNormalize( enemyDir );

		Vector	enemyBodyDir;
		CBasePlayer	*pPlayer = ToBasePlayer( pEnemy );

		if ( pPlayer != NULL )
		{
			enemyBodyDir = pPlayer->BodyDirection3D();
		}
		else
		{
			AngleVectors( pEnemy->GetAbsAngles(), &enemyBodyDir );
		}

		float	enemyDot = DotProduct( enemyBodyDir, enemyDir );

		//FIXME: Need to refine this a bit
		if ( enemyDot > 0.97f )
		{
			SetCondition( COND_ASSASSIN_ENEMY_TARGETTING_ME );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_Assassin::BuildScheduleTestBits( void )
{
	SetNextThink( gpGlobals->curtime + 0.05 );

		//Don't allow any modifications when scripted
	if ( m_NPCState == NPC_STATE_SCRIPT )
		return;

	//Become interrupted if we're targetted when shooting an enemy
	if ( IsCurSchedule( SCHED_RANGE_ATTACK1 ) )
	{
		SetCustomInterruptCondition( COND_ASSASSIN_ENEMY_TARGETTING_ME );
	}
	
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &info - 
//-----------------------------------------------------------------------------
void CNPC_Assassin::Event_Killed( const CTakeDamageInfo &info )
{
	BaseClass::Event_Killed( info );

	// Turn off the eye
	SetEyeState( ASSASSIN_EYE_DEAD );
	
	// Turn off the pistols
	SetBodygroup( 1, 0 );

	// Spawn her guns
}

//-----------------------------------------------------------------------------
//
// Schedules
//
//-----------------------------------------------------------------------------

AI_BEGIN_CUSTOM_NPC( npc_assassin, CNPC_Assassin )

	DECLARE_ACTIVITY(ACT_ASSASSIN_FLIP_LEFT)
	DECLARE_ACTIVITY(ACT_ASSASSIN_FLIP_RIGHT)
	DECLARE_ACTIVITY(ACT_ASSASSIN_FLIP_BACK)
	DECLARE_ACTIVITY(ACT_ASSASSIN_FLIP_FORWARD)
	DECLARE_ACTIVITY(ACT_ASSASSIN_PERCH)

	//Adrian: events go here
	DECLARE_ANIMEVENT( AE_ASSASIN_FIRE_PISTOL_RIGHT )
	DECLARE_ANIMEVENT( AE_ASSASIN_FIRE_PISTOL_LEFT )
	DECLARE_ANIMEVENT( AE_ASSASIN_KICK_HIT )

	DECLARE_TASK(TASK_ASSASSIN_GET_PATH_TO_VANTAGE_POINT)
	DECLARE_TASK(TASK_ASSASSIN_EVADE)
	DECLARE_TASK(TASK_ASSASSIN_SET_EYE_STATE)
	DECLARE_TASK(TASK_ASSASSIN_LUNGE)

	DECLARE_CONDITION(COND_ASSASSIN_ENEMY_TARGETTING_ME)

	//=========================================================
	// ASSASSIN_STALK_ENEMY
	//=========================================================

	DEFINE_SCHEDULE
	(
		SCHED_ASSASSIN_STALK_ENEMY,

		"	Tasks"
		"		TASK_STOP_MOVING						0"
		"		TASK_PLAY_SEQUENCE_FACE_ENEMY			ACTIVITY:ACT_ASSASSIN_PERCH"
		"	"
		"	Interrupts"
		"		COND_ASSASSIN_ENEMY_TARGETTING_ME"
		"		COND_SEE_ENEMY"
		"		COND_LIGHT_DAMAGE"
		"		COND_HEAVY_DAMAGE"
	)

	//=========================================================
	// > ASSASSIN_FIND_VANTAGE_POINT
	//=========================================================

	DEFINE_SCHEDULE
	(
		SCHED_ASSASSIN_FIND_VANTAGE_POINT,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE					SCHEDULE:SCHED_TAKE_COVER_FROM_ENEMY"
		"		TASK_STOP_MOVING						0"
		"		TASK_ASSASSIN_GET_PATH_TO_VANTAGE_POINT	0"
		"		TASK_RUN_PATH							0"
		"		TASK_WAIT_FOR_MOVEMENT					0"
		"		TASK_SET_SCHEDULE						SCHEDULE:SCHED_ASSASSIN_STALK_ENEMY"
		"	"
		"	Interrupts"
		"		COND_LIGHT_DAMAGE"
		"		COND_HEAVY_DAMAGE"
		"		COND_TASK_FAILED"
	)

	//=========================================================
	// Assassin needs to avoid the player
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_ASSASSIN_EVADE,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE		SCHEDULE:SCHED_ASSASSIN_FIND_VANTAGE_POINT"
		"		TASK_STOP_MOVING			0"
		"		TASK_ASSASSIN_EVADE			0"
		"	"
		"	Interrupts"
		"		COND_TASK_FAILED"
	)	

	//=========================================================
	// Assassin needs to avoid the player
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_ASSASSIN_LUNGE,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE		SCHEDULE:SCHED_ASSASSIN_FIND_VANTAGE_POINT"
		"		TASK_STOP_MOVING			0"
		"		TASK_FACE_ENEMY				0"
		"		TASK_PLAY_SEQUENCE			ACTIVITY:ACT_ASSASSIN_FLIP_FORWARD"
		"	"
		"	Interrupts"
		"		COND_TASK_FAILED"
	)	

AI_END_CUSTOM_NPC()
