//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Functions and data pertaining to the NPCs' AI scheduling system.
//			Implements default NPC tasks and schedules.
//
//=============================================================================//


#include "cbase.h"
#include "ai_default.h"
#include "animation.h"
#include "scripted.h"
#include "soundent.h"
#include "entitylist.h"
#include "basecombatweapon.h"
#include "bitstring.h"
#include "ai_task.h"
#include "ai_network.h"
#include "ai_schedule.h"
#include "ai_hull.h"
#include "ai_node.h"
#include "ai_motor.h"
#include "ai_hint.h"
#include "ai_memory.h"
#include "ai_navigator.h"
#include "ai_tacticalservices.h"
#include "ai_moveprobe.h"
#include "ai_squadslot.h"
#include "ai_squad.h"
#include "ai_speech.h"
#include "game.h"
#include "IEffects.h"
#include "vstdlib/random.h"
#include "ndebugoverlay.h"
#include "tier0/vcrmode.h"
#include "env_debughistory.h"

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

extern ConVar ai_task_pre_script;
extern ConVar ai_use_efficiency;
extern ConVar ai_use_think_optimizations;
#define ShouldUseEfficiency() ( ai_use_think_optimizations.GetBool() && ai_use_efficiency.GetBool() )

ConVar	ai_simulate_task_overtime( "ai_simulate_task_overtime", "0" );

#define MAX_TASKS_RUN 10

struct TaskTimings
{
	const char *pszTask;
	CFastTimer selectSchedule;
	CFastTimer startTimer;
	CFastTimer runTimer;
};

TaskTimings g_AITaskTimings[MAX_TASKS_RUN];
int			g_nAITasksRun;

void CAI_BaseNPC::DumpTaskTimings()
{
	DevMsg(" Tasks timings:\n" );
	for ( int i = 0; i < g_nAITasksRun; ++i )
	{
		DevMsg( "   %32s -- select %5.2f, start %5.2f, run %5.2f\n", g_AITaskTimings[i].pszTask,
			 g_AITaskTimings[i].selectSchedule.GetDuration().GetMillisecondsF(),
			 g_AITaskTimings[i].startTimer.GetDuration().GetMillisecondsF(),
			 g_AITaskTimings[i].runTimer.GetDuration().GetMillisecondsF() );
			
	}
}


//=========================================================
// FHaveSchedule - Returns true if NPC's GetCurSchedule()
// is anything other than NULL.
//=========================================================
bool CAI_BaseNPC::FHaveSchedule( void )
{
	if ( GetCurSchedule() == NULL )
	{
		return false;
	}

	return true;
}

//=========================================================
// ClearSchedule - blanks out the caller's schedule pointer
// and index.
//=========================================================
void CAI_BaseNPC::ClearSchedule( const char *szReason )
{
	if (szReason && m_debugOverlays & OVERLAY_TASK_TEXT_BIT)
	{
		DevMsg( this, AIMF_IGNORE_SELECTED, "  Schedule cleared: %s\n", szReason );
	}

	if ( szReason )
	{
		ADD_DEBUG_HISTORY( HISTORY_AI_DECISIONS, UTIL_VarArgs( "%s(%d):  Schedule cleared: %s\n", GetDebugName(), entindex(), szReason ) );
	}

	m_ScheduleState.timeCurTaskStarted = m_ScheduleState.timeStarted = 0;
	m_ScheduleState.bScheduleWasInterrupted = true;
	SetTaskStatus( TASKSTATUS_NEW );
	m_IdealSchedule = SCHED_NONE;
	m_pSchedule =  NULL;
	ResetScheduleCurTaskIndex();
	m_InverseIgnoreConditions.SetAll();
}

//=========================================================
// FScheduleDone - Returns true if the caller is on the
// last task in the schedule
//=========================================================
bool CAI_BaseNPC::FScheduleDone ( void )
{
	Assert( GetCurSchedule() != NULL );
	
	if ( GetScheduleCurTaskIndex() == GetCurSchedule()->NumTasks() )
	{
		return true;
	}

	return false;
}

//=========================================================

bool CAI_BaseNPC::SetSchedule( int localScheduleID ) 			
{ 
	CAI_Schedule *pNewSchedule = GetScheduleOfType( localScheduleID );
	if ( pNewSchedule )
	{
		// ken: I'm don't know of any remaining cases, but if you find one, hunt it down as to why the schedule is getting slammed while they're in the middle of script
		if (m_hCine != NULL)
		{
			if (!(localScheduleID == SCHED_SLEEP || localScheduleID == SCHED_WAIT_FOR_SCRIPT || localScheduleID == SCHED_SCRIPTED_WALK || localScheduleID == SCHED_SCRIPTED_RUN || localScheduleID == SCHED_SCRIPTED_CUSTOM_MOVE || localScheduleID == SCHED_SCRIPTED_WAIT || localScheduleID == SCHED_SCRIPTED_FACE) )
			{
				Assert( 0 );
				// ExitScriptedSequence();
			}
		}
		

		m_IdealSchedule = GetGlobalScheduleId( localScheduleID );
		SetSchedule( pNewSchedule ); 
		return true;
	}
	return false;
}

//=========================================================
// SetSchedule - replaces the NPC's schedule pointer
// with the passed pointer, and sets the ScheduleIndex back
// to 0
//=========================================================
#define SCHEDULE_HISTORY_SIZE	10
void CAI_BaseNPC::SetSchedule( CAI_Schedule *pNewSchedule )
{
	Assert( pNewSchedule != NULL );
	
	m_ScheduleState.timeCurTaskStarted = m_ScheduleState.timeStarted = gpGlobals->curtime;
	m_ScheduleState.bScheduleWasInterrupted = false;
	
	m_pSchedule = pNewSchedule ;
	ResetScheduleCurTaskIndex();
	SetTaskStatus( TASKSTATUS_NEW );
	m_failSchedule = SCHED_NONE;
	bool bCondInPVS = HasCondition( COND_IN_PVS );
	m_Conditions.ClearAll();
	if ( bCondInPVS )
		SetCondition( COND_IN_PVS );
	m_bConditionsGathered = false;
	GetNavigator()->ClearGoal();
	m_InverseIgnoreConditions.SetAll();
	Forget( bits_MEMORY_TURNING );

/*
#if _DEBUG
	if ( !ScheduleFromName( pNewSchedule->GetName() ) )
	{
		DevMsg( "Schedule %s not in table!!!\n", pNewSchedule->GetName() );
	}
#endif
*/	
// this is very useful code if you can isolate a test case in a level with a single NPC. It will notify
// you of every schedule selection the NPC makes.

	if (m_debugOverlays & OVERLAY_TASK_TEXT_BIT)
	{
		DevMsg(this, AIMF_IGNORE_SELECTED, "Schedule: %s (time: %.2f)\n", pNewSchedule->GetName(), gpGlobals->curtime );
	}

	ADD_DEBUG_HISTORY( HISTORY_AI_DECISIONS, UTIL_VarArgs("%s(%d): Schedule: %s (time: %.2f)\n", GetDebugName(), entindex(), pNewSchedule->GetName(), gpGlobals->curtime ) );

#ifdef AI_MONITOR_FOR_OSCILLATION
	if( m_bSelected )
	{
		AIScheduleChoice_t choice;
		choice.m_flTimeSelected = gpGlobals->curtime;
		choice.m_pScheduleSelected = pNewSchedule;
		m_ScheduleHistory.AddToHead(choice);

		if( m_ScheduleHistory.Count() > SCHEDULE_HISTORY_SIZE )
		{
			m_ScheduleHistory.Remove( SCHEDULE_HISTORY_SIZE );
		}

		assert( m_ScheduleHistory.Count() <= SCHEDULE_HISTORY_SIZE );

		// No analysis until the vector is full!
		if( m_ScheduleHistory.Count() == SCHEDULE_HISTORY_SIZE )
		{
			int		iNumSelections  = m_ScheduleHistory.Count();
			float	flTimeSpan		= m_ScheduleHistory.Head().m_flTimeSelected - m_ScheduleHistory.Tail().m_flTimeSelected;
			float	flSelectionsPerSecond = ((float)iNumSelections) / flTimeSpan;

			Msg( "%d selections in %f seconds   (avg. %f selections per second)\n", iNumSelections, flTimeSpan, flSelectionsPerSecond );

			if( flSelectionsPerSecond >=  8.0f )
			{
				DevMsg("\n\n %s is thrashing schedule selection:\n", GetDebugName() );

				for( int i = 0 ; i < m_ScheduleHistory.Count() ; i++ )
				{
					AIScheduleChoice_t choice = m_ScheduleHistory[i];
					Msg("--%s  %f\n", choice.m_pScheduleSelected->GetName(), choice.m_flTimeSelected );
				}

				Msg("\n");

				CAI_BaseNPC::m_nDebugBits |= bits_debugDisableAI;
			}
		}
	}
#endif//AI_MONITOR_FOR_OSCILLATION
}

//=========================================================
// NextScheduledTask - increments the ScheduleIndex
//=========================================================
void CAI_BaseNPC::NextScheduledTask ( void )
{
	Assert( GetCurSchedule() != NULL );

	SetTaskStatus( TASKSTATUS_NEW );
	IncScheduleCurTaskIndex();

	if ( FScheduleDone() )
	{
		// Reset memory of failed schedule 
		m_failedSchedule   = NULL;
		m_interuptSchedule = NULL;

		// just completed last task in schedule, so make it invalid by clearing it.
		SetCondition( COND_SCHEDULE_DONE );
	}
}


//-----------------------------------------------------------------------------
// Purpose: This function allows NPCs to modify the interrupt mask for the
//			current schedule. This enables them to use base schedules but with
//			different interrupt conditions. Implement this function in your
//			derived class, and Set or Clear condition bits as you please.
//
//			NOTE: Always call the base class in your implementation, but be
//				  aware of the difference between changing the bits before vs.
//				  changing the bits after calling the base implementation.
//
// Input  : pBitString - Receives the updated interrupt mask.
//-----------------------------------------------------------------------------
void CAI_BaseNPC::BuildScheduleTestBits( void )
{
	//NOTENOTE: Always defined in the leaf classes
}


//=========================================================
// IsScheduleValid - returns true as long as the current
// schedule is still the proper schedule to be executing,
// taking into account all conditions
//=========================================================
bool CAI_BaseNPC::IsScheduleValid()
{
	if ( GetCurSchedule() == NULL || GetCurSchedule()->NumTasks() == 0 )
	{
		return false;
	}

	//Start out with the base schedule's set interrupt conditions
	GetCurSchedule()->GetInterruptMask( &m_CustomInterruptConditions );

	// Let the leaf class modify our interrupt test bits, but:
	// - Don't allow any modifications when scripted
	// - Don't modify interrupts for Schedules that set the COND_NO_CUSTOM_INTERRUPTS bit.
	if ( m_NPCState != NPC_STATE_SCRIPT && !IsInLockedScene() && !m_CustomInterruptConditions.IsBitSet( COND_NO_CUSTOM_INTERRUPTS ) )
	{
		BuildScheduleTestBits();
	}

	//Any conditions set here will always be forced on the interrupt conditions
	SetCustomInterruptCondition( COND_NPC_FREEZE );

	// This is like: m_CustomInterruptConditions &= m_Conditions;
	CAI_ScheduleBits testBits;
	m_CustomInterruptConditions.And( m_Conditions, &testBits  );

	if (!testBits.IsAllClear()) 
	{
		// If in developer mode save the interrupt text for debug output
		if (g_pDeveloper->GetInt()) 
		{
			// Reset memory of failed schedule 
			m_failedSchedule   = NULL;
			m_interuptSchedule = GetCurSchedule();

			// Find the first non-zero bit
			for (int i=0;i<MAX_CONDITIONS;i++)
			{
				if (testBits.IsBitSet(i))
				{
					m_interruptText = ConditionName( AI_RemapToGlobal( i ) );
					if (!m_interruptText)
					{
						m_interruptText = "(UNKNOWN CONDITION)";
						/*
						static const char *pError = "ERROR: Unknown condition!";
						DevMsg("%s (%s)\n", pError, GetDebugName());
						m_interruptText = pError;
						*/
					}

					if (m_debugOverlays & OVERLAY_TASK_TEXT_BIT)
					{
						DevMsg( this, AIMF_IGNORE_SELECTED, "      Break condition -> %s\n", m_interruptText );
					}

					ADD_DEBUG_HISTORY( HISTORY_AI_DECISIONS, UTIL_VarArgs("%s(%d):      Break condition -> %s\n", GetDebugName(), entindex(), m_interruptText ) );

					break;
				}
			}
			
			if ( HasCondition( COND_NEW_ENEMY ) )
			{
				if (m_debugOverlays & OVERLAY_TASK_TEXT_BIT)
				{
					DevMsg( this, AIMF_IGNORE_SELECTED, "      New enemy: %s\n", GetEnemy() ? GetEnemy()->GetDebugName() : "<NULL>" );
				}
				
				ADD_DEBUG_HISTORY( HISTORY_AI_DECISIONS, UTIL_VarArgs("%s(%d):      New enemy: %s\n", GetDebugName(), entindex(), GetEnemy() ? GetEnemy()->GetDebugName() : "<NULL>" ) );
			}
		}

		return false;
	}

	if ( HasCondition(COND_SCHEDULE_DONE) || 
		 HasCondition(COND_TASK_FAILED)   )
	{
#ifdef DEBUG
		if ( HasCondition ( COND_TASK_FAILED ) && m_failSchedule == SCHED_NONE )
		{
			// fail! Send a visual indicator.
			DevWarning( 2, "Schedule: %s Failed\n", GetCurSchedule()->GetName() );

			Vector tmp;
			CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.5f, 1.0f ), &tmp );
			tmp.z += 16;

			g_pEffects->Sparks( tmp );
		}
#endif // DEBUG

		// some condition has interrupted the schedule, or the schedule is done
		return false;
	}
	
	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Determines whether or not SelectIdealState() should be called before
//			a NPC selects a new schedule. 
//
//			NOTE: This logic was a source of pure, distilled trouble in Half-Life.
//			If you change this function, please supply good comments.
//
// Output : Returns true if yes, false if no
//-----------------------------------------------------------------------------
bool CAI_BaseNPC::ShouldSelectIdealState( void )
{
/*

	HERE's the old Half-Life code that used to control this.

	if ( m_IdealNPCState != NPC_STATE_DEAD && 
		 (m_IdealNPCState != NPC_STATE_SCRIPT || m_IdealNPCState == m_NPCState) )
	{
		if (	(m_afConditions && !HasConditions(bits_COND_SCHEDULE_DONE)) ||
				(GetCurSchedule() && (GetCurSchedule()->iInterruptMask & bits_COND_SCHEDULE_DONE)) ||
				((m_NPCState == NPC_STATE_COMBAT) && (GetEnemy() == NULL))	)
		{
			GetIdealState();
		}
	}
*/
	
	// Don't get ideal state if you are supposed to be dead.
	if ( m_IdealNPCState == NPC_STATE_DEAD )
		return false;

	// If I'm supposed to be in scripted state, but i'm not yet, do not allow 
	// SelectIdealState() to be called, because it doesn't know how to determine 
	// that a NPC should be in SCRIPT state and will stomp it with some other 
	// state. (Most likely ALERT)
	if ( (m_IdealNPCState == NPC_STATE_SCRIPT) && (m_NPCState != NPC_STATE_SCRIPT) )
		return false;

	// If the NPC has any current conditions, and one of those conditions indicates
	// that the previous schedule completed successfully, then don't run SelectIdealState(). 
	// Paths between states only exist for interrupted schedules, or when a schedule 
	// contains a task that suggests that the NPC change state.
	if ( !HasCondition(COND_SCHEDULE_DONE) )
		return true;

	// This seems like some sort of hack...
	// Currently no schedule that I can see in the AI uses this feature, but if a schedule
	// interrupt mask contains bits_COND_SCHEDULE_DONE, then force a call to SelectIdealState().
	// If we want to keep this feature, I suggest we create a new condition with a name that
	// indicates exactly what it does. 
	if ( GetCurSchedule() && GetCurSchedule()->HasInterrupt(COND_SCHEDULE_DONE) )
		return true;

	// Don't call SelectIdealState if a NPC in combat state has a valid enemy handle. Otherwise,
	// we need to change state immediately because something unexpected happened to the enemy 
	// entity (it was blown apart by someone else, for example), and we need the NPC to change
	// state. THE REST OF OUR CODE should be robust enough that this can go away!!
	if ( (m_NPCState == NPC_STATE_COMBAT) && (GetEnemy() == NULL) )
		return true;

	if ( (m_NPCState == NPC_STATE_IDLE || m_NPCState == NPC_STATE_ALERT) && (GetEnemy() != NULL) )
		return true;

	return false;
}


