//========= Copyright <EFBFBD> 1996-2005, 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 "env_debughistory.h"
# include "ai_behavior.h"
# include "global_event_log.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 ) ;
OnSetSchedule ( ) ;
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 ) ;
}
if ( m_pEvent ! = NULL )
{
if ( m_pScheduleEvent ! = NULL )
{
GlobalEventLog . RemoveEvent ( m_pScheduleEvent ) ;
}
m_pScheduleEvent = GlobalEventLog . CreateEvent ( " Schedule " , false , m_pEvent ) ;
GlobalEventLog . AddKeyValue ( m_pScheduleEvent , false , " Schedule " , pNewSchedule - > GetName ( ) ) ;
}
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 ( ) )
{
DevMsg ( " **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 )
{
DevMsg ( " ERROR: Missing or invalid schedule! \n " ) ;
SetActivity ( ACT_IDLE ) ;
return ;
}
AI_PROFILE_SCOPE_BEGIN_ ( GetCurSchedule ( ) ? CAI_BaseNPC : : GetSchedulingSymbols ( ) - > ScheduleIdToSymbol ( GetCurSchedule ( ) - > GetId ( ) ) : " NULL SCHEDULE " ) ;
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 ) ;
}
if ( m_pScheduleEvent ! = NULL )
{
CGlobalEvent * pEvent = GlobalEventLog . CreateTempEvent ( " New Task " , m_pScheduleEvent ) ;
GlobalEventLog . AddKeyValue ( pEvent , false , " Task " , 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_ ( GetCurSchedule ( ) ? CAI_BaseNPC : : GetSchedulingSymbols ( ) - > ScheduleIdToSymbol ( GetCurSchedule ( ) - > GetId ( ) ) : " NULL SCHEDULE " ) ;
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 ;
}
for ( i = 0 ; i < m_Behaviors . Count ( ) ; i + + )
{
m_Behaviors [ i ] - > MaintainChannelSchedules ( ) ;
}
// 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 ( ) ) ) ;
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
{
DevMsg ( 2 , " 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 ;
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_OFF_OF_NPC :
{
Assert ( ( GetGroundEntity ( ) & & ( GetGroundEntity ( ) - > IsPlayer ( ) | | ( GetGroundEntity ( ) - > IsNPC ( ) & & IRelationType ( GetGroundEntity ( ) ) = = D_LI ) ) ) ) ;
GetNavigator ( ) - > SetAllowBigStep ( GetGroundEntity ( ) ) ;
ChainStartTask ( TASK_MOVE_AWAY_PATH , 48 ) ;
}
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 ( IsMovementFrozen ( ) )
{
TaskFail ( FAIL_FROZEN ) ;
break ;
}
if ( ! GetNavigator ( ) - > IsGoalActive ( ) )
{
TaskComplete ( ) ;
return ;
}
if ( IsActivityFinished ( ) )
{
TaskComplete ( ) ;
return ;
}
ValidateNavGoal ( ) ;
break ;
}
case TASK_WAIT_FOR_MOVEMENT :
{
if ( IsMovementFrozen ( ) )
{
TaskFail ( FAIL_FROZEN ) ;
break ;
}
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.
DevMsg ( 2 , " 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 )
{
DevMsg ( " Scripted sequence destroyed while in use \n " ) ;
TaskFail ( FAIL_SCHEDULE_NOT_FOUND ) ;
break ;
}
break ;
}
case TASK_PUSH_SCRIPT_ARRIVAL_ACTIVITY :
{
if ( ! m_hCine )
{
DevMsg ( " Scripted sequence destroyed while in use \n " ) ;
TaskFail ( FAIL_SCHEDULE_NOT_FOUND ) ;
break ;
}
string_t iszArrivalText ;
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 )
{
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 :
{
DevMsg ( " 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 )
{
DevMsg ( " 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 ( m_flFaceEnemyTolerance ) )
{
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 ;
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_GET_PATH_OFF_OF_NPC :
{
if ( AI_IsSinglePlayer ( ) )
{
GetNavigator ( ) - > SetAllowBigStep ( UTIL_GetLocalPlayer ( ) ) ;
}
ChainRunTask ( TASK_MOVE_AWAY_PATH , 48 ) ;
}
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 ) )
{
pHint - > DisableForSeconds ( 0.1f ) ; // Force others to find their own.
TaskComplete ( ) ;
break ;
}
}
}
}
# 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 :
{
if ( IsMovementFrozen ( ) )
{
TaskFail ( FAIL_FROZEN ) ;
break ;
}
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 ( ) ;
m_hCine - > OnBeginSequence ( ) ;
// 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 )
{
DevMsg ( " 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 )
{
DevMsg ( " 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 )
{
DevMsg ( " 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 , GetAITraceMask ( ) , & 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 :
if ( m_flFrozen < 1.0f )
{
Unfreeze ( ) ;
}
break ;
default :
{
DevMsg ( " 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 )
{
DevMsg ( " 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 ] ;
}
void CAI_BaseNPC : : TranslateAddOnAttachment ( char * pchAttachmentName , int iCount )
{
# ifdef HL2_DLL
if ( Classify ( ) = = CLASS_ZOMBIE | | ClassMatches ( " npc_combine* " ) )
{
if ( Q_strcmp ( pchAttachmentName , " addon_rear " ) = = 0 | |
Q_strcmp ( pchAttachmentName , " addon_front " ) = = 0 | |
Q_strcmp ( pchAttachmentName , " addon_rear_or_front " ) = = 0 )
{
if ( iCount = = 0 )
{
Q_strcpy ( pchAttachmentName , " eyes " ) ;
}
else
{
Q_strcpy ( pchAttachmentName , " " ) ;
}
return ;
}
}
# endif
if ( Q_strcmp ( pchAttachmentName , " addon_baseshooter " ) = = 0 )
{
switch ( iCount )
{
case 0 :
Q_strcpy ( pchAttachmentName , " anim_attachment_lh " ) ;
break ;
case 1 :
Q_strcpy ( pchAttachmentName , " anim_attachment_rh " ) ;
break ;
default :
Q_strcpy ( pchAttachmentName , " " ) ;
}
return ;
}
Q_strcpy ( pchAttachmentName , " " ) ;
}
//-----------------------------------------------------------------------------
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 ;
}