//-----------------------------------------------------------------------------
// Purpose: Returns a new schedule based on current condition bits.
//-----------------------------------------------------------------------------
CAI_Schedule *CAI_BaseNPC::GetNewSchedule( void )
{
	int scheduleType;

	//
	// Schedule selection code here overrides all leaf schedule selection.
	//
	if (HasCondition(COND_NPC_FREEZE))
	{
		scheduleType = SCHED_NPC_FREEZE;
	}
	else
	{
		// I dunno how this trend got started, but we need to find the problem.
		// You may not be in combat state with no enemy!!! (sjb) 11/4/03
		if( m_NPCState == NPC_STATE_COMBAT && !GetEnemy() )
		{
			CGMsg( 1, CON_GROUP_NPC_AI, "**ERROR: Combat State with no enemy! slamming to ALERT\n" );
			SetState( NPC_STATE_ALERT );
		}

		AI_PROFILE_SCOPE_BEGIN( CAI_BaseNPC_SelectSchedule);

		if ( m_NPCState == NPC_STATE_SCRIPT || m_NPCState == NPC_STATE_DEAD || m_iInteractionState == NPCINT_MOVING_TO_MARK )
		{
			scheduleType = CAI_BaseNPC::SelectSchedule();
		}
		else
		{
			scheduleType = SelectSchedule();
		}

		m_IdealSchedule = GetGlobalScheduleId( scheduleType );

		AI_PROFILE_SCOPE_END();
	}

	return GetScheduleOfType( scheduleType );
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CAI_Schedule *CAI_BaseNPC::GetFailSchedule( void )
{
	int prevSchedule;
	int failedTask;

	if ( GetCurSchedule() )
		prevSchedule = GetLocalScheduleId( GetCurSchedule()->GetId() );
	else
		prevSchedule = SCHED_NONE;
		
	const Task_t *pTask = GetTask();
	if ( pTask )
		failedTask = pTask->iTask;
	else
		failedTask = TASK_INVALID;

	Assert( AI_IdIsLocal( prevSchedule ) );
	Assert( AI_IdIsLocal( failedTask ) );

	int scheduleType = SelectFailSchedule( prevSchedule, failedTask, m_ScheduleState.taskFailureCode );
	return GetScheduleOfType( scheduleType );
}


//=========================================================
// MaintainSchedule - does all the per-think schedule maintenance.
// ensures that the NPC leaves this function with a valid
// schedule!
//=========================================================

static bool ShouldStopProcessingTasks( CAI_BaseNPC *pNPC, int taskTime, int timeLimit )
{
#ifdef DEBUG
	if( ai_simulate_task_overtime.GetBool() )
		return true;
#endif

	// Always stop processing if we've queued up a navigation query on the last task
	if ( pNPC->IsNavigationDeferred() )
		return true;

	if ( AIStrongOpt() )
	{
		bool bInScript = ( pNPC->GetState() == NPC_STATE_SCRIPT || pNPC->IsCurSchedule( SCHED_SCENE_GENERIC, false ) );
		
		// We ran a costly task, don't do it again!
		if ( pNPC->HasMemory( bits_MEMORY_TASK_EXPENSIVE ) && bInScript == false )
			return true;
	}

	if ( taskTime > timeLimit )
	{
		if ( ShouldUseEfficiency() || 
			 pNPC->IsMoving() || 
			 ( pNPC->GetIdealActivity() != ACT_RUN && pNPC->GetIdealActivity() != ACT_WALK ) )
		{
			return true;
		}
	}
	return false;
}

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

void CAI_BaseNPC::MaintainSchedule ( void )
{
	AI_PROFILE_SCOPE(CAI_BaseNPC_RunAI_MaintainSchedule);
	extern CFastTimer g_AIMaintainScheduleTimer;
	CTimeScope timeScope(&g_AIMaintainScheduleTimer);

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

	CAI_Schedule	*pNewSchedule;
	int			i;
	bool		runTask = true;

#if defined( VPROF_ENABLED )
#if defined(DISABLE_DEBUG_HISTORY)
	bool bDebugTaskNames = ( developer.GetBool() || ( VProfAI() && g_VProfCurrentProfile.IsEnabled() ) );
#else
	bool bDebugTaskNames = true;
#endif
#else
	bool bDebugTaskNames = false;
#endif

	memset( g_AITaskTimings, 0, sizeof(g_AITaskTimings) );
	
	g_nAITasksRun = 0;
	
	const int timeLimit = ( IsDebug() ) ? 16 : 8;
	int taskTime = Plat_MSTime();

	// Reset this at the beginning of the frame
	Forget( bits_MEMORY_TASK_EXPENSIVE );

	// UNDONE: Tune/fix this MAX_TASKS_RUN... This is just here so infinite loops are impossible
	bool bStopProcessing = false;
	for ( i = 0; i < MAX_TASKS_RUN && !bStopProcessing; i++ )
	{
		if ( GetCurSchedule() != NULL && TaskIsComplete() )
		{
			// Schedule is valid, so advance to the next task if the current is complete.
			NextScheduledTask();

			// If we finished the current schedule, clear our ignored conditions so they
			// aren't applied to the next schedule selection.
			if ( HasCondition( COND_SCHEDULE_DONE ) )
			{
				// Put our conditions back the way they were after GatherConditions,
				// but add in COND_SCHEDULE_DONE.
				m_Conditions = m_ConditionsPreIgnore;
				SetCondition( COND_SCHEDULE_DONE );

				m_InverseIgnoreConditions.SetAll();
			}

			// --------------------------------------------------------
			//	If debug stepping advance when I complete a task
			// --------------------------------------------------------
			if (CAI_BaseNPC::m_nDebugBits & bits_debugStepAI)
			{
				m_nDebugCurIndex++;
				return;
			}
		}
		
		int curTiming = g_nAITasksRun;
		g_nAITasksRun++;

		// validate existing schedule 
		if ( !IsScheduleValid() || m_NPCState != m_IdealNPCState )
		{
			// Notify the NPC that his schedule is changing
			m_ScheduleState.bScheduleWasInterrupted = true;
			OnScheduleChange();

			if ( !HasCondition(COND_NPC_FREEZE) && ( !m_bConditionsGathered || m_bSkippedChooseEnemy ) )
			{
				// occurs if a schedule is exhausted within one think
				GatherConditions();
			}

			if ( ShouldSelectIdealState() )
			{
				NPC_STATE eIdealState = SelectIdealState();
				SetIdealState( eIdealState );
			}

			if ( HasCondition( COND_TASK_FAILED ) && m_NPCState == m_IdealNPCState )
			{
				// Get a fail schedule if the previous schedule failed during execution and 
				// the NPC is still in its ideal state. Otherwise, the NPC would immediately
				// select the same schedule again and fail again.
				if (m_debugOverlays & OVERLAY_TASK_TEXT_BIT)
				{
					DevMsg( this, AIMF_IGNORE_SELECTED, "      (failed)\n" );
				}

				ADD_DEBUG_HISTORY( HISTORY_AI_DECISIONS, UTIL_VarArgs("%s(%d):      (failed)\n", GetDebugName(), entindex() ) );

				pNewSchedule = GetFailSchedule();
				m_IdealSchedule = pNewSchedule->GetId();
				DevWarning( 2, "(%s) Schedule (%s) Failed at %d!\n", STRING( GetEntityName() ), GetCurSchedule() ? GetCurSchedule()->GetName() : "GetCurSchedule() == NULL", GetScheduleCurTaskIndex() );
				SetSchedule( pNewSchedule );
			}
			else
			{
				// If the NPC is supposed to change state, it doesn't matter if the previous
				// schedule failed or completed. Changing state means selecting an entirely new schedule.
				SetState( m_IdealNPCState );
				
				g_AITaskTimings[curTiming].selectSchedule.Start();

				pNewSchedule = GetNewSchedule();

				g_AITaskTimings[curTiming].selectSchedule.End();

				SetSchedule( pNewSchedule );
			}
		}

		if (!GetCurSchedule())
		{
			g_AITaskTimings[curTiming].selectSchedule.Start();
			
			pNewSchedule = GetNewSchedule();
			
			g_AITaskTimings[curTiming].selectSchedule.End();

			if (pNewSchedule)
			{
				SetSchedule( pNewSchedule );
			}
		}

		if ( !GetCurSchedule() || GetCurSchedule()->NumTasks() == 0 )
		{
			CGMsg( 1, CON_GROUP_NPC_AI, "ERROR: Missing or invalid schedule!\n" );
			SetActivity ( ACT_IDLE );
			return;
		}
		
		AI_PROFILE_SCOPE_BEGIN_( CAI_BaseNPC::GetSchedulingSymbols()->ScheduleIdToSymbol( GetCurSchedule()->GetId() ) );

		if ( GetTaskStatus() == TASKSTATUS_NEW )
		{	
			if ( GetScheduleCurTaskIndex() == 0 )
			{
				int globalId = GetCurSchedule()->GetId();
				int localId = GetLocalScheduleId( globalId ); // if localId == -1, then it came from a behavior
				OnStartSchedule( (localId != -1)? localId : globalId );
			}

			g_AITaskTimings[curTiming].startTimer.Start();
			const Task_t *pTask = GetTask();
			const char *pszTaskName = ( bDebugTaskNames ) ? TaskName( pTask->iTask ) : "ai_task";
			Assert( pTask != NULL );
			g_AITaskTimings[i].pszTask = pszTaskName;

			if (m_debugOverlays & OVERLAY_TASK_TEXT_BIT)
			{
				DevMsg(this, AIMF_IGNORE_SELECTED, "  Task: %s\n", pszTaskName );
			}

			ADD_DEBUG_HISTORY( HISTORY_AI_DECISIONS, UTIL_VarArgs("%s(%d):  Task: %s\n", GetDebugName(), entindex(), pszTaskName ) );

			OnStartTask();
			
			m_ScheduleState.taskFailureCode    = NO_TASK_FAILURE;
			m_ScheduleState.timeCurTaskStarted = gpGlobals->curtime;
			
			AI_PROFILE_SCOPE_BEGIN_( pszTaskName );
			AI_PROFILE_SCOPE_BEGIN(CAI_BaseNPC_StartTask);

			StartTask( pTask );

			AI_PROFILE_SCOPE_END();
			AI_PROFILE_SCOPE_END();

			if ( TaskIsRunning() && !HasCondition(COND_TASK_FAILED) )
				StartTaskOverlay();

			g_AITaskTimings[curTiming].startTimer.End();
			// DevMsg( "%.2f StartTask( %s )\n", gpGlobals->curtime, m_pTaskSR->GetStringText( pTask->iTask ) );
		}

		AI_PROFILE_SCOPE_END();

		// UNDONE: Twice?!!!
		MaintainActivity();
		
		AI_PROFILE_SCOPE_BEGIN_( CAI_BaseNPC::GetSchedulingSymbols()->ScheduleIdToSymbol( GetCurSchedule()->GetId() ) );

		if ( !TaskIsComplete() && GetTaskStatus() != TASKSTATUS_NEW )
		{
			if ( TaskIsRunning() && !HasCondition(COND_TASK_FAILED) && runTask )
			{
				const Task_t *pTask = GetTask();
				const char *pszTaskName = ( bDebugTaskNames ) ? TaskName( pTask->iTask ) : "ai_task";
				Assert( pTask != NULL );
				g_AITaskTimings[i].pszTask = pszTaskName;
				// DevMsg( "%.2f RunTask( %s )\n", gpGlobals->curtime, m_pTaskSR->GetStringText( pTask->iTask ) );
				g_AITaskTimings[curTiming].runTimer.Start();

				AI_PROFILE_SCOPE_BEGIN_( pszTaskName );
				AI_PROFILE_SCOPE_BEGIN(CAI_BaseNPC_RunTask);

				int j;
				for (j = 0; j < 8; j++)
				{
					RunTask( pTask );

					if ( GetTaskInterrupt() == 0 || TaskIsComplete() || HasCondition(COND_TASK_FAILED) )
						break;

					if ( ShouldUseEfficiency() && ShouldStopProcessingTasks( this, Plat_MSTime() - taskTime, timeLimit ) )
					{
						bStopProcessing = true;
						break;
					}
				}
				AssertMsg( j < 8, "Runaway task interrupt\n" );
					
				AI_PROFILE_SCOPE_END();
				AI_PROFILE_SCOPE_END();

				if ( TaskIsRunning() && !HasCondition(COND_TASK_FAILED) )
				{
					if ( IsCurTaskContinuousMove() )
						Remember( bits_MEMORY_MOVED_FROM_SPAWN );
					RunTaskOverlay();
				}

				g_AITaskTimings[curTiming].runTimer.End();

				// don't do this again this frame
				// FIXME: RunTask() should eat some of the clock, depending on what it has done
				// runTask = false;

				if ( !TaskIsComplete() )
				{
					bStopProcessing = true;
				}
			}
			else
			{
				bStopProcessing = true;
			}
		}

		AI_PROFILE_SCOPE_END();

		// Decide if we should continue on this frame
		if ( !bStopProcessing && ShouldStopProcessingTasks( this, Plat_MSTime() - taskTime, timeLimit ) )
			bStopProcessing = true;
	}

	// UNDONE: We have to do this so that we have an animation set to blend to if RunTask changes the animation
	// RunTask() will always change animations at the end of a script!
	// Don't do this twice
	MaintainActivity();

	// --------------------------------------------------------
	//	If I'm stopping to debug step, don't animate unless
	//  I'm in motion
	// --------------------------------------------------------
	if (CAI_BaseNPC::m_nDebugBits & bits_debugStepAI)
	{
		if (!GetNavigator()->IsGoalActive() && 
			m_nDebugCurIndex >= CAI_BaseNPC::m_nDebugPauseIndex)
		{
			m_flPlaybackRate = 0;
		}
	}
}


//=========================================================

bool CAI_BaseNPC::FindCoverPos( CBaseEntity *pEntity, Vector *pResult )
{
	AI_PROFILE_SCOPE(CAI_BaseNPC_FindCoverPos);

	if ( !GetTacticalServices()->FindLateralCover( pEntity->EyePosition(), 0, pResult ) )
	{
		if ( !GetTacticalServices()->FindCoverPos( pEntity->GetAbsOrigin(), pEntity->EyePosition(), 0, CoverRadius(), pResult ) ) 
		{
			return false;
		}
	}
	return true;
}

//=========================================================

bool CAI_BaseNPC::FindCoverPosInRadius( CBaseEntity *pEntity, const Vector &goalPos, float coverRadius, Vector *pResult )
{
	AI_PROFILE_SCOPE(CAI_BaseNPC_FindCoverPosInRadius);

	if ( pEntity == NULL )
	{
		// Find cover from self if no enemy available
		pEntity = this;
	}

	Vector					coverPos			= vec3_invalid;
	CAI_TacticalServices *	pTacticalServices	= GetTacticalServices();
	const Vector &			enemyPos			= pEntity->GetAbsOrigin();
	Vector					enemyEyePos			= pEntity->EyePosition();

	if( ( !GetSquad() || GetSquad()->GetFirstMember() == this ) &&
		IsCoverPosition( enemyEyePos, goalPos + GetViewOffset() ) && 
		IsValidCover( goalPos, NULL ) )
	{
		coverPos = goalPos;
	}
	else if ( !pTacticalServices->FindCoverPos( goalPos, enemyPos, enemyEyePos, 0, coverRadius * 0.5, &coverPos ) )
	{
		if ( !pTacticalServices->FindLateralCover( goalPos, enemyEyePos, 0, coverRadius * 0.5, 3, &coverPos ) )
		{
			if ( !pTacticalServices->FindCoverPos( goalPos, enemyPos, enemyEyePos, coverRadius * 0.5 - 0.1, coverRadius, &coverPos ) )
			{
				pTacticalServices->FindLateralCover( goalPos, enemyEyePos, 0, coverRadius, 5, &coverPos );
			}
		}
	}
	
	if ( coverPos == vec3_invalid )
		return false;
	*pResult = coverPos;
	return true;
}

//=========================================================

bool CAI_BaseNPC::FindCoverPos( CSound *pSound, Vector *pResult )
{
	if ( !GetTacticalServices()->FindCoverPos( pSound->GetSoundReactOrigin(), 
												pSound->GetSoundReactOrigin(), 
												MIN( pSound->Volume(), 120.0 ), 
												CoverRadius(), 
												pResult ) )
	{
		return GetTacticalServices()->FindLateralCover( pSound->GetSoundReactOrigin(), MIN( pSound->Volume(), 60.0 ), pResult );
	}

	return true;
}

//=========================================================
// Start task - selects the correct activity and performs
// any necessary calculations to start the next task on the
// schedule. 
//=========================================================

//-----------------------------------------------------------------------------
// TASK_TURN_RIGHT / TASK_TURN_LEFT
//-----------------------------------------------------------------------------
void CAI_BaseNPC::StartTurn( float flDeltaYaw )
{
	float flCurrentYaw;
	
	flCurrentYaw = UTIL_AngleMod( GetLocalAngles().y );
	GetMotor()->SetIdealYaw( UTIL_AngleMod( flCurrentYaw + flDeltaYaw ) );
	SetTurnActivity();
}


//-----------------------------------------------------------------------------
// TASK_CLEAR_HINTNODE
//-----------------------------------------------------------------------------
void CAI_BaseNPC::ClearHintNode( float reuseDelay )
{
	if ( m_pHintNode )
	{
		if ( m_pHintNode->IsLockedBy(this) )
			m_pHintNode->Unlock(reuseDelay);
		m_pHintNode = NULL;
	}
}


void CAI_BaseNPC::SetHintNode( CAI_Hint *pHintNode )
{
	m_pHintNode = pHintNode;
}


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

bool CAI_BaseNPC::FindCoverFromEnemy( bool bNodesOnly, float flMinDistance, float flMaxDistance )
{
	CBaseEntity *pEntity = GetEnemy();

	// Find cover from self if no enemy available
	if ( pEntity == NULL )
		pEntity = this;

	Vector coverPos = vec3_invalid;

	ClearHintNode();
	
	if ( bNodesOnly )
	{
		if ( flMaxDistance == FLT_MAX )
			flMaxDistance = CoverRadius();
		
		if ( !GetTacticalServices()->FindCoverPos( pEntity->GetAbsOrigin(), pEntity->EyePosition(), flMinDistance, flMaxDistance, &coverPos ) )
			return false;
	}
	else
	{
		if ( !FindCoverPos( pEntity, &coverPos ) )
			return false;
	}

	AI_NavGoal_t goal( GOALTYPE_COVER, coverPos, ACT_RUN, AIN_HULL_TOLERANCE );

	if ( !GetNavigator()->SetGoal( goal ) )
		return false;
		
	// FIXME: add to goal
	if (GetHintNode())
	{
		GetNavigator()->SetArrivalActivity( GetCoverActivity( GetHintNode() ) );
#ifdef MAPBASE
		if (GetHintNode()->GetIgnoreFacing() != HIF_NO)
#endif
		GetNavigator()->SetArrivalDirection( GetHintNode()->GetDirection() );
	}
	
	return true;
}


//-----------------------------------------------------------------------------
// TASK_FIND_COVER_FROM_BEST_SOUND
//-----------------------------------------------------------------------------
bool CAI_BaseNPC::FindCoverFromBestSound( Vector *pCoverPos )
{
	CSound *pBestSound;

	pBestSound = GetBestSound();

	if (pBestSound)
	{
		// UNDONE: Back away if cover fails?  Grunts do this.
		return FindCoverPos( pBestSound, pCoverPos );
	}
	else
	{
		CGMsg( 2, CON_GROUP_NPC_AI, "Attempting to find cover from best sound, but best sound not founc.\n" );
	}
	
	return false;
}


//-----------------------------------------------------------------------------
// TASK_FACE_REASONABLE
//-----------------------------------------------------------------------------
float CAI_BaseNPC::CalcReasonableFacing( bool bIgnoreOriginalFacing )
{
	float flReasonableYaw;

	if( !bIgnoreOriginalFacing && !HasMemory( bits_MEMORY_MOVED_FROM_SPAWN ) && !HasCondition( COND_SEE_ENEMY) )
	{
		flReasonableYaw = m_flOriginalYaw;
	}
	else
	{
		// If I'm facing a wall, change my original yaw and try to find a good direction to face.
		trace_t tr;
		Vector forward;
		QAngle angles( 0, 0, 0 );

		float idealYaw = GetMotor()->GetIdealYaw();
		
		flReasonableYaw = idealYaw;
		
		// Try just using the facing we have
		const float MIN_DIST = GetReasonableFacingDist();
		float longestTrace = 0;
		
		// Early out if we're overriding reasonable facing
		if ( !MIN_DIST )
			return flReasonableYaw;

		// Otherwise, scan out back and forth until something better is found
		const float SLICES = 8.0f;
		const float SIZE_SLICE = 360.0 / SLICES;
		const int SEARCH_MAX = (int)SLICES / 2;

		float zEye = GetAbsOrigin().z + m_vDefaultEyeOffset.z; // always use standing eye so as to not screw with crouch cover

		for( int i = 0 ; i <= SEARCH_MAX; i++ )
		{
			float offset = i * SIZE_SLICE;
			for ( int j = -1; j <= 1; j += 2)
			{
				angles.y = idealYaw + ( offset * j );
				AngleVectors( angles, &forward, NULL, NULL );
				float curTrace;
				if( ( curTrace = LineOfSightDist( forward, zEye ) ) > longestTrace && IsValidReasonableFacing(forward, curTrace) )
				{
					// Take this one.
					flReasonableYaw = angles.y;
					longestTrace = curTrace;
				}
				
				if ( longestTrace > MIN_DIST) // found one
					break;

				if ( i == 0 || i == SEARCH_MAX) // if trying forwards or backwards, skip the check of the other side...
					break;
			}
			
			if ( longestTrace > MIN_DIST ) // found one
				break;
		}
	}
	
	return flReasonableYaw;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
float CAI_BaseNPC::GetReasonableFacingDist( void )
{
	if ( GetTask() && GetTask()->iTask == TASK_FACE_ENEMY )
	{
		const float dist = 3.5*12;
		if ( GetEnemy() )
		{
			float distEnemy = ( GetEnemy()->GetAbsOrigin().AsVector2D() - GetAbsOrigin().AsVector2D() ).Length() - 1.0; 
			return MIN( distEnemy, dist );
		}

		return dist;
	}
	return 5*12;
}

//-----------------------------------------------------------------------------
// TASK_SCRIPT_RUN_TO_TARGET / TASK_SCRIPT_WALK_TO_TARGET / TASK_SCRIPT_CUSTOM_MOVE_TO_TARGET
//-----------------------------------------------------------------------------
void CAI_BaseNPC::StartScriptMoveToTargetTask( int task )
{
	Activity newActivity;

	if ( m_hTargetEnt == NULL)
	{
		TaskFail(FAIL_NO_TARGET);
	}
	else if ( (m_hTargetEnt->GetAbsOrigin() - GetLocalOrigin()).Length() < 1 )
	{
		TaskComplete();
	}
	else
	{
		//
		// Select the appropriate activity.
		//
		if ( task == TASK_SCRIPT_WALK_TO_TARGET )
		{
			newActivity = ACT_WALK;
		}
		else if ( task == TASK_SCRIPT_RUN_TO_TARGET )
		{
			newActivity = ACT_RUN;
		}
		else
		{
			newActivity = GetScriptCustomMoveActivity();
		}

		if ( ( newActivity != ACT_SCRIPT_CUSTOM_MOVE ) && TranslateActivity( newActivity ) == ACT_INVALID )
		{
			// This NPC can't do this!
			Assert( 0 );
		}
		else 
		{
			if (m_hTargetEnt == NULL)
			{
				TaskFail(FAIL_NO_TARGET);
			}
			else 
			{

				AI_NavGoal_t goal( GOALTYPE_TARGETENT, newActivity );
				
				if ( GetState() == NPC_STATE_SCRIPT && 
					 ( m_ScriptArrivalActivity != AIN_DEF_ACTIVITY || 
					   m_strScriptArrivalSequence != NULL_STRING ) )
				{
					if ( m_ScriptArrivalActivity != AIN_DEF_ACTIVITY )
					{
						goal.arrivalActivity = m_ScriptArrivalActivity;
					}
					else
					{
						goal.arrivalSequence = LookupSequence( m_strScriptArrivalSequence.ToCStr() );
					}
				}
					
				if (!GetNavigator()->SetGoal( goal, AIN_DISCARD_IF_FAIL ))
				{
					if ( GetNavigator()->GetNavFailCounter() == 0 )
					{
						// no path was built, but OnNavFailed() did something so that next time it may work
						DevWarning("%s %s failed Urgent Movement, retrying\n", GetDebugName(), TaskName( task ) );
						return;
					}

					// FIXME: scripted sequences don't actually know how to handle failure, but we're failing.  This is serious
					DevWarning("%s %s failed Urgent Movement, abandoning schedule\n", GetDebugName(), TaskName( task ) );
					TaskFail(FAIL_NO_ROUTE);
				}
				else
				{
					GetNavigator()->SetArrivalDirection( m_hTargetEnt->GetAbsAngles() );
				}
			}
		}
	}

	m_ScriptArrivalActivity = AIN_DEF_ACTIVITY;
	m_strScriptArrivalSequence = NULL_STRING;

	TaskComplete();
}


//-----------------------------------------------------------------------------
// Start task!
//-----------------------------------------------------------------------------
void CAI_BaseNPC::StartTask( const Task_t *pTask )
{
	int task = pTask->iTask;
	switch ( pTask->iTask )
	{
	case TASK_RESET_ACTIVITY:
		m_Activity = ACT_RESET;
		TaskComplete();
		break;

	case TASK_CREATE_PENDING_WEAPON:
		Assert( m_iszPendingWeapon != NULL_STRING );
		GiveWeapon( m_iszPendingWeapon );
		m_iszPendingWeapon = NULL_STRING;
		TaskComplete();
		break;

	case TASK_RANDOMIZE_FRAMERATE:
		{
			float newRate = GetPlaybackRate();
			float percent = pTask->flTaskData / 100.0f;

			newRate += ( newRate * random->RandomFloat(-percent, percent) );

			SetPlaybackRate(newRate);

			TaskComplete();
		}
		break;

	case TASK_DEFER_DODGE:
		m_flNextDodgeTime = gpGlobals->curtime + pTask->flTaskData;
		TaskComplete();
		break;

	// Default case just completes.  Override in sub-classes
	// to play sound / animation / or pause
	case TASK_ANNOUNCE_ATTACK:
		TaskComplete();
		break;

	case TASK_TURN_RIGHT:
		StartTurn( -pTask->flTaskData );
		break;

	case TASK_TURN_LEFT:
		StartTurn( pTask->flTaskData );
		break;

	case TASK_REMEMBER:
		Remember ( (int)pTask->flTaskData );
		TaskComplete();
		break;

	case TASK_FORGET:
		Forget ( (int)pTask->flTaskData );
		TaskComplete();
		break;

	case TASK_FIND_HINTNODE:
	case TASK_FIND_LOCK_HINTNODE:
		{
			if (!GetHintNode())
			{
				SetHintNode( CAI_HintManager::FindHint( this, HINT_NONE, pTask->flTaskData, 2000 ) );
			}
			if (GetHintNode())
			{
				TaskComplete();
			}
			else
			{
				TaskFail(FAIL_NO_HINT_NODE);
			}
			if ( task == TASK_FIND_HINTNODE )
				break;
		}
		// Fall through on TASK_FIND_LOCK_HINTNODE...
		
	case TASK_LOCK_HINTNODE:
	{
		if (!GetHintNode())
		{
			TaskFail(FAIL_NO_HINT_NODE);
		}
		else if( GetHintNode()->Lock(this) )
		{
			TaskComplete();
		}
		else
		{
			TaskFail(FAIL_ALREADY_LOCKED);
			SetHintNode( NULL );
		}
		break;
	}

	case TASK_STORE_LASTPOSITION:
		m_vecLastPosition = GetLocalOrigin();
		TaskComplete();
		break;

	case TASK_CLEAR_LASTPOSITION:
		m_vecLastPosition = vec3_origin;
		TaskComplete();
		break;

	case TASK_STORE_POSITION_IN_SAVEPOSITION:
		m_vSavePosition = GetLocalOrigin();
		TaskComplete();
		break;

	case TASK_STORE_BESTSOUND_IN_SAVEPOSITION:
		{
			CSound *pBestSound = GetBestSound();
			if ( pBestSound )
			{
				m_vSavePosition = pBestSound->GetSoundOrigin();
				CBaseEntity *pSoundEnt = pBestSound->m_hOwner;
				if ( pSoundEnt )
				{
					Vector vel;
					pSoundEnt->GetVelocity( &vel, NULL );
					// HACKHACK: run away from cars in the right direction
					m_vSavePosition += vel * 2;	// add in 2 seconds of velocity
				}
				TaskComplete();
			}
			else
			{
				TaskFail("No Sound!");
				return;
			}
		}
		break;

	case TASK_STORE_BESTSOUND_REACTORIGIN_IN_SAVEPOSITION:
		{
			CSound *pBestSound = GetBestSound();
			if ( pBestSound )
			{
				m_vSavePosition = pBestSound->GetSoundReactOrigin();
				TaskComplete();
			}
			else
			{
				TaskFail("No Sound!");
				return;
			}
		}
		break;

	case TASK_STORE_ENEMY_POSITION_IN_SAVEPOSITION:
		if ( GetEnemy() != NULL )
		{
			m_vSavePosition = GetEnemy()->GetAbsOrigin();
			TaskComplete();
		}
		else
		{
			TaskFail(FAIL_NO_ENEMY);
		}
		break;

	case TASK_CLEAR_HINTNODE:
		ClearHintNode(pTask->flTaskData);
		TaskComplete();
		break;

	case TASK_STOP_MOVING:
		if ( ( GetNavigator()->IsGoalSet() && GetNavigator()->IsGoalActive() ) || GetNavType() == NAV_JUMP )
		{
			DbgNavMsg( this, "Start TASK_STOP_MOVING\n" );
			if ( pTask->flTaskData == 1 )
			{
				DbgNavMsg( this, "Initiating stopping path\n" );
				GetNavigator()->StopMoving( false );
			}
			else
			{
				GetNavigator()->ClearGoal();
			}

			// E3 Hack
			if  ( HasPoseMoveYaw() ) 
			{
				SetPoseParameter( m_poseMove_Yaw, 0 );
			}
		}
		else
		{
			if ( pTask->flTaskData == 1 && GetNavigator()->SetGoalFromStoppingPath() )
			{
				DbgNavMsg( this, "Start TASK_STOP_MOVING\n" );
				DbgNavMsg( this, "Initiating stopping path\n" );
			}
			else
			{
				GetNavigator()->ClearGoal();
				SetIdealActivity( GetStoppedActivity() );
				TaskComplete();
			}
		}
		break;

	case TASK_PLAY_PRIVATE_SEQUENCE:
	case TASK_PLAY_PRIVATE_SEQUENCE_FACE_ENEMY:
	case TASK_PLAY_SEQUENCE_FACE_ENEMY:
	case TASK_PLAY_SEQUENCE_FACE_TARGET:
	case TASK_PLAY_SEQUENCE:
		SetIdealActivity( (Activity)(int)pTask->flTaskData );
		break;

	case TASK_ADD_GESTURE_WAIT:
		{
			int iLayer = AddGesture( (Activity)(int)pTask->flTaskData );
			if (iLayer > 0)
			{
				float flDuration = GetLayerDuration( iLayer );
				SetWait( flDuration );
			}
			else
			{
				TaskFail( "Unable to allocate gesture" );
			}
			break;
		}

	case TASK_ADD_GESTURE:
		{
			AddGesture( (Activity)(int)pTask->flTaskData );
			TaskComplete();
			break;
		}

	case TASK_PLAY_HINT_ACTIVITY:
		if ( GetHintNode()->HintActivityName() != NULL_STRING )
		{
			Activity hintActivity = (Activity)CAI_BaseNPC::GetActivityID( STRING(GetHintNode()->HintActivityName()) );
			if ( hintActivity != ACT_INVALID )
			{
				SetIdealActivity( GetHintActivity(GetHintNode()->HintType(), hintActivity) );
			}
			else
			{
				int iSequence = LookupSequence(STRING(GetHintNode()->HintActivityName()));
				if ( iSequence > ACT_INVALID )
				{
					SetSequenceById( iSequence ); // ???
					SetIdealActivity( ACT_DO_NOT_DISTURB );
				}
				else
					SetIdealActivity( ACT_IDLE );
			}
		}
		else
		{
			SetIdealActivity( ACT_IDLE );
		}
		break;

	case TASK_SET_SCHEDULE:
		if ( !SetSchedule( pTask->flTaskData ) )
			TaskFail(FAIL_SCHEDULE_NOT_FOUND);
		break;

	case TASK_FIND_BACKAWAY_FROM_SAVEPOSITION:
		{
			if ( GetEnemy() != NULL )
			{
				Vector backPos;
				if ( !GetTacticalServices()->FindBackAwayPos( m_vSavePosition, &backPos ) )
				{
					// no place to backaway
					TaskFail(FAIL_NO_BACKAWAY_NODE);
				}
				else 
				{
					if (GetNavigator()->SetGoal( AI_NavGoal_t( backPos, ACT_RUN ) ) )
					{
						TaskComplete();
					}
					else
					{
						// no place to backaway
						TaskFail(FAIL_NO_ROUTE);
					}
				}
			}
			else
			{
				TaskFail(FAIL_NO_ENEMY);
			}
		}
		break;

	case TASK_FIND_NEAR_NODE_COVER_FROM_ENEMY:
	case TASK_FIND_FAR_NODE_COVER_FROM_ENEMY:
	case TASK_FIND_NODE_COVER_FROM_ENEMY:
	case TASK_FIND_COVER_FROM_ENEMY:
		{	
			bool 	bNodeCover 		= ( task != TASK_FIND_COVER_FROM_ENEMY );
			float 	flMinDistance 	= ( task == TASK_FIND_FAR_NODE_COVER_FROM_ENEMY ) ? pTask->flTaskData : 0.0;
			float 	flMaxDistance 	= ( task == TASK_FIND_NEAR_NODE_COVER_FROM_ENEMY ) ? pTask->flTaskData : FLT_MAX;
			
			if ( FindCoverFromEnemy( bNodeCover, flMinDistance, flMaxDistance ) )
			{
				if ( task == TASK_FIND_COVER_FROM_ENEMY )
					m_flMoveWaitFinished = gpGlobals->curtime + pTask->flTaskData;
				TaskComplete();
			}
			else
				TaskFail(FAIL_NO_COVER);
		}
		break;


	case TASK_FIND_COVER_FROM_ORIGIN:
		{
			Vector coverPos;

			if ( GetTacticalServices()->FindCoverPos( GetLocalOrigin(), EyePosition(), 0, CoverRadius(), &coverPos ) ) 
			{
				AI_NavGoal_t goal(coverPos, ACT_RUN, AIN_HULL_TOLERANCE);
				GetNavigator()->SetGoal( goal );

				m_flMoveWaitFinished = gpGlobals->curtime + pTask->flTaskData;
			}
			else
			{
				// no coverwhatsoever.
				TaskFail(FAIL_NO_COVER);
			}
		}

		break;

	case TASK_FIND_COVER_FROM_BEST_SOUND:
		{
		}
		break;

	case TASK_FACE_HINTNODE:
		
		// If the yaw is locked, this function will not act correctly
		Assert( GetMotor()->IsYawLocked() == false );

		GetMotor()->SetIdealYaw( GetHintNode()->Yaw() );
		GetMotor()->SetIdealYaw( CalcReasonableFacing( true ) ); // CalcReasonableFacing() is based on previously set ideal yaw
   		if ( FacingIdeal() )
   			TaskComplete();
		else
			SetTurnActivity();
		break;
	
	case TASK_FACE_LASTPOSITION:
		GetMotor()->SetIdealYawToTarget( m_vecLastPosition );
		GetMotor()->SetIdealYaw( CalcReasonableFacing( true ) ); // CalcReasonableFacing() is based on previously set ideal yaw
		SetTurnActivity(); 
		break;

	case TASK_FACE_SAVEPOSITION:
		GetMotor()->SetIdealYawToTarget( m_vSavePosition );
		GetMotor()->SetIdealYaw( CalcReasonableFacing( true ) ); // CalcReasonableFacing() is based on previously set ideal yaw
		SetTurnActivity(); 
		break;

	case TASK_FACE_AWAY_FROM_SAVEPOSITION:
		GetMotor()->SetIdealYawToTarget( m_vSavePosition, 0, 180.0 );
		GetMotor()->SetIdealYaw( CalcReasonableFacing( true ) ); // CalcReasonableFacing() is based on previously set ideal yaw
		SetTurnActivity(); 
		break;

	case TASK_SET_IDEAL_YAW_TO_CURRENT:
		GetMotor()->SetIdealYaw( UTIL_AngleMod( GetLocalAngles().y ) );
		TaskComplete();
		break;

	case TASK_FACE_TARGET:
		if ( m_hTargetEnt != NULL )
		{
			GetMotor()->SetIdealYawToTarget( m_hTargetEnt->GetAbsOrigin() );
			SetTurnActivity(); 
		}
		else
		{
			TaskFail(FAIL_NO_TARGET);
		}
		break;
		
	case TASK_FACE_PLAYER:
		// track head to the client for a while.
		SetWait( pTask->flTaskData );
		break;

#ifdef MAPBASE
	case TASK_FACE_INTERACTION_ANGLES:
		{
			if ( !m_hForcedInteractionPartner )
			{
				TaskFail( FAIL_NO_TARGET );
				return;
			}

			// Get our running interaction from our partner,
			// as this should only run with the NPC "receiving" the interaction
			ScriptedNPCInteraction_t *pInteraction = m_hForcedInteractionPartner->GetRunningDynamicInteraction();

			// Get our target's origin
			Vector vecTarget = m_hForcedInteractionPartner->GetAbsOrigin();

			// Face the angles the interaction actually wants us at, opposite to the partner
			float angInteractionAngle = pInteraction->angRelativeAngles.y;
			angInteractionAngle += 180.0f;

			GetMotor()->SetIdealYaw( CalcIdealYaw( vecTarget ) + angInteractionAngle );

			if (FacingIdeal())
				TaskComplete();
			else
			{
				GetMotor()->SetIdealYaw( CalcReasonableFacing( true ) );
				SetTurnActivity();
			}
		}
		break;
#endif

	case TASK_FACE_ENEMY:
		{
			Vector vecEnemyLKP = GetEnemyLKP();
			if (!FInAimCone( vecEnemyLKP ))
			{
				GetMotor()->SetIdealYawToTarget( vecEnemyLKP );
				GetMotor()->SetIdealYaw( CalcReasonableFacing( true ) ); // CalcReasonableFacing() is based on previously set ideal yaw
				SetTurnActivity(); 
			}
			else
			{
				float flReasonableFacing = CalcReasonableFacing( true );
				if ( fabsf( flReasonableFacing - GetMotor()->GetIdealYaw() ) < 1 )
					TaskComplete();
				else
				{
					GetMotor()->SetIdealYaw( flReasonableFacing );
					SetTurnActivity();
				}
			}
			break;
		}

	case TASK_FACE_IDEAL:
		SetTurnActivity();
		break;

	case TASK_FACE_REASONABLE:
		GetMotor()->SetIdealYaw( CalcReasonableFacing() );
		SetTurnActivity();
		break;

	case TASK_FACE_PATH:
		{
			if (!GetNavigator()->IsGoalActive())
			{
				DevWarning( 2, "No route to face!\n");
				TaskFail(FAIL_NO_ROUTE);
			}
			else
			{
				const float NPC_TRIVIAL_TURN = 15;	// (Degrees). Turns this small or smaller, don't bother with a transition.

				GetMotor()->SetIdealYawToTarget( GetNavigator()->GetCurWaypointPos());

				if( fabs( GetMotor()->DeltaIdealYaw() ) <= NPC_TRIVIAL_TURN )
				{
					// This character is already facing the path well enough that 
					// moving will look fairly natural. Don't bother with a transitional
					// turn animation.
					TaskComplete();
					break;
				}
				SetTurnActivity();
			}
		}
		break;

	// don't do anything.
	case TASK_WAIT_PVS:
	case TASK_WAIT_INDEFINITE:
		break;

	// set a future time that tells us when the wait is over.
	case TASK_WAIT:
	case TASK_WAIT_FACE_ENEMY:
		SetWait( pTask->flTaskData );
		break;

	// set a future time that tells us when the wait is over.
	case TASK_WAIT_RANDOM:
	case TASK_WAIT_FACE_ENEMY_RANDOM:
		SetWait( 0, pTask->flTaskData );
		break;

	case TASK_MOVE_TO_TARGET_RANGE:
	case TASK_MOVE_TO_GOAL_RANGE:
		{
			// Identical tasks, except that target_range uses m_hTargetEnt, 
			// and Goal range uses the nav goal
			CBaseEntity *pTarget = NULL;
			if ( task == TASK_MOVE_TO_GOAL_RANGE )
			{
				pTarget = GetNavigator()->GetGoalTarget();
			}
			if ( !pTarget )
			{
				pTarget = m_hTargetEnt.Get();
			}

			if ( pTarget == NULL)
			{
				TaskFail(FAIL_NO_TARGET);
			}
			else if ( (pTarget->GetAbsOrigin() - GetLocalOrigin()).Length() < 1 )
			{
				TaskComplete();
			}

			if (GetNavigator()->GetGoalType() == GOALTYPE_NONE)
			{
				TaskComplete();
				GetNavigator()->ClearGoal();		// Clear residual state
			}
			else
			{
				// set that we're probably going to stop before the goal
				GetNavigator()->SetArrivalDistance( pTask->flTaskData );
			}

			break;
		}

	case TASK_WAIT_UNTIL_NO_DANGER_SOUND:
		if( !HasCondition( COND_HEAR_DANGER ) )
		{
			TaskComplete();
		}
		break;

	case TASK_TARGET_PLAYER:
		{
			CBaseEntity *pPlayer = gEntList.FindEntityByName( NULL, "!player" );
			if ( pPlayer )
			{
				SetTarget( pPlayer );
				TaskComplete();
			}
			else
				TaskFail( FAIL_NO_PLAYER );
			break;
		}

	case TASK_SCRIPT_RUN_TO_TARGET:
	case TASK_SCRIPT_WALK_TO_TARGET:
	case TASK_SCRIPT_CUSTOM_MOVE_TO_TARGET:
		StartScriptMoveToTargetTask( pTask->iTask );
		break;

	case TASK_CLEAR_MOVE_WAIT:
		m_flMoveWaitFinished = gpGlobals->curtime;
		TaskComplete();
		break;

	case TASK_MELEE_ATTACK1:
		SetLastAttackTime( gpGlobals->curtime );
		ResetIdealActivity( ACT_MELEE_ATTACK1 );
		break;

	case TASK_MELEE_ATTACK2:
		SetLastAttackTime( gpGlobals->curtime );
		ResetIdealActivity( ACT_MELEE_ATTACK2 );
		break;

	case TASK_RANGE_ATTACK1:
		SetLastAttackTime( gpGlobals->curtime );
		ResetIdealActivity( ACT_RANGE_ATTACK1 );
		break;

	case TASK_RANGE_ATTACK2:
		SetLastAttackTime( gpGlobals->curtime );
		ResetIdealActivity( ACT_RANGE_ATTACK2 );
		break;

	case TASK_RELOAD:
		ResetIdealActivity( ACT_RELOAD );
		break;

	case TASK_SPECIAL_ATTACK1:
		ResetIdealActivity( ACT_SPECIAL_ATTACK1 );
		break;

	case TASK_SPECIAL_ATTACK2:
		ResetIdealActivity( ACT_SPECIAL_ATTACK2 );
		break;

	case TASK_SET_ACTIVITY:
		{
			Activity goalActivity = (Activity)((int)pTask->flTaskData);
			if (goalActivity != ACT_RESET)
			{
				SetIdealActivity( goalActivity );
			}
			else
			{
				m_Activity = ACT_RESET;
			}
			break;
		}
	case TASK_GET_CHASE_PATH_TO_ENEMY:
		{
			CBaseEntity *pEnemy = GetEnemy();
			if ( !pEnemy )
			{
				TaskFail(FAIL_NO_ROUTE);
				return;
			}

			if ( ( pEnemy->GetAbsOrigin() - GetEnemyLKP() ).LengthSqr() < Square(pTask->flTaskData) )
			{
				ChainStartTask( TASK_GET_PATH_TO_ENEMY );
			}
			else
			{
				ChainStartTask( TASK_GET_PATH_TO_ENEMY_LKP );
			}

			if ( !TaskIsComplete() && !HasCondition(COND_TASK_FAILED) )
				TaskFail(FAIL_NO_ROUTE);
			break;
		}

	case TASK_GET_PATH_TO_ENEMY_LKP:
		{
			CBaseEntity *pEnemy = GetEnemy();
			if (!pEnemy || IsUnreachable(pEnemy))
			{
				TaskFail(FAIL_NO_ROUTE);
				return;
			}
			AI_NavGoal_t goal( GetEnemyLKP() );

			TranslateNavGoal( pEnemy, goal.dest );

			if ( GetNavigator()->SetGoal( goal, AIN_CLEAR_TARGET ) )
			{
				TaskComplete();
			}
			else
			{
				// no way to get there =(
				DevWarning( 2, "GetPathToEnemyLKP failed!!\n" );
				RememberUnreachable(GetEnemy());
				TaskFail(FAIL_NO_ROUTE);
			}
			break;
		}

	case TASK_GET_PATH_TO_INTERACTION_PARTNER:
		{
			if ( !m_hForcedInteractionPartner || IsUnreachable(m_hForcedInteractionPartner) )
			{
				TaskFail(FAIL_NO_ROUTE);
				return;
			}

			// Calculate the position we need to be at to start the interaction.
			CalculateForcedInteractionPosition();

			AI_NavGoal_t goal( m_vecForcedWorldPosition );
			TranslateNavGoal( m_hForcedInteractionPartner, goal.dest );

			if ( GetNavigator()->SetGoal( goal, AIN_CLEAR_TARGET ) )
			{
				TaskComplete();
			}
			else
			{
				DevWarning( 2, "GetPathToInteractionPartner failed!!\n" );
				RememberUnreachable(m_hForcedInteractionPartner);
				TaskFail(FAIL_NO_ROUTE);
			}
			break;
		}

	case TASK_GET_PATH_TO_RANGE_ENEMY_LKP_LOS:
		{
			if ( GetEnemy() )
			{
				// Find out which range to use (either innately or a held weapon)
				float flRange = -1.0f;
				if ( CapabilitiesGet() & (bits_CAP_INNATE_RANGE_ATTACK1|bits_CAP_INNATE_RANGE_ATTACK2) )
				{
					flRange = InnateRange1MaxRange();
				}
				else if ( GetActiveWeapon() )
				{
					flRange = MAX( GetActiveWeapon()->m_fMaxRange1, GetActiveWeapon()->m_fMaxRange2 );
				}
				else
				{
					// You can't call this task without either innate range attacks or a weapon!
					Assert( 0 );
					TaskFail( FAIL_NO_ROUTE );
				}

				// Clamp to the specified range, if supplied
				if ( pTask->flTaskData != 0 && pTask->flTaskData < flRange )
					flRange = pTask->flTaskData;
						
				// For now, just try running straight at enemy
				float dist = EnemyDistance( GetEnemy() );
				if ( dist <= flRange || GetNavigator()->SetVectorGoalFromTarget( GetEnemy()->GetAbsOrigin(), dist - flRange ) )
				{
					TaskComplete();
					break;
				}
			}

			TaskFail( FAIL_NO_ROUTE );
			break;
		}
	
	case TASK_GET_PATH_TO_ENEMY_LOS:
	case TASK_GET_FLANK_RADIUS_PATH_TO_ENEMY_LOS:
	case TASK_GET_FLANK_ARC_PATH_TO_ENEMY_LOS:
	case TASK_GET_PATH_TO_ENEMY_LKP_LOS:
		{
			if ( GetEnemy() == NULL )
			{
				TaskFail(FAIL_NO_ENEMY);
				return;
			}
		
			AI_PROFILE_SCOPE(CAI_BaseNPC_FindLosToEnemy);
			float flMaxRange = 2000;
			float flMinRange = 0;
			
			if ( GetActiveWeapon() )
			{
				flMaxRange = MAX( GetActiveWeapon()->m_fMaxRange1, GetActiveWeapon()->m_fMaxRange2 );
				flMinRange = MIN( GetActiveWeapon()->m_fMinRange1, GetActiveWeapon()->m_fMinRange2 );
			}
			else if ( CapabilitiesGet() & bits_CAP_INNATE_RANGE_ATTACK1 )
			{
				flMaxRange = InnateRange1MaxRange();
				flMinRange = InnateRange1MinRange();
			}

			//Check against NPC's max range
			if ( flMaxRange > m_flDistTooFar )
			{
				flMaxRange = m_flDistTooFar;
			}

			Vector vecEnemy 	= ( task != TASK_GET_PATH_TO_ENEMY_LKP ) ? GetEnemy()->GetAbsOrigin() : GetEnemyLKP();
			Vector vecEnemyEye	= vecEnemy + GetEnemy()->GetViewOffset();

			Vector posLos;
			bool found = false;

			if ( ( task != TASK_GET_FLANK_RADIUS_PATH_TO_ENEMY_LOS ) && ( task != TASK_GET_FLANK_ARC_PATH_TO_ENEMY_LOS ) )
			{
				if ( GetTacticalServices()->FindLateralLos( vecEnemyEye, &posLos ) )
				{
					float dist = ( posLos - vecEnemyEye ).Length();
					if ( dist < flMaxRange && dist > flMinRange )
						found = true;
				}
			}
			
			if ( !found )
			{
				FlankType_t eFlankType = FLANKTYPE_NONE;
				Vector vecFlankRefPos = vec3_origin;
				float flFlankParam = 0;
			
				if ( task == TASK_GET_FLANK_RADIUS_PATH_TO_ENEMY_LOS )
				{
					eFlankType = FLANKTYPE_RADIUS;
					vecFlankRefPos = m_vSavePosition;
					flFlankParam = pTask->flTaskData;
				}
				else if ( task == TASK_GET_FLANK_ARC_PATH_TO_ENEMY_LOS )
				{
					eFlankType = FLANKTYPE_ARC;
					vecFlankRefPos = m_vSavePosition;
					flFlankParam = pTask->flTaskData;
				}

				if ( GetTacticalServices()->FindLos( vecEnemy, vecEnemyEye, flMinRange, flMaxRange, 1.0, eFlankType, vecFlankRefPos, flFlankParam, &posLos ) )
				{
					found = true;
				}
			}

			if ( !found )
			{
				TaskFail( FAIL_NO_SHOOT );
			}
			else
			{
				// else drop into run task to offer an interrupt
				m_vInterruptSavePosition = posLos;
			}
		}
		break;

//==================================================
// TASK_SET_GOAL
//==================================================

	case TASK_SET_GOAL:

		switch ( (int) pTask->flTaskData )
		{
		case GOAL_ENEMY:	//Enemy
			
			if ( GetEnemy() == NULL )
			{
				TaskFail( FAIL_NO_ENEMY );
				return;
			}
			
			//Setup our stored info
			m_vecStoredPathGoal = GetEnemy()->GetAbsOrigin();
			m_nStoredPathType	= GOALTYPE_ENEMY;
			m_fStoredPathFlags	= 0;
			m_hStoredPathTarget	= GetEnemy();
			GetNavigator()->SetMovementActivity(ACT_RUN);
			break;
		
		case GOAL_ENEMY_LKP:		//Enemy's last known position

			if ( GetEnemy() == NULL )
			{
				TaskFail( FAIL_NO_ENEMY );
				return;
			}
			
			//Setup our stored info
			m_vecStoredPathGoal = GetEnemyLKP();
			m_nStoredPathType	= GOALTYPE_LOCATION;
			m_fStoredPathFlags	= 0;
			m_hStoredPathTarget	= NULL;
			GetNavigator()->SetMovementActivity(ACT_RUN);
			break;
		
		case GOAL_TARGET:			//Target entity
			
			if ( m_hTargetEnt == NULL )
			{
				TaskFail( FAIL_NO_TARGET );
				return;
			}
			
			//Setup our stored info
			m_vecStoredPathGoal = m_hTargetEnt->GetAbsOrigin();
			m_nStoredPathType	= GOALTYPE_TARGETENT;
			m_fStoredPathFlags	= 0;
			m_hStoredPathTarget	= m_hTargetEnt;
			GetNavigator()->SetMovementActivity(ACT_RUN);
			break;

		case GOAL_SAVED_POSITION:	//Saved position
			
			//Setup our stored info
			m_vecStoredPathGoal = m_vSavePosition;
			m_nStoredPathType	= GOALTYPE_LOCATION;
			m_fStoredPathFlags	= 0;
			m_hStoredPathTarget	= NULL;
			GetNavigator()->SetMovementActivity(ACT_RUN);
			break;
		}

		TaskComplete();

		break;

//==================================================
// TASK_GET_PATH_TO_GOAL
//==================================================

	case TASK_GET_PATH_TO_GOAL:
		{
			AI_NavGoal_t goal( m_nStoredPathType, 
							   AIN_DEF_ACTIVITY, 
							   AIN_HULL_TOLERANCE,
							   AIN_DEF_FLAGS,
							   m_hStoredPathTarget );
			
			bool	foundPath = false;

			//Find our path
			switch ( (int) pTask->flTaskData )
			{
			case PATH_TRAVEL:	//A land path to our goal
				goal.dest = m_vecStoredPathGoal;
				foundPath = GetNavigator()->SetGoal( goal );
				break;

			case PATH_LOS:		//A path to get LOS to our goal
				{
					float flMaxRange = 2000.0f;
					float flMinRange = 0.0f;

					if ( GetActiveWeapon() )
					{
						flMaxRange = MAX( GetActiveWeapon()->m_fMaxRange1, GetActiveWeapon()->m_fMaxRange2 );
						flMinRange = MIN( GetActiveWeapon()->m_fMinRange1, GetActiveWeapon()->m_fMinRange2 );
					}
					else if ( CapabilitiesGet() & bits_CAP_INNATE_RANGE_ATTACK1 )
					{
						flMaxRange = InnateRange1MaxRange();
						flMinRange = InnateRange1MinRange();
					}

					// Check against NPC's max range
					if ( flMaxRange > m_flDistTooFar )
					{
						flMaxRange = m_flDistTooFar;
					}

					Vector	eyePosition = ( m_hStoredPathTarget != NULL ) ? m_hStoredPathTarget->EyePosition() : m_vecStoredPathGoal;

					Vector posLos;

					// See if we've found it
					if ( GetTacticalServices()->FindLos( m_vecStoredPathGoal, eyePosition, flMinRange, flMaxRange, 1.0f, &posLos ) )
					{
						goal.dest = posLos;
						foundPath = GetNavigator()->SetGoal( goal );
					}
					else
					{
						// No LOS to goal
						TaskFail( FAIL_NO_SHOOT );
						return;
					}
				}
				
				break;

			case PATH_COVER:	//Get a path to cover FROM our goal
				{
					CBaseEntity *pEntity = ( m_hStoredPathTarget == NULL ) ? this : m_hStoredPathTarget;

					//Find later cover first
					Vector coverPos;

					if ( GetTacticalServices()->FindLateralCover( pEntity->EyePosition(), 0, &coverPos ) )
					{
						AI_NavGoal_t goal( coverPos, ACT_RUN );
						GetNavigator()->SetGoal( goal, AIN_CLEAR_PREVIOUS_STATE );
						
 						//FIXME: What exactly is this doing internally?
						m_flMoveWaitFinished = gpGlobals->curtime + pTask->flTaskData;
						TaskComplete();
						return;
					}
					else
					{
						//Try any cover
						if ( GetTacticalServices()->FindCoverPos( pEntity->GetAbsOrigin(), pEntity->EyePosition(), 0, CoverRadius(), &coverPos ) ) 
						{
							//If we've found it, find a safe route there
							AI_NavGoal_t coverGoal( GOALTYPE_COVER, 
													coverPos,
													ACT_RUN,
													AIN_HULL_TOLERANCE,
													AIN_DEF_FLAGS,
													m_hStoredPathTarget );
							
							foundPath = GetNavigator()->SetGoal( goal );

							m_flMoveWaitFinished = gpGlobals->curtime + pTask->flTaskData;
						}
						else
						{
							TaskFail( FAIL_NO_COVER );
						}
					}
				}

				break;
			}

			//Now validate our try
			if ( foundPath )
			{
				TaskComplete();
			}
			else
			{
				TaskFail( FAIL_NO_ROUTE );
			}
		}
		break;

	case TASK_GET_PATH_TO_ENEMY:
		{
			if (IsUnreachable(GetEnemy()))
			{
				TaskFail(FAIL_NO_ROUTE);
				return;
			}

			CBaseEntity *pEnemy = GetEnemy();

			if ( pEnemy == NULL )
			{
				TaskFail(FAIL_NO_ENEMY);
				return;
			}
						
			if ( GetNavigator()->SetGoal( GOALTYPE_ENEMY ) )
			{
				TaskComplete();
			}
			else
			{
				// no way to get there =( 
				DevWarning( 2, "GetPathToEnemy failed!!\n" );
				RememberUnreachable(GetEnemy());
				TaskFail(FAIL_NO_ROUTE);
			}
			break;
		}
	case TASK_GET_PATH_TO_ENEMY_CORPSE:
		{
			Vector forward;
			AngleVectors( GetLocalAngles(), &forward );
			Vector vecEnemyLKP = GetEnemyLKP();

			GetNavigator()->SetGoal( vecEnemyLKP - forward * 64, AIN_CLEAR_TARGET);
		}
		break;

	case TASK_GET_PATH_TO_PLAYER:
		{
			CBaseEntity *pPlayer = gEntList.FindEntityByName( NULL, "!player" );

			AI_NavGoal_t goal;

			goal.type = GOALTYPE_LOCATION;
			goal.dest = pPlayer->WorldSpaceCenter();
			goal.pTarget = pPlayer;

			GetNavigator()->SetGoal( goal );
			break;
		}

	case TASK_GET_PATH_TO_SAVEPOSITION_LOS:
	{
		if ( GetEnemy() == NULL )
		{
			TaskFail(FAIL_NO_ENEMY);
			return;
		}
	
		float flMaxRange = 2000;
		float flMinRange = 0;
		if ( GetActiveWeapon() )
		{
			flMaxRange = MAX(GetActiveWeapon()->m_fMaxRange1,GetActiveWeapon()->m_fMaxRange2);
			flMinRange = MIN(GetActiveWeapon()->m_fMinRange1,GetActiveWeapon()->m_fMinRange2);
		}
		else if ( CapabilitiesGet() & bits_CAP_INNATE_RANGE_ATTACK1 )
		{
			flMaxRange = InnateRange1MaxRange();
			flMinRange = InnateRange1MinRange();
		}

		// Check against NPC's max range
		if (flMaxRange > m_flDistTooFar)
		{
			flMaxRange = m_flDistTooFar;
		}

		Vector posLos;

		if (GetTacticalServices()->FindLos(m_vSavePosition,m_vSavePosition, flMinRange, flMaxRange, 1.0, &posLos))
		{
			GetNavigator()->SetGoal( AI_NavGoal_t( posLos, ACT_RUN, AIN_HULL_TOLERANCE ) );
		}
		else
		{
			// no coverwhatsoever.
			TaskFail(FAIL_NO_SHOOT);
		}
		break;
	}

	case TASK_GET_PATH_TO_TARGET_WEAPON:
		{
			// Finds the nearest node within the leniency distances,
			// whether the node can see the target or not.
			const float XY_LENIENCY = 64.0;
			const float Z_LENIENCY	= 72.0;

			if (m_hTargetEnt == NULL)
			{
				TaskFail(FAIL_NO_TARGET);
			}
			else 
			{
				// Since this weapon MAY be on a table, we find the nearest node without verifying
				// line-of-sight, since weapons on the table will not be able to see nodes very nearby.
				int node = GetNavigator()->GetNetwork()->NearestNodeToPoint( this, m_hTargetEnt->GetAbsOrigin(), false );
				CAI_Node *pNode = GetNavigator()->GetNetwork()->GetNode( node );

				if( !pNode )
				{
					TaskFail( FAIL_NO_ROUTE );
					break;
				}

				bool bHasPath = true;
				Vector vecNodePos;

				vecNodePos = pNode->GetPosition( GetHullType() );

				float flDistZ;
				flDistZ = fabs( vecNodePos.z - m_hTargetEnt->GetAbsOrigin().z );
				if( flDistZ > Z_LENIENCY )
				{
					// The gun is too far away from its nearest node on the Z axis.
					TaskFail( "Target not within Z_LENIENCY!\n");
					CBaseCombatWeapon *pWeapon = dynamic_cast<CBaseCombatWeapon*>( m_hTargetEnt.Get() );
					if( pWeapon )
					{
						// Lock this weapon for a long time so no one else tries to get it.
						pWeapon->Lock( 30.0f, pWeapon );
						break;
					}
				}

				if( flDistZ >= 16.0 )
				{
					// The gun is higher or lower, but it's within reach. (probably on a table).
					float flDistXY = ( vecNodePos - m_hTargetEnt->GetAbsOrigin() ).Length2D();

					// This means we have to stand on the nearest node and still be able to
					// reach the gun.
					if( flDistXY > XY_LENIENCY )
					{
						TaskFail( "Target not within XY_LENIENCY!\n" );
						CBaseCombatWeapon *pWeapon = dynamic_cast<CBaseCombatWeapon*>( m_hTargetEnt.Get() );
						if( pWeapon )
						{
							// Lock this weapon for a long time so no one else tries to get it.
							pWeapon->Lock( 30.0f, pWeapon );
							break;
						}
					}

					AI_NavGoal_t goal( vecNodePos );
					goal.pTarget = m_hTargetEnt;
					bHasPath = GetNavigator()->SetGoal( goal );
				}
				else
				{
					// The gun is likely just lying on the floor. Just pick it up.
					AI_NavGoal_t goal( m_hTargetEnt->GetAbsOrigin() );
					goal.pTarget = m_hTargetEnt;
					bHasPath = GetNavigator()->SetGoal( goal );
				}

				if( !bHasPath )
				{
					CBaseCombatWeapon *pWeapon = dynamic_cast<CBaseCombatWeapon*>( m_hTargetEnt.Get() );
					if( pWeapon )
					{
						// Lock this weapon for a long time so no one else tries to get it.
						pWeapon->Lock( 15.0f, pWeapon );
					}
				}
			}
		}
		break;

	case TASK_GET_PATH_TO_TARGET:
		{
			if (m_hTargetEnt == NULL)
			{
				TaskFail(FAIL_NO_TARGET);
			}
			else 
			{
				AI_NavGoal_t goal( static_cast<const Vector&>(m_hTargetEnt->EyePosition()) );
				goal.pTarget = m_hTargetEnt;
				GetNavigator()->SetGoal( goal );
			}
			break;
		}

	case TASK_GET_PATH_TO_HINTNODE:// for active idles!
		{
			if (!GetHintNode())
			{
				TaskFail(FAIL_NO_HINT_NODE);
			}
			else
			{
				Vector vHintPos;
				GetHintNode()->GetPosition(this, &vHintPos);

				GetNavigator()->SetGoal( AI_NavGoal_t( vHintPos, ACT_RUN ) );
				if ( pTask->flTaskData == 0 )
					GetNavigator()->SetArrivalDirection( GetHintNode()->GetDirection() );
				if ( GetHintNode()->HintActivityName() != NULL_STRING )
				{
					Activity hintActivity = (Activity)CAI_BaseNPC::GetActivityID( STRING(GetHintNode()->HintActivityName()) );
					if ( hintActivity != ACT_INVALID )
					{
						GetNavigator()->SetArrivalActivity( GetHintActivity(GetHintNode()->HintType(), hintActivity) );
					}
					else
					{
						int iSequence = LookupSequence(STRING(GetHintNode()->HintActivityName()));;
						if ( iSequence != ACT_INVALID )
							GetNavigator()->SetArrivalSequence( iSequence );
					}

				}
			}
			break;
		}

	case TASK_GET_PATH_TO_COMMAND_GOAL:
		{
			if (!GetNavigator()->SetGoal( m_vecCommandGoal ))
			{
				OnMoveToCommandGoalFailed();
				TaskFail(FAIL_NO_ROUTE);
			}
			break;
		}

	case TASK_MARK_COMMAND_GOAL_POS:
		// Start watching my position to detect whether another AI process has moved me from my mark.
		m_CommandMoveMonitor.SetMark( this, COMMAND_GOAL_TOLERANCE );
		TaskComplete();
		break;

	case TASK_CLEAR_COMMAND_GOAL:
		m_vecCommandGoal = vec3_invalid;
		TaskComplete();
		break;
		
	case TASK_GET_PATH_TO_LASTPOSITION:
		{
			if (!GetNavigator()->SetGoal( m_vecLastPosition ))
			{
				TaskFail(FAIL_NO_ROUTE);
			}
			else
			{
				GetNavigator()->SetGoalTolerance( 48 );
			}
			break;
		}

	case TASK_GET_PATH_TO_SAVEPOSITION:
		{
			GetNavigator()->SetGoal( m_vSavePosition );
			break;
		}


	case TASK_GET_PATH_TO_RANDOM_NODE:  // Task argument is lenth of path to build
		{
			if ( GetNavigator()->SetRandomGoal( pTask->flTaskData ) )
				TaskComplete();
			else
				TaskFail(FAIL_NO_REACHABLE_NODE);
		
			break;
		}

	case TASK_GET_PATH_TO_BESTSOUND:
		{

			CSound *pSound = GetBestSound();
			if (!pSound)
			{
				TaskFail(FAIL_NO_SOUND);
			}
			else
			{
				GetNavigator()->SetGoal( pSound->GetSoundReactOrigin() );
			}
			break;
		}
	case TASK_GET_PATH_TO_BESTSCENT:
		{

			CSound *pScent = GetBestScent();
			if (!pScent) 
			{
				TaskFail(FAIL_NO_SCENT);
			}
			else
			{
				GetNavigator()->SetGoal( pScent->GetSoundOrigin() );
			}
			break;
		}
	
	case TASK_GET_PATH_AWAY_FROM_BEST_SOUND:
	{
		CSound *pBestSound = GetBestSound();
		if ( !pBestSound )
		{
			TaskFail("No Sound!");
			break;
		}

		GetMotor()->SetIdealYawToTarget( pBestSound->GetSoundOrigin() );
		ChainStartTask( TASK_MOVE_AWAY_PATH, pTask->flTaskData );
		LockBestSound();
		break;
	}	
	
	case TASK_MOVE_AWAY_PATH:
		{
			// Drop into run task to support interrupt
			DesireStand();
		}
		break;

	case TASK_WEAPON_RUN_PATH:
	case TASK_ITEM_RUN_PATH:
		GetNavigator()->SetMovementActivity(ACT_RUN);
		break;

	case TASK_RUN_PATH:
		{
			// UNDONE: This is in some default AI and some NPCs can't run? -- walk instead?
			if ( TranslateActivity( ACT_RUN ) != ACT_INVALID )
			{
				GetNavigator()->SetMovementActivity( ACT_RUN );
			}
			else
			{
				GetNavigator()->SetMovementActivity(ACT_WALK);
			}
			// Cover is void once I move
			Forget( bits_MEMORY_INCOVER );
			TaskComplete();
			break;
		}

	case TASK_WALK_PATH_FOR_UNITS:
	{
		GetNavigator()->SetMovementActivity(ACT_WALK);
		break;
	}
		
	case TASK_RUN_PATH_FOR_UNITS:
	{
		GetNavigator()->SetMovementActivity(ACT_RUN);
		break;
	}	
	
	case TASK_WALK_PATH:
		{
			bool bIsFlying = (GetMoveType() == MOVETYPE_FLY) || (GetMoveType() == MOVETYPE_FLYGRAVITY);
			if ( bIsFlying && ( TranslateActivity( ACT_FLY ) != ACT_INVALID) )
			{
				GetNavigator()->SetMovementActivity(ACT_FLY);
			}
			else if ( TranslateActivity( ACT_WALK ) != ACT_INVALID )
			{
				GetNavigator()->SetMovementActivity(ACT_WALK);
			}
			else
			{
				GetNavigator()->SetMovementActivity(ACT_RUN);
			}
			// Cover is void once I move
			Forget( bits_MEMORY_INCOVER );
			TaskComplete();
			break;
		}
	case TASK_WALK_PATH_WITHIN_DIST:
		{
			GetNavigator()->SetMovementActivity(ACT_WALK);
			// set that we're probably going to stop before the goal
			GetNavigator()->SetArrivalDistance( pTask->flTaskData );
			break;
		}
	case TASK_RUN_PATH_WITHIN_DIST:
		{
			GetNavigator()->SetMovementActivity(ACT_RUN);
			// set that we're probably going to stop before the goal
			GetNavigator()->SetArrivalDistance( pTask->flTaskData );
			break;
		}
	case TASK_RUN_PATH_FLEE:
		{
			Vector vecDiff;
			vecDiff = GetLocalOrigin() - GetNavigator()->GetGoalPos();

			if( vecDiff.Length() <= pTask->flTaskData )
			{
				GetNavigator()->StopMoving();
				TaskFail("Flee path shorter than task parameter");
			}
			else
			{
				GetNavigator()->SetMovementActivity(ACT_RUN);
			}

			break;
		}
	case TASK_WALK_PATH_TIMED:
		{
			GetNavigator()->SetMovementActivity(ACT_WALK);
			SetWait( pTask->flTaskData );
			break;
		}
	case TASK_RUN_PATH_TIMED:
		{
			GetNavigator()->SetMovementActivity(ACT_RUN);
			SetWait( pTask->flTaskData );
			break;
		}
	case TASK_STRAFE_PATH:
		{
			Vector2D vec2DirToPoint; 
			Vector2D vec2RightSide;

			// to start strafing, we have to first figure out if the target is on the left side or right side
			Vector right;
			AngleVectors( GetLocalAngles(), NULL, &right, NULL );

			vec2DirToPoint = ( GetNavigator()->GetCurWaypointPos() - GetLocalOrigin() ).AsVector2D();
			Vector2DNormalize(vec2DirToPoint);
			vec2RightSide = right.AsVector2D();
			Vector2DNormalize(vec2RightSide);

			if ( DotProduct2D ( vec2DirToPoint, vec2RightSide ) > 0 )
			{
				// strafe right
				GetNavigator()->SetMovementActivity(ACT_STRAFE_RIGHT);
			}
			else
			{
				// strafe left
				GetNavigator()->SetMovementActivity(ACT_STRAFE_LEFT);
			}
			TaskComplete();
			break;
		}

	case TASK_WAIT_FOR_MOVEMENT_STEP:
		{
			if(!GetNavigator()->IsGoalActive())
			{
				TaskComplete();
				return;
			}

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

	case TASK_WAIT_FOR_MOVEMENT:
		{
			if (GetNavigator()->GetGoalType() == GOALTYPE_NONE)
			{
				TaskComplete();
				GetNavigator()->ClearGoal();		// Clear residual state
			}
			else if (!GetNavigator()->IsGoalActive())
			{
				SetIdealActivity( GetStoppedActivity() );
			}
			else
			{
				// Check validity of goal type
				ValidateNavGoal();
			}
			break;
		}
	case TASK_SMALL_FLINCH:
		{
			Remember(bits_MEMORY_FLINCHED);
			SetIdealActivity( GetFlinchActivity( false, false ) );
			m_flNextFlinchTime = gpGlobals->curtime + random->RandomFloat( 3, 5 );
			break;
		}
	case TASK_BIG_FLINCH:
		{
			Remember(bits_MEMORY_FLINCHED);
			SetIdealActivity( GetFlinchActivity( true, false ) );
			m_flNextFlinchTime = gpGlobals->curtime + random->RandomFloat( 3, 5 );
			break;
		}
	case TASK_DIE:
		{
			GetNavigator()->StopMoving();	
			SetIdealActivity( GetDeathActivity() );
			m_lifeState = LIFE_DYING;

			break;
		}
	case TASK_SOUND_WAKE:
		{
			AlertSound();
			TaskComplete();
			break;
		}
	case TASK_SOUND_DIE:
		{
			CTakeDamageInfo info;
			DeathSound( info );
			TaskComplete();
			break;
		}
	case TASK_SOUND_IDLE:
		{
			IdleSound();
			TaskComplete();
			break;
		}
	case TASK_SOUND_PAIN:
		{
			CTakeDamageInfo info;
			PainSound( info );
			TaskComplete();
			break;
		}
	case TASK_SOUND_ANGRY:
		{
			// sounds are complete as soon as we get here, cause we've already played them.
			CGMsg( 2, CON_GROUP_NPC_AI, "SOUND\n" );			
			TaskComplete();
			break;
		}
	case TASK_SPEAK_SENTENCE:
		{
			SpeakSentence(pTask->flTaskData);	
			TaskComplete();
			break;
		}
	case TASK_WAIT_FOR_SPEAK_FINISH:
		{
			if ( !GetExpresser() )
				TaskComplete();
			else
			{
				// Are we waiting for our speech to end? Or for the mutex to be free?
				if ( pTask->flTaskData )
				{
					// Waiting for our speech to end
					if ( GetExpresser()->CanSpeakAfterMyself() )
					{
						TaskComplete();
					}
				}
				else
				{
					// Waiting for the speech & the delay afterwards
					if ( !GetExpresser()->IsSpeaking() )
					{
						TaskComplete();
					}
				}
				break;

			}
			break;
		}
	case TASK_WAIT_FOR_SCRIPT:
		{
			if ( !m_hCine )
			{
				CGMsg( 1, CON_GROUP_NPC_SCRIPTS, "Scripted sequence destroyed while in use\n" );
				TaskFail( FAIL_SCHEDULE_NOT_FOUND );
				break;
			}

			break;
		}
	case TASK_PUSH_SCRIPT_ARRIVAL_ACTIVITY:
		{
			if ( !m_hCine )
			{
				CGMsg( 1, CON_GROUP_NPC_SCRIPTS, "Scripted sequence destroyed while in use\n" );
				TaskFail( FAIL_SCHEDULE_NOT_FOUND );
				break;
			}

			string_t iszArrivalText;

#ifdef MAPBASE
			if ( m_hCine->m_iszPreIdle != NULL_STRING )
			{
				iszArrivalText = m_hCine->m_iszPreIdle;
			}
			else
#endif
			if ( m_hCine->m_iszEntry != NULL_STRING )
			{
				iszArrivalText = m_hCine->m_iszEntry;
			}
			else if ( m_hCine->m_iszPlay != NULL_STRING )
			{
				iszArrivalText = m_hCine->m_iszPlay;
			}
			else if ( m_hCine->m_iszPostIdle != NULL_STRING )
			{
				iszArrivalText = m_hCine->m_iszPostIdle;
			}
			else
				iszArrivalText = NULL_STRING;

			m_ScriptArrivalActivity = AIN_DEF_ACTIVITY;
			m_strScriptArrivalSequence = NULL_STRING;

			if ( iszArrivalText != NULL_STRING )
			{
				m_ScriptArrivalActivity = (Activity)GetActivityID( STRING( iszArrivalText ) );
				if ( m_ScriptArrivalActivity == ACT_INVALID )
					m_strScriptArrivalSequence = iszArrivalText;
			}

			TaskComplete();
			break;
		}

	case TASK_PLAY_SCRIPT:
		{
			// Throw away any stopping paths we have saved, because we 
			// won't be able to resume them after the sequence.
			GetNavigator()->IgnoreStoppingPath();

			if ( HasMovement( GetSequence() ) || m_hCine->m_bIgnoreGravity )
			{
				AddFlag( FL_FLY );
				SetGroundEntity( NULL );
			}

			if (m_hCine)
			{
				m_hCine->SynchronizeSequence( this );
			}
			//
			// Start playing a scripted sequence.
			//
			m_scriptState = SCRIPT_PLAYING;
			break;
		}
	case TASK_PLAY_SCRIPT_POST_IDLE:
		{
			//
			// Start playing a scripted post idle.
			//
			m_scriptState = SCRIPT_POST_IDLE;
			break;
		}

	// This is the first task of every schedule driven by a scripted_sequence.
	// Delay starting the sequence until all actors have hit their marks.
	case TASK_PRE_SCRIPT:
		{
			if ( !ai_task_pre_script.GetBool() )
			{
				TaskComplete();
			}
			else if ( !m_hCine )
			{
				TaskComplete();
				//DevMsg( "Scripted sequence destroyed while in use\n" );
				//TaskFail( FAIL_SCHEDULE_NOT_FOUND );
			}
			else
			{
				m_hCine->DelayStart( true );
				TaskComplete();
			}
			break;
		}

	case TASK_ENABLE_SCRIPT:
		{
			//
			// Start waiting to play a script. Play the script's pre idle animation if one
			// is specified, otherwise just go to our default idle activity.
			//
			if ( m_hCine->m_iszPreIdle != NULL_STRING )
			{
#ifdef MAPBASE
				m_hCine->OnPreIdleSequence( this );
#endif
				m_hCine->StartSequence( ( CAI_BaseNPC * )this, m_hCine->m_iszPreIdle, false );
				if ( FStrEq( STRING( m_hCine->m_iszPreIdle ), STRING( m_hCine->m_iszPlay ) ) )
				{
					m_flPlaybackRate = 0;
				}
			}
			else if ( m_scriptState != SCRIPT_CUSTOM_MOVE_TO_MARK )
			{
				// FIXME: too many ss assume its safe to leave the npc is whatever sequence they were in before, so only slam their activity
				//		  if they're playing a recognizable movement animation
				//
#ifdef HL2_EPISODIC
				// dvs: Check current activity rather than ideal activity. Since scripted NPCs early out in MaintainActivity,
				//      they'll never reach their ideal activity if it's different from their current activity.
				if ( GetActivity() == ACT_WALK || 
					 GetActivity() == ACT_RUN || 
					 GetActivity() == ACT_WALK_AIM || 
					 GetActivity() == ACT_RUN_AIM )
				{
					SetActivity( ACT_IDLE );
				}
#else
				if ( GetIdealActivity() == ACT_WALK || 
					 GetIdealActivity() == ACT_RUN || 
					 GetIdealActivity() == ACT_WALK_AIM || 
					 GetIdealActivity() == ACT_RUN_AIM )
				{
					SetActivity( ACT_IDLE );
				}
#endif // HL2_EPISODIC
			}
			break;
		}
	case TASK_PLANT_ON_SCRIPT:
		{
			if ( m_hTargetEnt != NULL )
			{
				SetLocalOrigin( m_hTargetEnt->GetAbsOrigin() );	// Plant on target
			}

			TaskComplete();
			break;
		}
	case TASK_FACE_SCRIPT:
		{
			if ( m_hTargetEnt != NULL )
			{
				GetMotor()->SetIdealYaw( UTIL_AngleMod( m_hTargetEnt->GetLocalAngles().y ) );
			}

			if ( m_scriptState != SCRIPT_CUSTOM_MOVE_TO_MARK )
			{
				SetTurnActivity();
				
				// dvs: HACK: MaintainActivity won't do anything while scripted, so go straight there.
				SetActivity( GetIdealActivity() );
			}

			GetNavigator()->StopMoving();
			break;
		}

	case TASK_PLAY_SCENE:
		{
			// inside a scene with movement and sequence commands
			break;
		}


	case TASK_SUGGEST_STATE:
		{
			SetIdealState( (NPC_STATE)(int)pTask->flTaskData );
			TaskComplete();
			break;
		}

	case TASK_SET_FAIL_SCHEDULE:
		m_failSchedule = (int)pTask->flTaskData;
		TaskComplete();
		break;

	case TASK_SET_TOLERANCE_DISTANCE:
		GetNavigator()->SetGoalTolerance( (int)pTask->flTaskData );
		TaskComplete();
		break;

	case TASK_SET_ROUTE_SEARCH_TIME:
		GetNavigator()->SetMaxRouteRebuildTime( (int)pTask->flTaskData );
		TaskComplete();
		break;

	case TASK_CLEAR_FAIL_SCHEDULE:
		m_failSchedule = SCHED_NONE;
		TaskComplete();
		break;

	case TASK_WEAPON_FIND:
		{
			m_hTargetEnt = Weapon_FindUsable( Vector(1000,1000,1000) );
			if (m_hTargetEnt)
			{
				TaskComplete();
			}
			else
			{
				TaskFail(FAIL_ITEM_NO_FIND);
			}
		}
		break;

	case TASK_ITEM_PICKUP:
		{
			SetIdealActivity( ACT_PICKUP_GROUND );
		}
		break;

	case TASK_WEAPON_PICKUP:
		{
  			if( GetActiveWeapon() )
  			{
  				Weapon_Drop( GetActiveWeapon() );
  			}

			if( GetTarget() )
			{
				CBaseCombatWeapon *pWeapon = dynamic_cast<CBaseCombatWeapon*>(GetTarget());
				if( pWeapon )
				{
					if( Weapon_IsOnGround( pWeapon ) )
					{
						// Squat down
						SetIdealActivity( ACT_PICKUP_GROUND );
					}
					else
					{
						// Reach and take this weapon from rack or shelf.
						SetIdealActivity( ACT_PICKUP_RACK );
					}

					return;
				}
			}

			TaskFail("Weapon went away!\n");
		}
		break;

	case TASK_WEAPON_CREATE:
		{
			if( !GetActiveWeapon() && GetTarget() )
			{
				// Create a copy of the weapon this NPC is trying to pick up.
				CBaseCombatWeapon *pTargetWeapon = dynamic_cast<CBaseCombatWeapon*>(GetTarget());

				if( pTargetWeapon )
				{
					CBaseCombatWeapon *pWeapon = Weapon_Create( pTargetWeapon->GetClassname() );
					if ( pWeapon )
					{
						Weapon_Equip( pWeapon );
					}
				}
			}
			SetTarget( NULL );
			TaskComplete();
		}
		break;

	case TASK_USE_SMALL_HULL:
		{
			SetHullSizeSmall();
			TaskComplete();
		}
		break;
	
	case TASK_FALL_TO_GROUND:
		// Set a wait time to try to force a ground ent.
		SetWait(4);
		break;
		
	case TASK_WANDER:
		{
			// This task really uses 2 parameters, so we have to extract
			// them from a single integer. To send both parameters, the
			// formula is MIN_DIST * 10000 + MAX_DIST
			{
				int iMinDist, iMaxDist, iParameter;

				iParameter = pTask->flTaskData;

				iMinDist = iParameter / 10000;
				iMaxDist = iParameter - (iMinDist * 10000);

				if ( GetNavigator()->SetWanderGoal( iMinDist, iMaxDist ) )
					TaskComplete();
				else
					TaskFail(FAIL_NO_REACHABLE_NODE);
			}
		}
		break;

	case TASK_FREEZE:
		m_flPlaybackRate = 0;
		break;

	case TASK_GATHER_CONDITIONS:
		GatherConditions();
		TaskComplete();
		break;

	case TASK_IGNORE_OLD_ENEMIES:
		m_flAcceptableTimeSeenEnemy = gpGlobals->curtime;
		if ( GetEnemy() && GetEnemyLastTimeSeen() < m_flAcceptableTimeSeenEnemy )
		{
			CBaseEntity *pNewEnemy = BestEnemy();

			Assert( pNewEnemy != GetEnemy() );

			if( pNewEnemy != NULL )
			{
				// New enemy! Clear the timers and set conditions.
				SetEnemy( pNewEnemy );
				SetState( NPC_STATE_COMBAT );
			}
			else
			{
				SetEnemy( NULL );
				ClearAttackConditions();
			}
		}
		TaskComplete();
		break;

	case TASK_ADD_HEALTH:
		TakeHealth( (int)pTask->flTaskData, DMG_GENERIC );
		TaskComplete();
		break;

	default:
		{
			CGMsg( 1, CON_GROUP_NPC_AI, "No StartTask entry for %s\n", TaskName( task ) );
		}
		break;
	}
}

void CAI_BaseNPC::StartTaskOverlay()
{
	if ( IsCurTaskContinuousMove() )
	{
		if ( ShouldMoveAndShoot() )
		{
			m_MoveAndShootOverlay.StartShootWhileMove();
		}
		else
		{
			m_MoveAndShootOverlay.NoShootWhileMove();
		}
	}
}


//-----------------------------------------------------------------------------
// TASK_DIE.
//-----------------------------------------------------------------------------
void CAI_BaseNPC::RunDieTask()
{
	AutoMovement();

	if ( IsActivityFinished() && GetCycle() >= 1.0f )
	{
		m_lifeState = LIFE_DEAD;
		
		SetThink ( NULL );
		StopAnimation();

		if ( !BBoxFlat() )
		{
			// a bit of a hack. If a corpses' bbox is positioned such that being left solid so that it can be attacked will
			// block the player on a slope or stairs, the corpse is made nonsolid. 
//					SetSolid( SOLID_NOT );
			UTIL_SetSize ( this, Vector ( -4, -4, 0 ), Vector ( 4, 4, 1 ) );
		}
		else // !!!HACKHACK - put NPC in a thin, wide bounding box until we fix the solid type/bounding volume problem
			UTIL_SetSize ( this, WorldAlignMins(), Vector ( WorldAlignMaxs().x, WorldAlignMaxs().y, WorldAlignMins().z + 1 ) );
	}
}


//-----------------------------------------------------------------------------
// TASK_RANGE_ATTACK1 / TASK_RANGE_ATTACK2 / etc.
//-----------------------------------------------------------------------------
void CAI_BaseNPC::RunAttackTask( int task )
{
	AutoMovement( );

	Vector vecEnemyLKP = GetEnemyLKP();

	// If our enemy was killed, but I'm not done animating, the last known position comes
	// back as the origin and makes the me face the world origin if my attack schedule
	// doesn't break when my enemy dies. (sjb)
	if( vecEnemyLKP != vec3_origin )
	{
		if ( ( task == TASK_RANGE_ATTACK1 || task == TASK_RELOAD ) && 
			 ( CapabilitiesGet() & bits_CAP_AIM_GUN ) && 
			 FInAimCone( vecEnemyLKP ) )
		{
			// Arms will aim, so leave body yaw as is
			GetMotor()->SetIdealYawAndUpdate( GetMotor()->GetIdealYaw(), AI_KEEP_YAW_SPEED );
		}
		else
		{
			GetMotor()->SetIdealYawToTargetAndUpdate( vecEnemyLKP, AI_KEEP_YAW_SPEED );
		}
	}

	if ( IsActivityFinished() )
	{
		if ( task == TASK_RELOAD && GetShotRegulator() )
		{
			GetShotRegulator()->Reset( false );
		}

		TaskComplete();
	}
}


//=========================================================
// RunTask 
//=========================================================
void CAI_BaseNPC::RunTask( const Task_t *pTask )
{
	VPROF_BUDGET( "CAI_BaseNPC::RunTask", VPROF_BUDGETGROUP_NPCS );
	switch ( pTask->iTask )
	{
	case TASK_GET_PATH_TO_RANDOM_NODE:
		{
			break;
		}
	case TASK_TURN_RIGHT:
	case TASK_TURN_LEFT:
		{
			// If the yaw is locked, this function will not act correctly
			Assert( GetMotor()->IsYawLocked() == false );

			GetMotor()->UpdateYaw();

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

	case TASK_PLAY_PRIVATE_SEQUENCE_FACE_ENEMY:
	case TASK_PLAY_SEQUENCE_FACE_ENEMY:
	case TASK_PLAY_SEQUENCE_FACE_TARGET:
		{
			CBaseEntity *pTarget;

			if ( pTask->iTask == TASK_PLAY_SEQUENCE_FACE_TARGET )
				pTarget = m_hTargetEnt;
			else
				pTarget = GetEnemy();
			if ( pTarget )
			{
				GetMotor()->SetIdealYawAndUpdate( pTarget->GetAbsOrigin() - GetLocalOrigin() , AI_KEEP_YAW_SPEED );
			}

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

	case TASK_PLAY_HINT_ACTIVITY:
		{
			if (!GetHintNode())
			{
				TaskFail(FAIL_NO_HINT_NODE);
			}

			// Put a debugging check in here
			if (GetHintNode()->User() != this)
			{
				CGMsg( 1, CON_GROUP_NPC_AI, "Hint node (%s) being used by non-owner!\n", GetHintNode()->GetDebugName() );
			}

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

			break;
		}

	case TASK_STOP_MOVING:
		{
			if ( pTask->flTaskData == 1 )
			{
				ChainRunTask( TASK_WAIT_FOR_MOVEMENT );
				if ( GetTaskStatus() == TASKSTATUS_COMPLETE )
				{
					DbgNavMsg( this, "TASK_STOP_MOVING Complete\n" );
				}
			}
			else
			{
				// if they're jumping, wait until they land
				if (GetNavType() == NAV_JUMP)
				{
					if (GetFlags() & FL_ONGROUND)
					{
						DbgNavMsg( this, "Jump landed\n" );
						SetNavType( NAV_GROUND ); // this assumes that NAV_JUMP only happens with npcs that use NAV_GROUND as base movement
					}
					else if (GetSmoothedVelocity().Length() > 0.01) // use an EPSILON damnit!!
					{
						// wait until you land
						break;
					}
					else
					{
						DbgNavMsg( this, "Jump stuck\n" );
						// stopped and stuck!
						SetNavType( NAV_GROUND );
						TaskFail( FAIL_STUCK_ONTOP );						
					}
				}

				// @TODO (toml 10-30-02): this is unacceptable, but needed until navigation can handle commencing
				// 						  a navigation while in the middle of a climb
				if (GetNavType() == NAV_CLIMB)
				{
					// wait until you reach the end
					break;
				}

				DbgNavMsg( this, "TASK_STOP_MOVING Complete\n" );
				SetIdealActivity( GetStoppedActivity() );

				TaskComplete();
			}
			break;
		}

	case TASK_PLAY_SEQUENCE:
	case TASK_PLAY_PRIVATE_SEQUENCE:
		{
			AutoMovement( );
			if ( IsActivityFinished() )
			{
				TaskComplete();
			}
			break;
		}

	case TASK_ADD_GESTURE_WAIT:
		{
			if ( IsWaitFinished() )
			{
				TaskComplete();
			}
			break;
		}

	case TASK_SET_ACTIVITY:
		{
			if ( IsActivityStarted() )
			{
				TaskComplete();
			}
		}
		break;

	case TASK_FACE_ENEMY:
		{
			// If the yaw is locked, this function will not act correctly
			Assert( GetMotor()->IsYawLocked() == false );

			Vector vecEnemyLKP = GetEnemyLKP();
			if (!FInAimCone( vecEnemyLKP ))
			{
				GetMotor()->SetIdealYawToTarget( vecEnemyLKP );
				GetMotor()->SetIdealYaw( CalcReasonableFacing( true ) ); // CalcReasonableFacing() is based on previously set ideal yaw
			}
			else
			{
				float flReasonableFacing = CalcReasonableFacing( true );
				if ( fabsf( flReasonableFacing - GetMotor()->GetIdealYaw() ) > 1 )
					GetMotor()->SetIdealYaw( flReasonableFacing );
			}

			GetMotor()->UpdateYaw();
			
			if ( FacingIdeal() )
			{
				TaskComplete();
			}
			break;
		}
	case TASK_FACE_PLAYER:
		{
			// Get edict for one player
			CBasePlayer *pPlayer = AI_GetSinglePlayer();
			if ( pPlayer )
			{
				GetMotor()->SetIdealYawToTargetAndUpdate( pPlayer->GetAbsOrigin(), AI_KEEP_YAW_SPEED );
				SetTurnActivity();
				if ( IsWaitFinished() && GetMotor()->DeltaIdealYaw() < 10 )
				{
					TaskComplete();
				}
			}
			else
			{
				TaskFail(FAIL_NO_PLAYER);
			}
		}
		break;

#ifdef MAPBASE
	case TASK_FACE_INTERACTION_ANGLES:
		{
			if ( !m_hForcedInteractionPartner )
			{
				TaskFail( FAIL_NO_TARGET );
				return;
			}

			// Get our running interaction from our partner,
			// as this should only run with the NPC "receiving" the interaction
			ScriptedNPCInteraction_t *pInteraction = m_hForcedInteractionPartner->GetRunningDynamicInteraction();

			// Get our target's origin
			Vector vecTarget = m_hForcedInteractionPartner->GetAbsOrigin();

			// Face the angles the interaction actually wants us at, opposite to the partner
			float angInteractionAngle = pInteraction->angRelativeAngles.y;
			angInteractionAngle += 180.0f;

			GetMotor()->SetIdealYawAndUpdate( CalcIdealYaw( vecTarget ) + angInteractionAngle, AI_KEEP_YAW_SPEED );

			if (IsWaitFinished())
			{
				TaskComplete();
			}
		}
		break;
#endif

	case TASK_FIND_COVER_FROM_BEST_SOUND:
		{
			switch( GetTaskInterrupt() )
			{
			case 0:
				{
					if ( !FindCoverFromBestSound( &m_vInterruptSavePosition ) )
						TaskFail(FAIL_NO_COVER);
					else
					{
						GetNavigator()->IgnoreStoppingPath();
						LockBestSound();
						TaskInterrupt();
					}
				}
				break;

			case 1:
				{
					AI_NavGoal_t goal(m_vInterruptSavePosition, ACT_RUN, AIN_HULL_TOLERANCE);

					CSound *pBestSound = GetBestSound();
					if ( pBestSound )
						goal.maxInitialSimplificationDist = pBestSound->Volume() * 0.5;

					if ( GetNavigator()->SetGoal( goal ) )
					{
						m_flMoveWaitFinished = gpGlobals->curtime + pTask->flTaskData;
					}
				}
				break;
			}
		}
		break;

	case TASK_FACE_HINTNODE:
	case TASK_FACE_LASTPOSITION:
	case TASK_FACE_SAVEPOSITION:
	case TASK_FACE_AWAY_FROM_SAVEPOSITION:
	case TASK_FACE_TARGET:
	case TASK_FACE_IDEAL:
	case TASK_FACE_SCRIPT:
	case TASK_FACE_PATH:
		{
			// If the yaw is locked, this function will not act correctly
			Assert( GetMotor()->IsYawLocked() == false );

			GetMotor()->UpdateYaw();
   
   			if ( FacingIdeal() )
   			{
   				TaskComplete();
   			}
   			break;
		}

	case TASK_FACE_REASONABLE:
		{
			// If the yaw is locked, this function will not act correctly
			Assert( GetMotor()->IsYawLocked() == false );

			GetMotor()->UpdateYaw();

			if ( FacingIdeal() )
			{
				TaskComplete();
			}
			break;
		}
	case TASK_WAIT_PVS:
		{
			if ( ShouldAlwaysThink() || 
				 UTIL_FindClientInPVS(edict()) || 
				 ( GetState() == NPC_STATE_COMBAT && GetEnemy() && gpGlobals->curtime - GetEnemies()->LastTimeSeen( GetEnemy() ) < 15 ) )
			{
				TaskComplete();
			}
			break;
		}
	case TASK_WAIT_INDEFINITE:
		{
			// don't do anything.
			break;
		}
	case TASK_WAIT:
	case TASK_WAIT_RANDOM:
		{
			if ( IsWaitFinished() )
			{
				TaskComplete();
			}
			break;
		}
	case TASK_WAIT_FACE_ENEMY:
	case TASK_WAIT_FACE_ENEMY_RANDOM:
		{
			Vector vecEnemyLKP = GetEnemyLKP();
			if (!FInAimCone( vecEnemyLKP ))
			{
				GetMotor()->SetIdealYawToTargetAndUpdate( vecEnemyLKP , AI_KEEP_YAW_SPEED );
			}

			if ( IsWaitFinished() )
			{
				TaskComplete();
			}
			break;
		}
	case TASK_WAIT_UNTIL_NO_DANGER_SOUND:
		if( !HasCondition( COND_HEAR_DANGER ) )
		{
			TaskComplete();
		}
		break;

	case TASK_MOVE_TO_TARGET_RANGE:
	case TASK_MOVE_TO_GOAL_RANGE:
		{
			// Identical tasks, except that target_range uses m_hTargetEnt, 
			// and Goal range uses the nav goal
			CBaseEntity *pTarget = NULL;
			if ( pTask->iTask == TASK_MOVE_TO_GOAL_RANGE )
			{
				pTarget = GetNavigator()->GetGoalTarget();
			}
			if ( !pTarget )
			{
				pTarget = m_hTargetEnt.Get();
			}

			float distance;

			if ( pTarget == NULL )
			{
				TaskFail(FAIL_NO_TARGET);
			}
			else if (GetNavigator()->GetGoalType() == GOALTYPE_NONE)
			{
				TaskComplete();
				GetNavigator()->ClearGoal();		// Clear residual state
			}
			else
			{
				bool bForceRun = false; 

				// Check Z first, and only check 2d if we're within that
				Vector vecGoalPos = GetNavigator()->GetGoalPos();
				distance = fabs(vecGoalPos.z - GetLocalOrigin().z);
				if ( distance < pTask->flTaskData )
				{
					distance = ( vecGoalPos - GetLocalOrigin() ).Length2D();
				}
				else
				{
					// If the target is significantly higher or lower than me, I must run. 
					bForceRun = true;
				}

				// If we're jumping, wait until we're finished to update our goal position.
				if ( GetNavigator()->GetNavType() != NAV_JUMP )
				{
					// Re-evaluate when you think your finished, or the target has moved too far
					if ( (distance < pTask->flTaskData) || (vecGoalPos - pTarget->GetAbsOrigin()).Length() > pTask->flTaskData * 0.5 )
					{
						distance = ( pTarget->GetAbsOrigin() - GetLocalOrigin() ).Length2D();
						if ( !GetNavigator()->UpdateGoalPos( pTarget->GetAbsOrigin() ) )
						{
							TaskFail( FAIL_NO_ROUTE );
							break;
						}
					}
				}
				
				// Set the appropriate activity based on an overlapping range
				// overlap the range to prevent oscillation
				// BUGBUG: this is checking linear distance (ie. through walls) and not path distance or even visibility
				if ( distance < pTask->flTaskData )
				{
					TaskComplete();
#ifndef HL2_DLL
	// HL2 uses TASK_STOP_MOVING
					GetNavigator()->StopMoving();		// Stop moving
#endif
				}
				else
				{
					// Pick the right movement activity.
					Activity followActivity; 
						
					if( bForceRun )
					{
						followActivity = ACT_RUN;
					}
					else
					{
						followActivity = ( distance < 190 && m_NPCState != NPC_STATE_COMBAT ) ? ACT_WALK : ACT_RUN;
					}

					// Don't confuse move and shoot by resetting the activity every think
					Activity curActivity = GetNavigator()->GetMovementActivity();
					switch( curActivity )
					{
					case ACT_WALK_AIM:	curActivity = ACT_WALK;	break;
					case ACT_RUN_AIM:	curActivity = ACT_RUN;	break;
					}

					if ( curActivity != followActivity )
					{
						GetNavigator()->SetMovementActivity(followActivity);
					}
					GetNavigator()->SetArrivalDirection( pTarget );
				}
			}
			break;
		}
	case TASK_GET_PATH_TO_ENEMY_LOS:
	case TASK_GET_FLANK_RADIUS_PATH_TO_ENEMY_LOS:
	case TASK_GET_FLANK_ARC_PATH_TO_ENEMY_LOS:
	case TASK_GET_PATH_TO_ENEMY_LKP_LOS:
		{
			if ( GetEnemy() == NULL )
			{
				TaskFail(FAIL_NO_ENEMY);
				return;
			}
			if ( GetTaskInterrupt() > 0 )
			{
				ClearTaskInterrupt();

				Vector vecEnemy = ( pTask->iTask == TASK_GET_PATH_TO_ENEMY_LOS ) ? GetEnemy()->GetAbsOrigin() : GetEnemyLKP();
				AI_NavGoal_t goal( m_vInterruptSavePosition, ACT_RUN, AIN_HULL_TOLERANCE );

				GetNavigator()->SetGoal( goal, AIN_CLEAR_TARGET );
				GetNavigator()->SetArrivalDirection( vecEnemy - goal.dest );
			}
			else
				TaskInterrupt();
		}
		break;

	case TASK_GET_PATH_AWAY_FROM_BEST_SOUND:
	{
		ChainRunTask( TASK_MOVE_AWAY_PATH, pTask->flTaskData );
		if ( GetNavigator()->IsGoalActive() )
		{
			Vector vecDest = GetNavigator()->GetGoalPos();
			float flDist = ( GetAbsOrigin() - vecDest ).Length();

			if( flDist < 10.0 * 12.0 )
			{
				TaskFail("Path away from best sound too short!\n");
			}
		}
		break;
	}	

	case TASK_MOVE_AWAY_PATH:
		{
			QAngle ang = GetLocalAngles();
			ang.y = GetMotor()->GetIdealYaw() + 180;
			Vector move;

			switch ( GetTaskInterrupt() )
			{
			case 0:
				{
					if( IsPlayerAlly() )
					{
						// Look for a move away hint node.
						CAI_Hint *pHint;
						CHintCriteria hintCriteria;

						hintCriteria.AddHintType( HINT_PLAYER_ALLY_MOVE_AWAY_DEST );
						hintCriteria.SetFlag( bits_HINT_NODE_NEAREST );
						hintCriteria.AddIncludePosition( GetAbsOrigin(), (20.0f * 12.0f) ); // 20 feet max
						hintCriteria.AddExcludePosition( GetAbsOrigin(), 28.0f ); // don't plant on an hint that you start on

						pHint = CAI_HintManager::FindHint( this, hintCriteria );

						if( pHint )
						{
							CBasePlayer *pPlayer = AI_GetSinglePlayer();
							Vector vecGoal = pHint->GetAbsOrigin();

							if( vecGoal.DistToSqr(GetAbsOrigin()) < vecGoal.DistToSqr(pPlayer->GetAbsOrigin()) )
							{
								if( GetNavigator()->SetGoal(vecGoal) )
								{
#ifdef MAPBASE
									// Pushaway destinations could be an entire floor above.
									// That would get frustrating. Only go to hints within a path distance of 300 units,
									// only slightly above our initial search conditions.
									if (GetNavigator()->BuildAndGetPathDistToGoal() < 300.0f)
									{
										pHint->NPCHandleStartNav(this, false);
										pHint->DisableForSeconds( 0.1f ); // Force others to find their own.
										TaskComplete();
										break;
									}
#else
									pHint->DisableForSeconds( 0.1f ); // Force others to find their own.
									TaskComplete();
									break;
#endif
								}
							}
						}
					}

#ifdef HL2_EPISODIC
					// See if we're moving away from a vehicle
					CSound *pBestSound = GetBestSound( SOUND_MOVE_AWAY );
					if ( pBestSound && pBestSound->m_hOwner && pBestSound->m_hOwner->GetServerVehicle() )
					{
						// Move away from the vehicle's center, regardless of our facing
						move = ( GetAbsOrigin() - pBestSound->m_hOwner->WorldSpaceCenter() );
						VectorNormalize( move );
					}
					else
					{
						// Use the first angles
						AngleVectors( ang, &move );
					}
#else
					AngleVectors( ang, &move );
#endif	//HL2_EPISODIC
					if ( GetNavigator()->SetVectorGoal( move, (float)pTask->flTaskData, MIN(36,pTask->flTaskData), true ) && IsValidMoveAwayDest( GetNavigator()->GetGoalPos() ))
					{
						TaskComplete();
					}
					else
					{
						ang.y = GetMotor()->GetIdealYaw() + 91;
						AngleVectors( ang, &move );

						if ( GetNavigator()->SetVectorGoal( move, (float)pTask->flTaskData, MIN(24,pTask->flTaskData), true ) && IsValidMoveAwayDest( GetNavigator()->GetGoalPos() ) )
						{
							TaskComplete();
						}
						else
						{
							TaskInterrupt();
						}
					}
				}
				break;

			case 1:
				{
					ang.y = GetMotor()->GetIdealYaw() + 271;
					AngleVectors( ang, &move );

					if ( GetNavigator()->SetVectorGoal( move, (float)pTask->flTaskData, MIN(24,pTask->flTaskData), true ) && IsValidMoveAwayDest( GetNavigator()->GetGoalPos() ) )
					{
						TaskComplete();
					}
					else
					{
						ang.y = GetMotor()->GetIdealYaw() + 180;
						while (ang.y < 0)
							ang.y += 360;
						while (ang.y >= 360)
							ang.y -= 360;
						if ( ang.y < 45 || ang.y >= 315 )
							ang.y = 0;
						else if ( ang.y < 135 )
							ang.y = 90;
						else if ( ang.y < 225 )
							ang.y = 180;
						else
							ang.y = 270;

						AngleVectors( ang, &move );

						if ( GetNavigator()->SetVectorGoal( move, (float)pTask->flTaskData, MIN(6,pTask->flTaskData), false ) && IsValidMoveAwayDest( GetNavigator()->GetGoalPos() ) )
						{
							TaskComplete();
						}
						else
						{
							TaskInterrupt();
						}
					}
				}
				break;

			case 2:
				{
					ClearTaskInterrupt();
					Vector coverPos;

					if ( GetTacticalServices()->FindCoverPos( GetLocalOrigin(), EyePosition(), 0, CoverRadius(), &coverPos ) && IsValidMoveAwayDest( GetNavigator()->GetGoalPos() ) ) 
					{
						GetNavigator()->SetGoal( AI_NavGoal_t( coverPos, ACT_RUN ) );
						m_flMoveWaitFinished = gpGlobals->curtime + 2;
					}
					else
					{
						// no coverwhatsoever.
						TaskFail(FAIL_NO_ROUTE);
					}
				}
				break;

			}
		}
		break;

	case TASK_WEAPON_RUN_PATH:
	case TASK_ITEM_RUN_PATH:
		{
			CBaseEntity *pTarget = m_hTargetEnt;
			if ( pTarget )
			{
				if ( pTarget->GetOwnerEntity() )
				{
					TaskFail(FAIL_WEAPON_OWNED);
				}
				else if (GetNavigator()->GetGoalType() == GOALTYPE_NONE)
				{
					TaskComplete();
				}
			}
			else
			{
				TaskFail(FAIL_ITEM_NO_FIND);
			}
		}
		break;

	case TASK_WAIT_FOR_MOVEMENT_STEP:
	case TASK_WAIT_FOR_MOVEMENT:
		{
			bool fTimeExpired = ( pTask->flTaskData != 0 && pTask->flTaskData < gpGlobals->curtime - GetTimeTaskStarted() );
			
			if (fTimeExpired || GetNavigator()->GetGoalType() == GOALTYPE_NONE)
			{
				TaskComplete();
				GetNavigator()->StopMoving();		// Stop moving
			}
			else if (!GetNavigator()->IsGoalActive())
			{
				SetIdealActivity( GetStoppedActivity() );
			}
			else
			{
				// Check validity of goal type
				ValidateNavGoal();
			}
			break;
		}

	case TASK_DIE:
		RunDieTask();
		break;

	case TASK_WAIT_FOR_SPEAK_FINISH:
		Assert( GetExpresser() );
		if ( GetExpresser() )
		{
			// Are we waiting for our speech to end? Or for the mutex to be free?
			if ( pTask->flTaskData )
			{
				// Waiting for our speech to end
				if ( GetExpresser()->CanSpeakAfterMyself() )
				{
					TaskComplete();
				}
			}
			else
			{
				// Waiting for the speech & the delay afterwards
				if ( !GetExpresser()->IsSpeaking() )
				{
					TaskComplete();
				}
			}
		}
		break;

	case TASK_SCRIPT_RUN_TO_TARGET:
	case TASK_SCRIPT_WALK_TO_TARGET:
	case TASK_SCRIPT_CUSTOM_MOVE_TO_TARGET:
		StartScriptMoveToTargetTask( pTask->iTask );
		break;

	case TASK_RANGE_ATTACK1:
	case TASK_RANGE_ATTACK2:
	case TASK_MELEE_ATTACK1:
	case TASK_MELEE_ATTACK2:
	case TASK_SPECIAL_ATTACK1:
	case TASK_SPECIAL_ATTACK2:
	case TASK_RELOAD:
		RunAttackTask( pTask->iTask );
		break;

	case TASK_SMALL_FLINCH:
	case TASK_BIG_FLINCH:
		{
			if ( IsActivityFinished() )
			{
				TaskComplete();
			}
		}
		break;

	case TASK_WAIT_FOR_SCRIPT:
		{
			//
			// Waiting to play a script. If the script is ready, start playing the sequence.
			//
			if ( m_hCine && m_hCine->IsTimeToStart() )
			{
				TaskComplete();
#ifdef MAPBASE
				m_hCine->OnBeginSequence(this);
#else
				m_hCine->OnBeginSequence();
#endif

				// If we have an entry, we have to play it first
				if ( m_hCine->m_iszEntry != NULL_STRING )
				{
					m_hCine->StartSequence( (CAI_BaseNPC *)this, m_hCine->m_iszEntry, true );
				}
				else
				{
					m_hCine->StartSequence( (CAI_BaseNPC *)this, m_hCine->m_iszPlay, true );
				}

				// StartSequence() can call CineCleanup().  If that happened, just exit schedule
				if ( !m_hCine )
				{
					ClearSchedule( "Waiting for script, but lost script!" );
				}

				m_flPlaybackRate = 1.0;
				//DevMsg( 2, "Script %s has begun for %s\n", STRING( m_hCine->m_iszPlay ), GetClassname() );
			}
			else if (!m_hCine)
			{
				CGMsg( 1, CON_GROUP_NPC_SCRIPTS, "Cine died!\n" );
				TaskComplete();
			}
			else if ( IsRunningDynamicInteraction() )
			{
				// If we've lost our partner, abort
				if ( !m_hInteractionPartner )
				{
					CineCleanup();
				}
			}
			break;
		}
	case TASK_PLAY_SCRIPT:
		{
			//
			// Playing a scripted sequence.
			//
			AutoMovement( );

			if ( IsSequenceFinished() )
			{
				// Check to see if we are done with the action sequence.
				if ( m_hCine->FinishedActionSequence( this ) )
				{
					// dvs: This is done in FixScriptNPCSchedule -- doing it here is too early because we still
					//      need to play our post-action idle sequence, which might also require FL_FLY.
					//
					// drop to ground if this guy is only marked "fly" because of the auto movement
					/*if ( !(m_hCine->m_savedFlags & FL_FLY) )
					{
						if ( ( GetFlags() & FL_FLY ) && !m_hCine->m_bIgnoreGravity )
						{
							RemoveFlag( FL_FLY );
						}
					}*/

					if (m_hCine)
					{
						m_hCine->SequenceDone( this );
					}

					TaskComplete();
				}
				else if ( m_hCine && m_hCine->m_bForceSynch )
				{
					m_hCine->SynchronizeSequence( this );
				}
			}
			break;
		}

	case TASK_PLAY_SCRIPT_POST_IDLE:
		{
			if ( !m_hCine )
			{
				CGMsg( 1, CON_GROUP_NPC_SCRIPTS, "Scripted sequence destroyed while in use\n" );
				TaskFail( FAIL_SCHEDULE_NOT_FOUND );
				break;
			}

			//
			// Playing a scripted post idle sequence. Quit early if another sequence has grabbed the NPC.
			//
			if ( IsSequenceFinished() || ( m_hCine->m_hNextCine != NULL ) )
			{
				m_hCine->PostIdleDone( this );
			}
			break;
		}

	case TASK_ENABLE_SCRIPT:
		{
			if ( !m_hCine )
			{
				CGMsg( 1, CON_GROUP_NPC_SCRIPTS, "Scripted sequence destroyed while in use\n" );
				TaskFail( FAIL_SCHEDULE_NOT_FOUND );
				break;
			}

			if (!m_hCine->IsWaitingForBegin())
			{
				m_hCine->DelayStart( false );
				TaskComplete();
			}
			break;
		}


	case TASK_PLAY_SCENE:
		{
			if (!IsInLockedScene())
			{
				ClearSchedule( "Playing a scene, but not in a scene!" );
			}
			if (GetNavigator()->GetGoalType() != GOALTYPE_NONE)
			{
				TaskComplete();
			}
			break;
		}
		
	case TASK_RUN_PATH_FOR_UNITS:
	case TASK_WALK_PATH_FOR_UNITS:
	{
		float distance;

		distance = (m_vecLastPosition - GetLocalOrigin()).Length2D();

		// Walk path until far enough away
		if ( distance > pTask->flTaskData || 
			 GetNavigator()->GetGoalType() == GOALTYPE_NONE )
		{
			TaskComplete();
		}
		break;
	}
		
	case TASK_RUN_PATH_FLEE:
		{
			Vector vecDiff;
			vecDiff = GetLocalOrigin() - GetNavigator()->GetGoalPos();

			if( vecDiff.Length() <= pTask->flTaskData )
			{
				TaskComplete();
			}
			break;
		}

	case TASK_WALK_PATH_WITHIN_DIST:
	case TASK_RUN_PATH_WITHIN_DIST:
		{
			Vector vecDiff;

			vecDiff = GetLocalOrigin() - GetNavigator()->GetGoalPos();

			if( vecDiff.Length() <= pTask->flTaskData )
			{
				TaskComplete();
			}
			break;
		}

	case TASK_WALK_PATH_TIMED:
	case TASK_RUN_PATH_TIMED:
		{
			if ( IsWaitFinished() || 
				 GetNavigator()->GetGoalType() == GOALTYPE_NONE )
			{
				TaskComplete();
			}
		}
		break;

	case TASK_WEAPON_PICKUP:
		{
			if ( IsActivityFinished() )
			{
				CBaseCombatWeapon	 *pWeapon = dynamic_cast<CBaseCombatWeapon *>(	(CBaseEntity *)m_hTargetEnt);
				CBaseCombatCharacter *pOwner  = pWeapon->GetOwner();
				if ( !pOwner )
				{
					TaskComplete();
				}
				else
				{
					TaskFail(FAIL_WEAPON_OWNED);
				}
			}
			break;
		}
		break;

	case TASK_ITEM_PICKUP:
		{
			if ( IsActivityFinished() )
			{
				TaskComplete();
			}
			break;
		}
		break;

	case TASK_FALL_TO_GROUND:
		if ( GetFlags() & FL_ONGROUND )
		{
			TaskComplete();
		}
		else if( GetFlags() & FL_FLY )
		{
			// We're never going to fall if we're FL_FLY.
			RemoveFlag( FL_FLY );
		}
		else
		{
			if( IsWaitFinished() )
			{
				// After 4 seconds of trying to fall to ground, Assume that we're in a bad case where the NPC
				// isn't actually falling, and make an attempt to slam the ground entity to whatever's under the NPC.
				Vector maxs = WorldAlignMaxs() - Vector( .1, .1, .2 );
				Vector mins = WorldAlignMins() + Vector( .1, .1, 0 );
				Vector vecStart	= GetAbsOrigin() + Vector( 0, 0, .1 );
				Vector vecDown	= GetAbsOrigin();
				vecDown.z -= 0.2;

				trace_t trace;
				m_pMoveProbe->TraceHull( vecStart, vecDown, mins, maxs, MASK_NPCSOLID, &trace );

				if( trace.m_pEnt )
				{
					// Found something!
					SetGroundEntity( trace.m_pEnt );
					TaskComplete();
				}
				else
				{
					// Try again in a few seconds.
					SetWait(4);
				}
			}
		}
		break;

	case TASK_WANDER:
		break;

	case TASK_FREEZE:
		break;

	default:
		{
			CGMsg( 1, CON_GROUP_NPC_AI, "No RunTask entry for %s\n", TaskName( pTask->iTask ) );
			TaskComplete();
		}
		break;
	}
}

void CAI_BaseNPC::RunTaskOverlay()
{
	if ( IsCurTaskContinuousMove() )
	{
		m_MoveAndShootOverlay.RunShootWhileMove();
	}
}

void CAI_BaseNPC::EndTaskOverlay()
{
	m_MoveAndShootOverlay.EndShootWhileMove();
}

//=========================================================
// SetTurnActivity - measures the difference between the way
// the NPC is facing and determines whether or not to
// select one of the 180 turn animations.
//=========================================================
void CAI_BaseNPC::SetTurnActivity ( void )
{
	if ( IsCrouching() )
	{
		SetIdealActivity( ACT_IDLE ); // failure case
		return;
	}

	float flYD;
	flYD = GetMotor()->DeltaIdealYaw();

	// FIXME: unknown case, update yaw should catch these
	/*
	if (GetMotor()->AddTurnGesture( flYD ))
	{
		SetIdealActivity( ACT_IDLE );
		Remember( bits_MEMORY_TURNING );
		return;
	}
	*/

	if( flYD <= -80 && flYD >= -100 && SelectWeightedSequence( ACT_90_RIGHT ) != ACTIVITY_NOT_AVAILABLE )
	{
		// 90 degree right.
		Remember( bits_MEMORY_TURNING );
		SetIdealActivity( ACT_90_RIGHT );
		return;
	}
	if( flYD >= 80 && flYD <= 100 && SelectWeightedSequence( ACT_90_LEFT ) != ACTIVITY_NOT_AVAILABLE )
	{
		// 90 degree left.
		Remember( bits_MEMORY_TURNING );
		SetIdealActivity( ACT_90_LEFT );
		return;
	}
	if( fabs( flYD ) >= 160 && SelectWeightedSequence ( ACT_180_LEFT ) != ACTIVITY_NOT_AVAILABLE )
	{
		Remember( bits_MEMORY_TURNING );
		SetIdealActivity( ACT_180_LEFT );
		return;
	}

	if ( flYD <= -45 && SelectWeightedSequence ( ACT_TURN_RIGHT ) != ACTIVITY_NOT_AVAILABLE )
	{// big right turn
		SetIdealActivity( ACT_TURN_RIGHT );
		return;
	}
	if ( flYD >= 45 && SelectWeightedSequence ( ACT_TURN_LEFT ) != ACTIVITY_NOT_AVAILABLE )
	{// big left turn
		SetIdealActivity( ACT_TURN_LEFT );
		return;
	}

	SetIdealActivity( ACT_IDLE ); // failure case

}


//-----------------------------------------------------------------------------
// Purpose: For a specific delta, add a turn gesture and set the yaw speed
// Input  : yaw delta
//-----------------------------------------------------------------------------


bool CAI_BaseNPC::UpdateTurnGesture( void )
{
	float flYD = GetMotor()->DeltaIdealYaw();
	return GetMotor()->AddTurnGesture( flYD );
}


//-----------------------------------------------------------------------------
// Purpose: For non-looping animations that may be replayed sequentially (like attacks)
//			Set the activity to ACT_RESET if this is a replay, otherwise just set ideal activity
// Input  : newIdealActivity - desired ideal activity
//-----------------------------------------------------------------------------
void CAI_BaseNPC::ResetIdealActivity( Activity newIdealActivity )
{
	if ( m_Activity == newIdealActivity )
	{
		m_Activity = ACT_RESET;
	}

	SetIdealActivity( newIdealActivity );
}

			
void CAI_BaseNPC::TranslateNavGoal( CBaseEntity *pEnemy, Vector &chasePosition )
{
	if ( GetNavType() == NAV_FLY )
	{
		// UNDONE: Cache these per enemy instead?
		Vector offset = pEnemy->EyePosition() - pEnemy->GetAbsOrigin();
		chasePosition += offset;
	}
}


//-----------------------------------------------------------------------------
// Purpose: Returns the custom movement activity for the script that this NPC
//			is running.
// Output : Returns the activity, or ACT_INVALID is the sequence is unknown.
//-----------------------------------------------------------------------------
Activity CAI_BaseNPC::GetScriptCustomMoveActivity( void )
{
	Activity eActivity = ACT_WALK;

	if ( ( m_hCine != NULL ) && ( m_hCine->m_iszCustomMove != NULL_STRING ) )
	{
		// We have a valid script. Look up the custom movement activity.
		eActivity = ( Activity )LookupActivity( STRING( m_hCine->m_iszCustomMove ) );
		if ( eActivity == ACT_INVALID )
		{
			// Not an activity, at least make sure it's a valid sequence.
			if ( LookupSequence( STRING( m_hCine->m_iszCustomMove ) ) != ACT_INVALID )
			{
				eActivity = ACT_SCRIPT_CUSTOM_MOVE;
			}
			else
			{
				eActivity = ACT_WALK;
			}
		}
	}
	else if ( m_iszSceneCustomMoveSeq != NULL_STRING )
	{
		eActivity = ACT_SCRIPT_CUSTOM_MOVE;
	}

	return eActivity;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Output : int
//-----------------------------------------------------------------------------
int CAI_BaseNPC::GetScriptCustomMoveSequence( void )
{
	int iSequence = ACTIVITY_NOT_AVAILABLE;

	// If we have a scripted sequence entity, use it's custom move
	if ( m_hCine != NULL )
	{
		iSequence = LookupSequence( STRING( m_hCine->m_iszCustomMove ) );
		if ( iSequence == ACTIVITY_NOT_AVAILABLE )
		{
			CGMsg( 1, CON_GROUP_NPC_SCRIPTS, "SCRIPT_CUSTOM_MOVE: %s has no sequence:%s\n", GetClassname(), STRING(m_hCine->m_iszCustomMove) );
		}
	}
	else if ( m_iszSceneCustomMoveSeq != NULL_STRING )
	{
		// Otherwise, use the .vcd custom move
		iSequence = LookupSequence( STRING( m_iszSceneCustomMoveSeq ) );
		if ( iSequence == ACTIVITY_NOT_AVAILABLE )
		{
			Warning( "SCRIPT_CUSTOM_MOVE: %s failed scripted custom move. Has no sequence called: %s\n", GetClassname(), STRING(m_iszSceneCustomMoveSeq) );
		}
	}

	// Failed? Use walk.
	if ( iSequence == ACTIVITY_NOT_AVAILABLE )
	{
		iSequence = SelectWeightedSequence( ACT_WALK );
	}

	return iSequence;
}

//=========================================================
// GetTask - returns a pointer to the current 
// scheduled task. NULL if there's a problem.
//=========================================================
const Task_t *CAI_BaseNPC::GetTask( void ) 
{
	int iScheduleIndex = GetScheduleCurTaskIndex();
	if ( !GetCurSchedule() ||  iScheduleIndex < 0 || iScheduleIndex >= GetCurSchedule()->NumTasks() )
		// iScheduleIndex is not within valid range for the NPC's current schedule.
		return NULL;

	return &GetCurSchedule()->GetTaskList()[ iScheduleIndex ];
}


//-----------------------------------------------------------------------------
bool CAI_BaseNPC::IsInterruptable()
{
	if ( GetState() == NPC_STATE_SCRIPT )
	{
		if ( m_hCine )
		{
			if (!m_hCine->CanInterrupt() )
				return false;

			// are the in an script FL_FLY state?
			if ((GetFlags() & FL_FLY ) && !(m_hCine->m_savedFlags & FL_FLY))
			{
				return false;
			}
		}
	}
	
	return IsAlive();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CAI_BaseNPC::SelectInteractionSchedule( void )
{
	SetTarget( m_hForcedInteractionPartner );

	// If we have an interaction, we're the initiator. Move to our interaction point.
	if ( m_iInteractionPlaying != NPCINT_NONE )
		return SCHED_INTERACTION_MOVE_TO_PARTNER;

	// Otherwise, turn towards our partner and wait for him to reach us.
	//m_iInteractionState = NPCINT_MOVING_TO_MARK;
	return SCHED_INTERACTION_WAIT_FOR_PARTNER;
}

//-----------------------------------------------------------------------------
// Idle schedule selection
//-----------------------------------------------------------------------------
int CAI_BaseNPC::SelectIdleSchedule()
{
	if ( m_hForcedInteractionPartner )
		return SelectInteractionSchedule();

	int nSched = SelectFlinchSchedule();
	if ( nSched != SCHED_NONE )
		return nSched;

	if ( HasCondition ( COND_HEAR_DANGER ) ||
		 HasCondition ( COND_HEAR_COMBAT ) ||
		 HasCondition ( COND_HEAR_WORLD  ) ||
		 HasCondition ( COND_HEAR_BULLET_IMPACT ) ||
		 HasCondition ( COND_HEAR_PLAYER ) )
	{
		return SCHED_ALERT_FACE_BESTSOUND;
	}
	
	// no valid route!
	if (GetNavigator()->GetGoalType() == GOALTYPE_NONE)
		return SCHED_IDLE_STAND;

	// valid route. Get moving
	return SCHED_IDLE_WALK;
}


//-----------------------------------------------------------------------------
// Alert schedule selection
//-----------------------------------------------------------------------------
int CAI_BaseNPC::SelectAlertSchedule()
{
	if ( m_hForcedInteractionPartner )
		return SelectInteractionSchedule();

	int nSched = SelectFlinchSchedule();
	if ( nSched != SCHED_NONE )
		return nSched;

	// Scan around for new enemies
	if ( HasCondition( COND_ENEMY_DEAD ) && SelectWeightedSequence( ACT_VICTORY_DANCE ) != ACTIVITY_NOT_AVAILABLE )
		return SCHED_ALERT_SCAN;

	if( IsPlayerAlly() && HasCondition(COND_HEAR_COMBAT) )
	{
		return SCHED_ALERT_REACT_TO_COMBAT_SOUND;
	}

	if ( HasCondition ( COND_HEAR_DANGER ) ||
			  HasCondition ( COND_HEAR_PLAYER ) ||
			  HasCondition ( COND_HEAR_WORLD  ) ||
			  HasCondition ( COND_HEAR_BULLET_IMPACT ) ||
			  HasCondition ( COND_HEAR_COMBAT ) )
	{
		return SCHED_ALERT_FACE_BESTSOUND;
	}

	if ( gpGlobals->curtime - GetEnemies()->LastTimeSeen( AI_UNKNOWN_ENEMY ) < TIME_CARE_ABOUT_DAMAGE )
		return SCHED_ALERT_FACE;

	return SCHED_ALERT_STAND;
}


//-----------------------------------------------------------------------------
// Combat schedule selection
//-----------------------------------------------------------------------------
int CAI_BaseNPC::SelectCombatSchedule()
{
	if ( m_hForcedInteractionPartner )
		return SelectInteractionSchedule();

	int nSched = SelectFlinchSchedule();
	if ( nSched != SCHED_NONE )
		return nSched;

	if ( HasCondition(COND_NEW_ENEMY) && gpGlobals->curtime - GetEnemies()->FirstTimeSeen(GetEnemy()) < 2.0 )
	{
		return SCHED_WAKE_ANGRY;
	}
	
	if ( HasCondition( COND_ENEMY_DEAD ) )
	{
		// clear the current (dead) enemy and try to find another.
		SetEnemy( NULL );
		 
		if ( ChooseEnemy() )
		{
			ClearCondition( COND_ENEMY_DEAD );
			return SelectSchedule();
		}

		SetState( NPC_STATE_ALERT );
		return SelectSchedule();
	}
	
	// If I'm scared of this enemy run away
	if ( IRelationType( GetEnemy() ) == D_FR )
	{
		if (HasCondition( COND_SEE_ENEMY )	|| 
			HasCondition( COND_LIGHT_DAMAGE )|| 
			HasCondition( COND_HEAVY_DAMAGE ))
		{
			FearSound();
			//ClearCommandGoal();
			return SCHED_RUN_FROM_ENEMY;
		}

		// If I've seen the enemy recently, cower. Ignore the time for unforgettable enemies.
		AI_EnemyInfo_t *pMemory = GetEnemies()->Find( GetEnemy() );
		if ( (pMemory && pMemory->bUnforgettable) || (GetEnemyLastTimeSeen() > (gpGlobals->curtime - 5.0)) )
		{
			// If we're facing him, just look ready. Otherwise, face him.
			if ( FInAimCone( GetEnemy()->EyePosition() ) )
				return SCHED_COMBAT_STAND;

			return SCHED_FEAR_FACE;
		}
	}

	// Check if need to reload
	if ( HasCondition( COND_LOW_PRIMARY_AMMO ) || HasCondition( COND_NO_PRIMARY_AMMO ) )
	{
		return SCHED_HIDE_AND_RELOAD;
	}

	// Can we see the enemy?
	if ( !HasCondition(COND_SEE_ENEMY) )
	{
		// enemy is unseen, but not occluded!
		// turn to face enemy
		if ( !HasCondition(COND_ENEMY_OCCLUDED) )
			return SCHED_COMBAT_FACE;

		// chase!
		if ( GetActiveWeapon() || (CapabilitiesGet() & (bits_CAP_INNATE_RANGE_ATTACK1|bits_CAP_INNATE_RANGE_ATTACK2)))
			return SCHED_ESTABLISH_LINE_OF_FIRE;
		else if ( (CapabilitiesGet() & (bits_CAP_INNATE_MELEE_ATTACK1|bits_CAP_INNATE_MELEE_ATTACK2)))
			return SCHED_CHASE_ENEMY;
		else
			return SCHED_TAKE_COVER_FROM_ENEMY;
	}
	
	if ( HasCondition(COND_TOO_CLOSE_TO_ATTACK) ) 
		return SCHED_BACK_AWAY_FROM_ENEMY;
	
	if ( HasCondition( COND_WEAPON_PLAYER_IN_SPREAD ) || 
			HasCondition( COND_WEAPON_BLOCKED_BY_FRIEND ) || 
			HasCondition( COND_WEAPON_SIGHT_OCCLUDED ) )
	{
		return SCHED_ESTABLISH_LINE_OF_FIRE;
	}

	if ( GetShotRegulator()->IsInRestInterval() )
	{
		if ( HasCondition(COND_CAN_RANGE_ATTACK1) )
			return SCHED_COMBAT_FACE;
	}

	// we can see the enemy
	if ( HasCondition(COND_CAN_RANGE_ATTACK1) )
	{
		if ( !UseAttackSquadSlots() || OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) )
			return SCHED_RANGE_ATTACK1;
		return SCHED_COMBAT_FACE;
	}

	if ( HasCondition(COND_CAN_RANGE_ATTACK2) )
		return SCHED_RANGE_ATTACK2;

	if ( HasCondition(COND_CAN_MELEE_ATTACK1) )
		return SCHED_MELEE_ATTACK1;

	if ( HasCondition(COND_CAN_MELEE_ATTACK2) )
		return SCHED_MELEE_ATTACK2;

	if ( HasCondition(COND_NOT_FACING_ATTACK) )
		return SCHED_COMBAT_FACE;

	if ( !HasCondition(COND_CAN_RANGE_ATTACK1) && !HasCondition(COND_CAN_MELEE_ATTACK1) )
	{
		// if we can see enemy but can't use either attack type, we must need to get closer to enemy
		if ( GetActiveWeapon() )
			return SCHED_MOVE_TO_WEAPON_RANGE;

		// If we have an innate attack and we're too far (or occluded) then get line of sight
		if ( HasCondition( COND_TOO_FAR_TO_ATTACK ) && ( CapabilitiesGet() & (bits_CAP_INNATE_RANGE_ATTACK1|bits_CAP_INNATE_RANGE_ATTACK2)) )
			return SCHED_MOVE_TO_WEAPON_RANGE;

		// if we can see enemy but can't use either attack type, we must need to get closer to enemy
		if ( CapabilitiesGet() & (bits_CAP_INNATE_MELEE_ATTACK1|bits_CAP_INNATE_MELEE_ATTACK2) )
			return SCHED_CHASE_ENEMY;
		else
			return SCHED_TAKE_COVER_FROM_ENEMY;
	}

	DevWarning( 2, "No suitable combat schedule!\n" );
	return SCHED_FAIL;
}


//-----------------------------------------------------------------------------
// Dead schedule selection
//-----------------------------------------------------------------------------
int CAI_BaseNPC::SelectDeadSchedule()
{
	if ( BecomeRagdollOnClient( vec3_origin ) )
	{
		CleanupOnDeath();
		return SCHED_DIE_RAGDOLL;
	}

	// Adrian - Alread dead (by animation event maybe?)
	// Is it safe to set it to SCHED_NONE?
	if ( m_lifeState == LIFE_DEAD )
		 return SCHED_NONE;

	CleanupOnDeath();
	return SCHED_DIE;
}


//-----------------------------------------------------------------------------
// Script schedule selection
//-----------------------------------------------------------------------------
int CAI_BaseNPC::SelectScriptSchedule()
{
	Assert( m_hCine != NULL );
	if ( m_hCine )
		return SCHED_AISCRIPT;

	DevWarning( 2, "Script failed for %s\n", GetClassname() );
	CineCleanup();
	return SCHED_IDLE_STAND;
}

//-----------------------------------------------------------------------------
// Purpose: Select a gesture to play in response to damage we've taken
// Output : int
//-----------------------------------------------------------------------------
void CAI_BaseNPC::PlayFlinchGesture()
{
	if ( !CanFlinch() )
		return;

	Activity iFlinchActivity = ACT_INVALID;

	float flNextFlinch = random->RandomFloat( 0.5f, 1.0f );

	// If I haven't flinched for a while, play the big flinch gesture
	if ( !HasMemory(bits_MEMORY_FLINCHED) )
	{
		iFlinchActivity = GetFlinchActivity( true, true );

		if ( HaveSequenceForActivity( iFlinchActivity ) )
		{
			RestartGesture( iFlinchActivity );
		}

		Remember(bits_MEMORY_FLINCHED);

	}
	else
	{
		iFlinchActivity = GetFlinchActivity( false, true );
		if ( HaveSequenceForActivity( iFlinchActivity ) )
		{
			RestartGesture( iFlinchActivity );
		}
	}

	if ( iFlinchActivity != ACT_INVALID )
	{
		//Get the duration of the flinch and delay the next one by that (plus a bit more)
		int iSequence = GetLayerSequence( FindGestureLayer( iFlinchActivity ) );

		if ( iSequence != ACT_INVALID )
		{
			flNextFlinch += SequenceDuration( iSequence );
		}

		m_flNextFlinchTime = gpGlobals->curtime + flNextFlinch;
	}
}

//-----------------------------------------------------------------------------
// Purpose: See if we should flinch in response to damage we've taken
// Output : int
//-----------------------------------------------------------------------------
int CAI_BaseNPC::SelectFlinchSchedule()
{
	if ( !HasCondition(COND_HEAVY_DAMAGE) )
		return SCHED_NONE;

	// If we've flinched recently, don't do it again. A gesture flinch will be played instead.
 	if ( HasMemory(bits_MEMORY_FLINCHED) )
		return SCHED_NONE;

	if ( !CanFlinch() )
		return SCHED_NONE;

	// Robin: This was in the original HL1 flinch code. Do we still want it?
	//if ( fabs( GetMotor()->DeltaIdealYaw() ) < (1.0 - m_flFieldOfView) * 60 ) // roughly in the correct direction
	//	return SCHED_TAKE_COVER_FROM_ORIGIN;

	// Heavy damage. Break out of my current schedule and flinch.
	Activity iFlinchActivity = GetFlinchActivity( true, false );
	if ( HaveSequenceForActivity( iFlinchActivity ) )
		return SCHED_BIG_FLINCH;

	/*
	// Not used anymore, because gesture flinches are played instead for heavy damage
	// taken shortly after we've already flinched full.
	//
	iFlinchActivity = GetFlinchActivity( false, false );
	if ( HaveSequenceForActivity( iFlinchActivity ) )
		return SCHED_SMALL_FLINCH;
	*/

	return SCHED_NONE;
}
		
//-----------------------------------------------------------------------------
// Purpose: Decides which type of schedule best suits the NPC's current 
// state and conditions. Then calls NPC's member function to get a pointer 
// to a schedule of the proper type.
//-----------------------------------------------------------------------------
int CAI_BaseNPC::SelectSchedule( void )
{
	if ( HasCondition( COND_FLOATING_OFF_GROUND ) )
	{
		SetGravity( 1.0 );
		SetGroundEntity( NULL );
		return SCHED_FALL_TO_GROUND;
	}

	switch( m_NPCState )
	{
	case NPC_STATE_NONE:
		DevWarning( 2, "NPC_STATE IS NONE!\n" );
		break;

	case NPC_STATE_PRONE:
		return SCHED_IDLE_STAND;

	case NPC_STATE_IDLE:
		AssertMsgOnce( GetEnemy() == NULL, "NPC has enemy but is not in combat state?" );
		return SelectIdleSchedule();

	case NPC_STATE_ALERT:
		AssertMsgOnce( GetEnemy() == NULL, "NPC has enemy but is not in combat state?" );
		return SelectAlertSchedule();

	case NPC_STATE_COMBAT:
		return SelectCombatSchedule();

	case NPC_STATE_DEAD:
		return SelectDeadSchedule();

	case NPC_STATE_SCRIPT:
		return SelectScriptSchedule();

	default:
		DevWarning( 2, "Invalid State for SelectSchedule!\n" );
		break;
	}

	return SCHED_FAIL;
}


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

int CAI_BaseNPC::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
{
	return ( m_failSchedule != SCHED_NONE ) ? m_failSchedule : SCHED_FAIL;
}
