Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1745 lines
48 KiB

//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "cbase.h"
#include "ai_agent.h"
#include "datacache/imdlcache.h"
#include "isaverestore.h"
#include "game.h"
#include "env_debughistory.h"
#include "checksum_crc.h"
#include "IEffects.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//-----------------------------------------------------------------------------
//
// Crude frame timings
//
extern CFastTimer g_AIRunTimer;
extern CFastTimer g_AIPostRunTimer;
extern CFastTimer g_AIConditionsTimer;
extern CFastTimer g_AIPrescheduleThinkTimer;
extern CFastTimer g_AIMaintainScheduleTimer;
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// ================================================================
// Init static data
// ================================================================
CAI_ClassScheduleIdSpace CAI_Agent::gm_ClassScheduleIdSpace( true );
CAI_GlobalScheduleNamespace CAI_Agent::gm_SchedulingSymbols;
// ================================================================
// Class Methods
// ================================================================
//---------------------------------------------------------
//---------------------------------------------------------
#define InterruptFromCondition( iCondition ) \
AI_RemapFromGlobal( ( AI_IdIsLocal( iCondition ) ? GetClassScheduleIdSpace()->ConditionLocalToGlobal( iCondition ) : iCondition ) )
void CAI_Agent::SetCondition( int iCondition )
{
int interrupt = InterruptFromCondition( iCondition );
if ( interrupt == -1 )
{
Assert(0);
return;
}
m_Conditions.Set( interrupt );
}
//---------------------------------------------------------
//---------------------------------------------------------
bool CAI_Agent::HasCondition( int iCondition )
{
int interrupt = InterruptFromCondition( iCondition );
if ( interrupt == -1 )
{
Assert(0);
return false;
}
bool bReturn = m_Conditions.IsBitSet(interrupt);
return (bReturn);
}
//---------------------------------------------------------
//---------------------------------------------------------
bool CAI_Agent::HasCondition( int iCondition, bool bUseIgnoreConditions )
{
if ( bUseIgnoreConditions )
return HasCondition( iCondition );
int interrupt = InterruptFromCondition( iCondition );
if ( interrupt == -1 )
{
Assert(0);
return false;
}
bool bReturn = m_ConditionsPreIgnore.IsBitSet(interrupt);
return (bReturn);
}
//---------------------------------------------------------
//---------------------------------------------------------
void CAI_Agent::ClearCondition( int iCondition )
{
int interrupt = InterruptFromCondition( iCondition );
if ( interrupt == -1 )
{
Assert(0);
return;
}
m_Conditions.Clear(interrupt);
}
//---------------------------------------------------------
//---------------------------------------------------------
void CAI_Agent::ClearConditions( int *pConditions, int nConditions )
{
for ( int i = 0; i < nConditions; ++i )
{
int iCondition = pConditions[i];
int interrupt = InterruptFromCondition( iCondition );
if ( interrupt == -1 )
{
Assert(0);
continue;
}
m_Conditions.Clear( interrupt );
}
}
//---------------------------------------------------------
//---------------------------------------------------------
void CAI_Agent::SetIgnoreConditions( int *pConditions, int nConditions )
{
for ( int i = 0; i < nConditions; ++i )
{
int iCondition = pConditions[i];
int interrupt = InterruptFromCondition( iCondition );
if ( interrupt == -1 )
{
Assert(0);
continue;
}
m_InverseIgnoreConditions.Clear( interrupt ); // clear means ignore
}
}
void CAI_Agent::ClearIgnoreConditions( int *pConditions, int nConditions )
{
for ( int i = 0; i < nConditions; ++i )
{
int iCondition = pConditions[i];
int interrupt = InterruptFromCondition( iCondition );
if ( interrupt == -1 )
{
Assert(0);
continue;
}
m_InverseIgnoreConditions.Set( interrupt ); // set means don't ignore
}
}
//---------------------------------------------------------
//---------------------------------------------------------
bool CAI_Agent::HasInterruptCondition( int iCondition )
{
if( !GetCurSchedule() )
{
return false;
}
int interrupt = InterruptFromCondition( iCondition );
if ( interrupt == -1 )
{
Assert(0);
return false;
}
return ( m_Conditions.IsBitSet( interrupt ) && GetCurSchedule()->HasInterrupt( interrupt ) );
}
//---------------------------------------------------------
//---------------------------------------------------------
bool CAI_Agent::ConditionInterruptsCurSchedule( int iCondition )
{
if( !GetCurSchedule() )
{
return false;
}
int interrupt = InterruptFromCondition( iCondition );
if ( interrupt == -1 )
{
Assert(0);
return false;
}
return ( GetCurSchedule()->HasInterrupt( interrupt ) );
}
//---------------------------------------------------------
//---------------------------------------------------------
bool CAI_Agent::ConditionInterruptsSchedule( int localScheduleID, int iCondition )
{
CAI_Schedule *pSchedule = GetSchedule( localScheduleID );
if ( !pSchedule )
return false;
int interrupt = InterruptFromCondition( iCondition );
if ( interrupt == -1 )
{
Assert(0);
return false;
}
return ( pSchedule->HasInterrupt( interrupt ) );
}
//-----------------------------------------------------------------------------
// Returns whether we currently have any interrupt conditions that would
// interrupt the given schedule.
//-----------------------------------------------------------------------------
bool CAI_Agent::HasConditionsToInterruptSchedule( int nLocalScheduleID )
{
CAI_Schedule *pSchedule = GetSchedule( nLocalScheduleID );
if ( !pSchedule )
return false;
CAI_ScheduleBits bitsMask;
pSchedule->GetInterruptMask( &bitsMask );
CAI_ScheduleBits bitsOut;
AccessConditionBits().And( bitsMask, &bitsOut );
return !bitsOut.IsAllClear();
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CAI_Agent::IsCustomInterruptConditionSet( int nCondition )
{
int interrupt = InterruptFromCondition( nCondition );
if ( interrupt == -1 )
{
Assert(0);
return false;
}
return m_CustomInterruptConditions.IsBitSet( interrupt );
}
//-----------------------------------------------------------------------------
// Purpose: Sets a flag in the custom interrupt flags, translating the condition
// to the proper global space, if necessary
//-----------------------------------------------------------------------------
void CAI_Agent::SetCustomInterruptCondition( int nCondition )
{
int interrupt = InterruptFromCondition( nCondition );
if ( interrupt == -1 )
{
Assert(0);
return;
}
m_CustomInterruptConditions.Set( interrupt );
}
//-----------------------------------------------------------------------------
// Purpose: Clears a flag in the custom interrupt flags, translating the condition
// to the proper global space, if necessary
//-----------------------------------------------------------------------------
void CAI_Agent::ClearCustomInterruptCondition( int nCondition )
{
int interrupt = InterruptFromCondition( nCondition );
if ( interrupt == -1 )
{
Assert(0);
return;
}
m_CustomInterruptConditions.Clear( interrupt );
}
//-----------------------------------------------------------------------------
// Purpose: Clears all the custom interrupt flags.
//-----------------------------------------------------------------------------
void CAI_Agent::ClearCustomInterruptConditions()
{
m_CustomInterruptConditions.ClearAll();
}
//-----------------------------------------------------------------------------
bool CAI_Agent::PreThink( void )
{
return true;
}
//-----------------------------------------------------------------------------
// NPC Think - calls out to core AI functions and handles this
// npc's specific animation events
//
void CAI_Agent::Think( void )
{
if ( PreThink() )
{
RunAI();
}
}
//-----------------------------------------------------------------------------
// Purpose: Virtual function that allows us to have any npc ignore a set of
// shared conditions.
//
//-----------------------------------------------------------------------------
void CAI_Agent::RemoveIgnoredConditions( void )
{
m_ConditionsPreIgnore = m_Conditions;
m_Conditions.And( m_InverseIgnoreConditions, &m_Conditions );
}
//-----------------------------------------------------------------------------
void CAI_Agent::GatherConditions( void )
{
m_bConditionsGathered = true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_Agent::PrescheduleThink( void )
{
}
//-----------------------------------------------------------------------------
// Main entry point for processing AI
//-----------------------------------------------------------------------------
void CAI_Agent::RunAI( void )
{
AI_PROFILE_SCOPE(CAI_Agent_RunAI);
g_AIRunTimer.Start();
m_bConditionsGathered = false;
AI_PROFILE_SCOPE_BEGIN(CAI_Agent_RunAI_GatherConditions);
GatherConditions();
RemoveIgnoredConditions();
AI_PROFILE_SCOPE_END();
if ( !m_bConditionsGathered )
m_bConditionsGathered = true; // derived class didn't call to base
g_AIPrescheduleThinkTimer.Start();
AI_PROFILE_SCOPE_BEGIN(CAI_RunAI_PrescheduleThink);
PrescheduleThink();
AI_PROFILE_SCOPE_END();
g_AIPrescheduleThinkTimer.End();
MaintainSchedule();
PostscheduleThink();
ClearTransientConditions();
g_AIRunTimer.End();
}
//-----------------------------------------------------------------------------
void CAI_Agent::ClearTransientConditions()
{
}
//=========================================================
// NPCInit - after a npc is spawned, it needs to
// be dropped into the world, checked for mobility problems,
// and put on the proper path, if any. This function does
// all of those things after the npc spawns. Any
// initialization that should take place for all npcs
// goes here.
//=========================================================
void CAI_Agent::Init( void )
{
// Clear conditions
m_Conditions.ClearAll();
// NOTE: Can't call NPC Init Think directly... logic changed about
// what time it is when worldspawn happens..
// We must put off the rest of our initialization
// until we're sure everything else has had a chance to spawn. Otherwise
// we may try to reference entities that haven't spawned yet.(sjb)
ForceGatherConditions();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_Agent::TaskComplete( bool fIgnoreSetFailedCondition )
{
// EndTaskOverlay();
// Handy thing to use for debugging
//if (IsCurSchedule(SCHED_PUT_HERE) &&
// GetTask()->iTask == TASK_PUT_HERE)
//{
// int put_breakpoint_here = 5;
//}
if ( fIgnoreSetFailedCondition || !HasCondition(COND_TASK_FAILED) )
{
SetTaskStatus( TASKSTATUS_COMPLETE );
}
}
void CAI_Agent::TaskMovementComplete( void )
{
switch( GetTaskStatus() )
{
case TASKSTATUS_NEW:
case TASKSTATUS_RUN_MOVE_AND_TASK:
SetTaskStatus( TASKSTATUS_RUN_TASK );
break;
case TASKSTATUS_RUN_MOVE:
TaskComplete();
break;
case TASKSTATUS_RUN_TASK:
// FIXME: find out how to safely restart movement
//Warning( "Movement completed twice!\n" );
//Assert( 0 );
break;
case TASKSTATUS_COMPLETE:
break;
}
}
int CAI_Agent::TaskIsRunning( void )
{
if ( GetTaskStatus() != TASKSTATUS_COMPLETE &&
GetTaskStatus() != TASKSTATUS_RUN_MOVE )
return 1;
return 0;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input :
// Output :
//-----------------------------------------------------------------------------
void CAI_Agent::TaskFail( AI_TaskFailureCode_t code )
{
// EndTaskOverlay();
// Handy tool for debugging
//if (IsCurSchedule(SCHED_PUT_NAME_HERE))
//{
// int put_breakpoint_here = 5;
//}
// If in developer mode save the fail text for debug output
if (g_pDeveloper->GetInt())
{
m_failText = TaskFailureToString( code );
m_interuptSchedule = NULL;
m_failedSchedule = GetCurSchedule();
if (GetDebugOverlayFlags() & OVERLAY_TASK_TEXT_BIT)
{
DevMsg(this, AIMF_IGNORE_SELECTED, " TaskFail -> %s\n", m_failText );
}
ADD_DEBUG_HISTORY( HISTORY_AI_DECISIONS, UTIL_VarArgs("%s(%d): TaskFail -> %s\n", GetDebugName(), entindex(), m_failText ) );
//AddTimedOverlay( fail_text, 5);
}
m_ScheduleState.taskFailureCode = code;
SetCondition(COND_TASK_FAILED);
}
//-----------------------------------------------------------------------------
// Purpose: Draw any debug text overlays
// Input :
// Output : Current text offset from the top
//-----------------------------------------------------------------------------
int CAI_Agent::DrawDebugTextOverlays( int text_offset )
{
if (GetDebugOverlayFlags() & OVERLAY_TEXT_BIT)
{
char tempstr[512];
// --------------
// Print Schedule
// --------------
if ( GetCurSchedule() )
{
const char *pName = NULL;
pName = GetCurSchedule()->GetName();
if ( !pName )
{
pName = "Unknown";
}
Q_snprintf(tempstr,sizeof(tempstr),"Schd: %s, ", pName );
EntityText(text_offset,tempstr,0);
text_offset++;
if (GetDebugOverlayFlags() & OVERLAY_NPC_TASK_BIT)
{
for (int i = 0 ; i < GetCurSchedule()->NumTasks(); i++)
{
Q_snprintf(tempstr,sizeof(tempstr),"%s%s%s%s",
((i==0) ? "Task:":" "),
((i==GetScheduleCurTaskIndex()) ? "->" :" "),
TaskName(GetCurSchedule()->GetTaskList()[i].iTask),
((i==GetScheduleCurTaskIndex()) ? "<-" :""));
EntityText(text_offset,tempstr,0);
text_offset++;
}
}
else
{
const Task_t *pTask = GetTask();
if ( pTask )
{
Q_snprintf(tempstr,sizeof(tempstr),"Task: %s (#%d), ", TaskName(pTask->iTask), GetScheduleCurTaskIndex() );
}
else
{
Q_strncpy(tempstr,"Task: None",sizeof(tempstr));
}
EntityText(text_offset,tempstr,0);
text_offset++;
}
}
//
// Print all the current conditions.
//
if (GetDebugOverlayFlags() & OVERLAY_NPC_CONDITIONS_BIT)
{
bool bHasConditions = false;
for (int i = 0; i < MAX_CONDITIONS; i++)
{
if (m_Conditions.IsBitSet(i))
{
Q_snprintf(tempstr, sizeof(tempstr), "Cond: %s\n", ConditionName(AI_RemapToGlobal(i)));
EntityText(text_offset, tempstr, 0);
text_offset++;
bHasConditions = true;
}
}
if (!bHasConditions)
{
Q_snprintf(tempstr,sizeof(tempstr),"(no conditions)");
EntityText(text_offset,tempstr,0);
text_offset++;
}
}
// --------------
// Print Interrupte
// --------------
if (m_interuptSchedule)
{
const char *pName = NULL;
pName = m_interuptSchedule->GetName();
if ( !pName )
{
pName = "Unknown";
}
Q_snprintf(tempstr,sizeof(tempstr),"Intr: %s (%s)\n", pName, m_interruptText );
EntityText(text_offset,tempstr,0);
text_offset++;
}
// --------------
// Print Failure
// --------------
if (m_failedSchedule)
{
const char *pName = NULL;
pName = m_failedSchedule->GetName();
if ( !pName )
{
pName = "Unknown";
}
Q_snprintf(tempstr,sizeof(tempstr),"Fail: %s (%s)\n", pName,m_failText );
EntityText(text_offset,tempstr,0);
text_offset++;
}
}
return text_offset;
}
//------------------------------------------------------------------------------
// Purpose : Add new entity positioned overlay text
// Input : How many lines to offset text from origin
// The text to print
// How long to display text
// The color of the text
// Output :
//------------------------------------------------------------------------------
void CAI_Agent::EntityText( int text_offset, const char *text, float duration, int r, int g, int b, int a )
{
NDebugOverlay::EntityTextAtPosition( m_vecAgentDebugOverlaysPos, text_offset, text, duration, r, g, b, a );
}
//=========================================================
//=========================================================
void CAI_Agent::OnScheduleChange ( void )
{
// EndTaskOverlay();
}
// Global Savedata for npc
//
// This should be an exact copy of the var's in the header. Fields
// that aren't save/restored are commented out
BEGIN_SIMPLE_DATADESC( CAI_Agent )
// m_pSchedule (reacquired on restore)
DEFINE_EMBEDDED( m_ScheduleState ),
DEFINE_FIELD( m_IdealSchedule, FIELD_INTEGER ), // handled specially but left in for "virtual" schedules
DEFINE_FIELD( m_failSchedule, FIELD_INTEGER ), // handled specially but left in for "virtual" schedules
// m_Conditions (custom save)
// m_CustomInterruptConditions (custom save)
// m_ConditionsPreIgnore (custom save)
// m_InverseIgnoreConditions (custom save)
DEFINE_FIELD( m_bForceConditionsGather, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bConditionsGathered, FIELD_BOOLEAN ),
// m_fIsUsingSmallHull TODO -- This needs more consideration than simple save/load
// m_failText DEBUG
// m_interruptText DEBUG
// m_failedSchedule DEBUG
// m_interuptSchedule DEBUG
// m_nDebugCurIndex DEBUG
// m_LastShootAccuracy DEBUG
// m_RecentShotAccuracy DEBUG
// m_TotalShots DEBUG
// m_TotalHits DEBUG
// m_bSelected DEBUG
// m_TimeLastShotMark DEBUG
// m_bDeferredNavigation
END_DATADESC()
BEGIN_SIMPLE_DATADESC( AIAgentScheduleState_t )
DEFINE_FIELD( iCurTask, FIELD_INTEGER ),
DEFINE_FIELD( fTaskStatus, FIELD_INTEGER ),
DEFINE_FIELD( timeStarted, FIELD_TIME ),
DEFINE_FIELD( timeCurTaskStarted, FIELD_TIME ),
DEFINE_FIELD( taskFailureCode, FIELD_INTEGER ),
DEFINE_FIELD( iTaskInterrupt, FIELD_INTEGER ),
DEFINE_FIELD( bScheduleWasInterrupted, FIELD_BOOLEAN ),
END_DATADESC()
//-----------------------------------------------------------------------------
const short AI_EXTENDED_SAVE_HEADER_VERSION = 5;
const short AI_EXTENDED_SAVE_HEADER_RESET_VERSION = 3;
const short AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_CONDITIONS = 2;
const short AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_SCHEDULE_ID_FIXUP = 3;
const short AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_SEQUENCE = 4;
const short AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_NAVIGATOR_SAVE = 5;
struct AIAgentSaveHeader_t
{
AIAgentSaveHeader_t()
: version(AI_EXTENDED_SAVE_HEADER_VERSION),
flags(0),
scheduleCrc(0)
{
szSchedule[0] = 0;
szIdealSchedule[0] = 0;
szFailSchedule[0] = 0;
szSequence[0] = 0;
}
short version;
unsigned flags;
char szSchedule[128];
CRC32_t scheduleCrc;
char szIdealSchedule[128];
char szFailSchedule[128];
char szSequence[128];
DECLARE_SIMPLE_DATADESC();
};
//-------------------------------------
BEGIN_SIMPLE_DATADESC( AIAgentSaveHeader_t )
DEFINE_FIELD( version, FIELD_SHORT ),
DEFINE_FIELD( flags, FIELD_INTEGER ),
DEFINE_AUTO_ARRAY( szSchedule, FIELD_CHARACTER ),
DEFINE_FIELD( scheduleCrc, FIELD_INTEGER ),
DEFINE_AUTO_ARRAY( szIdealSchedule, FIELD_CHARACTER ),
DEFINE_AUTO_ARRAY( szFailSchedule, FIELD_CHARACTER ),
DEFINE_AUTO_ARRAY( szSequence, FIELD_CHARACTER ),
END_DATADESC()
//-------------------------------------
int CAI_Agent::Save( ISave &save )
{
AIAgentSaveHeader_t saveHeader;
if ( m_pSchedule )
{
const char *pszSchedule = m_pSchedule->GetName();
Assert( Q_strlen( pszSchedule ) < sizeof( saveHeader.szSchedule ) - 1 );
Q_strncpy( saveHeader.szSchedule, pszSchedule, sizeof( saveHeader.szSchedule ) );
CRC32_Init( &saveHeader.scheduleCrc );
CRC32_ProcessBuffer( &saveHeader.scheduleCrc, (void *)m_pSchedule->GetTaskList(), m_pSchedule->NumTasks() * sizeof(Task_t) );
CRC32_Final( &saveHeader.scheduleCrc );
}
else
{
saveHeader.szSchedule[0] = 0;
saveHeader.scheduleCrc = 0;
}
int idealSchedule = GetGlobalScheduleId( m_IdealSchedule );
if ( idealSchedule != -1 && idealSchedule != AI_RemapToGlobal( SCHED_NONE ) )
{
CAI_Schedule *pIdealSchedule = GetSchedule( m_IdealSchedule );
if ( pIdealSchedule )
{
const char *pszIdealSchedule = pIdealSchedule->GetName();
Assert( Q_strlen( pszIdealSchedule ) < sizeof( saveHeader.szIdealSchedule ) - 1 );
Q_strncpy( saveHeader.szIdealSchedule, pszIdealSchedule, sizeof( saveHeader.szIdealSchedule ) );
}
}
int failSchedule = GetGlobalScheduleId( m_failSchedule );
if ( failSchedule != -1 && failSchedule != AI_RemapToGlobal( SCHED_NONE ) )
{
CAI_Schedule *pFailSchedule = GetSchedule( m_failSchedule );
if ( pFailSchedule )
{
const char *pszFailSchedule = pFailSchedule->GetName();
Assert( Q_strlen( pszFailSchedule ) < sizeof( saveHeader.szFailSchedule ) - 1 );
Q_strncpy( saveHeader.szFailSchedule, pszFailSchedule, sizeof( saveHeader.szFailSchedule ) );
}
}
save.WriteAll( &saveHeader );
save.StartBlock();
SaveConditions( save, m_Conditions );
SaveConditions( save, m_CustomInterruptConditions );
SaveConditions( save, m_ConditionsPreIgnore );
CAI_ScheduleBits ignoreConditions;
m_InverseIgnoreConditions.Not( &ignoreConditions );
SaveConditions( save, ignoreConditions );
save.EndBlock();
return 1;
}
//-------------------------------------
void CAI_Agent::DiscardScheduleState()
{
// We don't save/restore schedules yet
ClearSchedule( "Restoring NPC" );
m_Conditions.ClearAll();
}
//-------------------------------------
int CAI_Agent::Restore( IRestore &restore )
{
AIAgentSaveHeader_t saveHeader;
restore.ReadAll( &saveHeader );
restore.StartBlock();
RestoreConditions( restore, &m_Conditions );
RestoreConditions( restore, &m_CustomInterruptConditions );
RestoreConditions( restore, &m_ConditionsPreIgnore );
CAI_ScheduleBits ignoreConditions;
RestoreConditions( restore, &ignoreConditions );
ignoreConditions.Not( &m_InverseIgnoreConditions );
restore.EndBlock();
#ifdef TODO
// do a normal restore
int status = BaseClass::Restore(restore);
if ( !status )
return 0;
#else
int status = 1;
#endif
// Do schedule fix-up
if ( saveHeader.version >= AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_SCHEDULE_ID_FIXUP )
{
if ( saveHeader.szIdealSchedule[0] )
{
CAI_Schedule *pIdealSchedule = g_AI_AgentSchedulesManager.GetScheduleByName( saveHeader.szIdealSchedule );
m_IdealSchedule = ( pIdealSchedule ) ? pIdealSchedule->GetId() : SCHED_NONE;
}
if ( saveHeader.szFailSchedule[0] )
{
CAI_Schedule *pFailSchedule = g_AI_AgentSchedulesManager.GetScheduleByName( saveHeader.szFailSchedule );
m_failSchedule = ( pFailSchedule ) ? pFailSchedule->GetId() : SCHED_NONE;
}
}
bool bDiscardScheduleState = ( saveHeader.szSchedule[0] == 0 ||
saveHeader.version < AI_EXTENDED_SAVE_HEADER_RESET_VERSION );
if ( m_ScheduleState.taskFailureCode >= NUM_FAIL_CODES )
m_ScheduleState.taskFailureCode = FAIL_NO_TARGET; // must have been a string, gotta punt
if ( !bDiscardScheduleState )
{
m_pSchedule = g_AI_AgentSchedulesManager.GetScheduleByName( saveHeader.szSchedule );
if ( m_pSchedule )
{
CRC32_t scheduleCrc;
CRC32_Init( &scheduleCrc );
CRC32_ProcessBuffer( &scheduleCrc, (void *)m_pSchedule->GetTaskList(), m_pSchedule->NumTasks() * sizeof(Task_t) );
CRC32_Final( &scheduleCrc );
if ( scheduleCrc != saveHeader.scheduleCrc )
{
m_pSchedule = NULL;
}
}
}
if ( !m_pSchedule )
bDiscardScheduleState = true;
if ( bDiscardScheduleState )
{
DiscardScheduleState();
}
return status;
}
//-------------------------------------
void CAI_Agent::SaveConditions( ISave &save, const CAI_ScheduleBits &conditions )
{
for (int i = 0; i < MAX_CONDITIONS; i++)
{
if (conditions.IsBitSet(i))
{
const char *pszConditionName = ConditionName(AI_RemapToGlobal(i));
if ( !pszConditionName )
break;
save.WriteString( pszConditionName );
}
}
save.WriteString( "" );
}
//-------------------------------------
void CAI_Agent::RestoreConditions( IRestore &restore, CAI_ScheduleBits *pConditions )
{
pConditions->ClearAll();
char szCondition[256];
for (;;)
{
restore.ReadString( szCondition, sizeof(szCondition), 0 );
if ( !szCondition[0] )
break;
int iCondition = GetSchedulingSymbols()->ConditionSymbolToId( szCondition );
if ( iCondition != -1 )
pConditions->Set( AI_RemapFromGlobal( iCondition ) );
}
}
//-----------------------------------------------------------------------------
// Purpose: Written by subclasses macro to load schedules
// Input :
// Output :
//-----------------------------------------------------------------------------
bool CAI_Agent::LoadSchedules(void)
{
return true;
}
//-----------------------------------------------------------------------------
bool CAI_Agent::LoadedSchedules(void)
{
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Constructor
// Input :
// Output :
//-----------------------------------------------------------------------------
CAI_Agent::CAI_Agent(void)
{
m_pSchedule = NULL;
m_IdealSchedule = SCHED_NONE;
// ----------------------------
// Debugging fields
// ----------------------------
m_interruptText = NULL;
m_failText = NULL;
m_failedSchedule = NULL;
m_interuptSchedule = NULL;
}
//-----------------------------------------------------------------------------
// Purpose: Destructor
// Input :
// Output :
//-----------------------------------------------------------------------------
CAI_Agent::~CAI_Agent(void)
{
}
//-----------------------------------------------------------------------------
static void AIMsgGuts( CAI_Agent *pAI, unsigned flags, const char *pszMsg )
{
// int len = strlen( pszMsg );
// const char *pszFmt2 = NULL;
//
// if ( len && pszMsg[len-1] == '\n' )
// {
// (const_cast<char *>(pszMsg))[len-1] = 0;
// pszFmt2 = "%s (%s: %d/%s) [%d]\n";
// }
// else
// pszFmt2 = "%s (%s: %d/%s) [%d]";
//
// DevMsg( pszFmt2,
// pszMsg,
// pAI->GetClassname(),
// pAI->entindex(),
// ( pAI->GetEntityName() == NULL_STRING ) ? "<unnamed>" : STRING(pAI->GetEntityName()),
// gpGlobals->tickcount );
}
void DevMsg( CAI_Agent *pAI, unsigned flags, const char *pszFormat, ... )
{
if ( (flags & AIMF_IGNORE_SELECTED) || (pAI->GetDebugOverlayFlags() & OVERLAY_NPC_SELECTED_BIT) )
{
AIMsgGuts( pAI, flags, CFmtStr( &pszFormat ) );
}
}
//-----------------------------------------------------------------------------
void DevMsg( CAI_Agent *pAI, const char *pszFormat, ... )
{
if ( (pAI->GetDebugOverlayFlags() & OVERLAY_NPC_SELECTED_BIT) )
{
AIMsgGuts( pAI, 0, CFmtStr( &pszFormat ) );
}
}
//=========================================================
// GetScheduleOfType - returns a pointer to one of the
// NPC's available schedules of the indicated type.
//=========================================================
CAI_Schedule *CAI_Agent::GetScheduleOfType( int scheduleType )
{
// allow the derived classes to pick an appropriate version of this schedule or override
// base schedule types.
AI_PROFILE_SCOPE_BEGIN(CAI_BaseNPC_TranslateSchedule);
scheduleType = TranslateSchedule( scheduleType );
AI_PROFILE_SCOPE_END();
// Get a pointer to that schedule
CAI_Schedule *schedule = GetSchedule(scheduleType);
if (!schedule)
{
//DevMsg( "GetScheduleOfType(): No CASE for Schedule Type %d!\n", scheduleType );
return GetSchedule(SCHED_NONE);
}
return schedule;
}
CAI_Schedule *CAI_Agent::GetSchedule(int schedule)
{
if ( schedule < NEXT_SCHEDULE )
{
return NULL;
}
if (!GetClassScheduleIdSpace()->IsGlobalBaseSet())
{
Warning("ERROR: %s missing schedule!\n", GetSchedulingErrorName());
return g_AI_AgentSchedulesManager.GetScheduleFromID(SCHED_NONE);
}
if ( AI_IdIsLocal( schedule ) )
{
schedule = GetClassScheduleIdSpace()->ScheduleLocalToGlobal(schedule);
}
return g_AI_AgentSchedulesManager.GetScheduleFromID( schedule );
}
bool CAI_Agent::IsCurSchedule( int schedId, bool fIdeal )
{
if ( !m_pSchedule )
return ( schedId == SCHED_NONE || schedId == AI_RemapToGlobal(SCHED_NONE) );
schedId = ( AI_IdIsLocal( schedId ) ) ? GetClassScheduleIdSpace()->ScheduleLocalToGlobal(schedId) : schedId;
if ( fIdeal )
return ( schedId == m_IdealSchedule );
return ( m_pSchedule->GetId() == schedId );
}
const char* CAI_Agent::ConditionName(int conditionID)
{
if ( AI_IdIsLocal( conditionID ) )
conditionID = GetClassScheduleIdSpace()->ConditionLocalToGlobal(conditionID);
return GetSchedulingSymbols()->ConditionIdToSymbol(conditionID);
}
const char *CAI_Agent::TaskName(int taskID)
{
if ( AI_IdIsLocal( taskID ) )
taskID = GetClassScheduleIdSpace()->TaskLocalToGlobal(taskID);
return GetSchedulingSymbols()->TaskIdToSymbol( taskID );
}
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() )
extern ConVar ai_simulate_task_overtime;
#define MAX_TASKS_RUN 10
struct AgentTaskTimings
{
const char *pszTask;
CFastTimer selectSchedule;
CFastTimer startTimer;
CFastTimer runTimer;
};
AgentTaskTimings g_AIAgentTaskTimings[MAX_TASKS_RUN];
int g_nAIAgentTasksRun;
void CAI_Agent::DumpTaskTimings()
{
DevMsg(" Tasks timings:\n" );
for ( int i = 0; i < g_nAIAgentTasksRun; ++i )
{
DevMsg( " %32s -- select %5.2f, start %5.2f, run %5.2f\n", g_AIAgentTaskTimings[i].pszTask,
g_AIAgentTaskTimings[i].selectSchedule.GetDuration().GetMillisecondsF(),
g_AIAgentTaskTimings[i].startTimer.GetDuration().GetMillisecondsF(),
g_AIAgentTaskTimings[i].runTimer.GetDuration().GetMillisecondsF() );
}
}
//=========================================================
// FHaveSchedule - Returns true if NPC's GetCurSchedule()
// is anything other than NULL.
//=========================================================
bool CAI_Agent::FHaveSchedule( void )
{
if ( GetCurSchedule() == NULL )
{
return false;
}
return true;
}
//=========================================================
// ClearSchedule - blanks out the caller's schedule pointer
// and index.
//=========================================================
void CAI_Agent::ClearSchedule( const char *szReason )
{
if (szReason && GetDebugOverlayFlags() & 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_Agent::FScheduleDone ( void )
{
Assert( GetCurSchedule() != NULL );
if ( GetScheduleCurTaskIndex() == GetCurSchedule()->NumTasks() )
{
return true;
}
return false;
}
//=========================================================
bool CAI_Agent::SetSchedule( int localScheduleID )
{
CAI_Schedule *pNewSchedule = GetScheduleOfType( localScheduleID );
if ( pNewSchedule )
{
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_Agent::SetSchedule( CAI_Schedule *pNewSchedule )
{
//Assert( pNewSchedule != NULL );
m_ScheduleState.timeCurTaskStarted = m_ScheduleState.timeStarted = gpGlobals->curtime;
m_ScheduleState.bScheduleWasInterrupted = false;
m_pSchedule = pNewSchedule ;
ResetScheduleCurTaskIndex();
SetTaskStatus( TASKSTATUS_NEW );
m_failSchedule = SCHED_NONE;
m_Conditions.ClearAll();
m_bConditionsGathered = false;
m_InverseIgnoreConditions.SetAll();
// 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( pNewSchedule != NULL )
{
if (GetDebugOverlayFlags() & OVERLAY_TASK_TEXT_BIT)
{
DevMsg(this, AIMF_IGNORE_SELECTED, "Schedule: %s (time: %.2f)\n", pNewSchedule->GetName(), gpGlobals->curtime );
}
ADD_DEBUG_HISTORY( HISTORY_AI_DECISIONS, UTIL_VarArgs("%s(%d): Schedule: %s (time: %.2f)\n", GetDebugName(), entindex(), pNewSchedule->GetName(), gpGlobals->curtime ) );
}
}
//=========================================================
// NextScheduledTask - increments the ScheduleIndex
//=========================================================
void CAI_Agent::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_Agent::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_Agent::IsScheduleValid()
{
if ( GetCurSchedule() == NULL || GetCurSchedule()->NumTasks() == 0 )
{
return false;
}
//Start out with the base schedule's set interrupt conditions
GetCurSchedule()->GetInterruptMask( &m_CustomInterruptConditions );
if ( !m_CustomInterruptConditions.IsBitSet( COND_NO_CUSTOM_INTERRUPTS ) )
{
BuildScheduleTestBits();
}
// 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 (GetDebugOverlayFlags() & 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;
}
}
}
return false;
}
if ( HasCondition(COND_SCHEDULE_DONE) ||
HasCondition(COND_TASK_FAILED) )
{
// 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
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose: Returns a new schedule based on current condition bits.
//-----------------------------------------------------------------------------
CAI_Schedule *CAI_Agent::GetNewSchedule( void )
{
int scheduleType;
//
// Schedule selection code here overrides all leaf schedule selection.
//
scheduleType = SelectSchedule();
m_IdealSchedule = GetGlobalScheduleId( scheduleType );
return GetScheduleOfType( scheduleType );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CAI_Schedule *CAI_Agent::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_Agent *pNPC, int taskTime, int timeLimit )
{
#ifdef DEBUG
if( ai_simulate_task_overtime.GetBool() )
return true;
#endif
return false;
}
//-------------------------------------
void CAI_Agent::MaintainSchedule ( void )
{
AI_PROFILE_SCOPE(CAI_Agent_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_AIAgentTaskTimings, 0, sizeof(g_AIAgentTaskTimings) );
g_nAIAgentTasksRun = 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();
}
}
int curTiming = g_nAIAgentTasksRun;
g_nAIAgentTasksRun++;
// validate existing schedule
if ( !IsScheduleValid() /* || m_NPCState != m_IdealNPCState */ )
{
// Notify the NPC that his schedule is changing
m_ScheduleState.bScheduleWasInterrupted = true;
OnScheduleChange();
if ( !m_bConditionsGathered )
{
// 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 (GetDebugOverlayFlags() & 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_AIAgentTaskTimings[curTiming].selectSchedule.Start();
pNewSchedule = GetNewSchedule();
g_AIAgentTaskTimings[curTiming].selectSchedule.End();
SetSchedule( pNewSchedule );
}
}
if (!GetCurSchedule())
{
g_AIAgentTaskTimings[curTiming].selectSchedule.Start();
pNewSchedule = GetNewSchedule();
g_AIAgentTaskTimings[curTiming].selectSchedule.End();
if (pNewSchedule)
{
SetSchedule( pNewSchedule );
}
}
if ( !GetCurSchedule() || GetCurSchedule()->NumTasks() == 0 )
{
return;
}
AI_PROFILE_SCOPE_BEGIN_( CAI_Agent::GetSchedulingSymbols()->ScheduleIdToSymbol( GetCurSchedule()->GetId() ) );
if ( GetTaskStatus() == TASKSTATUS_NEW )
{
if ( GetScheduleCurTaskIndex() == 0 )
{
int globalId = GetCurSchedule()->GetId();
int localId = GetLocalScheduleId( globalId ); // if localId == -1, then it came from a behavior
OnStartSchedule( (localId != -1)? localId : globalId );
}
g_AIAgentTaskTimings[curTiming].startTimer.Start();
const Task_t *pTask = GetTask();
const char *pszTaskName = ( bDebugTaskNames ) ? TaskName( pTask->iTask ) : "ai_task";
Assert( pTask != NULL );
g_AIAgentTaskTimings[i].pszTask = pszTaskName;
if (GetDebugOverlayFlags() & OVERLAY_TASK_TEXT_BIT)
{
DevMsg(this, AIMF_IGNORE_SELECTED, " Task: %s\n", pszTaskName );
}
ADD_DEBUG_HISTORY( HISTORY_AI_DECISIONS, UTIL_VarArgs("%s(%d): Task: %s\n", GetDebugName(), entindex(), pszTaskName ) );
OnStartTask();
m_ScheduleState.taskFailureCode = NO_TASK_FAILURE;
m_ScheduleState.timeCurTaskStarted = gpGlobals->curtime;
AI_PROFILE_SCOPE_BEGIN_( pszTaskName );
AI_PROFILE_SCOPE_BEGIN(CAI_Agent_StartTask);
StartTask( pTask );
AI_PROFILE_SCOPE_END();
AI_PROFILE_SCOPE_END();
// if ( TaskIsRunning() && !HasCondition(COND_TASK_FAILED) )
// StartTaskOverlay();
g_AIAgentTaskTimings[curTiming].startTimer.End();
// DevMsg( "%.2f StartTask( %s )\n", gpGlobals->curtime, m_pTaskSR->GetStringText( pTask->iTask ) );
}
AI_PROFILE_SCOPE_END();
// // UNDONE: Twice?!!!
// MaintainActivity();
AI_PROFILE_SCOPE_BEGIN_( CAI_Agent::GetSchedulingSymbols()->ScheduleIdToSymbol( GetCurSchedule()->GetId() ) );
if ( !TaskIsComplete() && GetTaskStatus() != TASKSTATUS_NEW )
{
if ( TaskIsRunning() && !HasCondition(COND_TASK_FAILED) && runTask )
{
const Task_t *pTask = GetTask();
const char *pszTaskName = ( bDebugTaskNames ) ? TaskName( pTask->iTask ) : "ai_task";
Assert( pTask != NULL );
g_AIAgentTaskTimings[i].pszTask = pszTaskName;
// DevMsg( "%.2f RunTask( %s )\n", gpGlobals->curtime, m_pTaskSR->GetStringText( pTask->iTask ) );
g_AIAgentTaskTimings[curTiming].runTimer.Start();
AI_PROFILE_SCOPE_BEGIN_( pszTaskName );
AI_PROFILE_SCOPE_BEGIN(CAI_Agent_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) )
{
// EndTaskOverlay();
//
}
g_AIAgentTaskTimings[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;
}
}
//-----------------------------------------------------------------------------
// Start task!
//-----------------------------------------------------------------------------
void CAI_Agent::StartTask( const Task_t *pTask )
{
switch( pTask->iTask )
{
case TASK_SET_SCHEDULE:
if ( !SetSchedule( pTask->flTaskData ) )
TaskFail(FAIL_SCHEDULE_NOT_FOUND);
break;
default:
DevMsg( "No StartTask entry for %s\n", TaskName( pTask->iTask ) );
};
}
//=========================================================
// RunTask
//=========================================================
void CAI_Agent::RunTask( const Task_t *pTask )
{
DevMsg( "No RunTask entry for %s\n", TaskName( pTask->iTask ) );
TaskComplete();
}
//=========================================================
// GetTask - returns a pointer to the current
// scheduled task. NULL if there's a problem.
//=========================================================
const Task_t *CAI_Agent::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 ];
}
//-----------------------------------------------------------------------------
// 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_Agent::SelectSchedule( void )
{
return SCHED_FAIL;
}
//-----------------------------------------------------------------------------
int CAI_Agent::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
{
return ( m_failSchedule != SCHED_NONE ) ? m_failSchedule : SCHED_FAIL;
}
void CAI_Agent::InitDefaultTaskSR(void)
{
#define ADD_DEF_TASK( name ) idSpace.AddTask(#name, name, "CAI_Agent" )
CAI_ClassScheduleIdSpace &idSpace = CAI_Agent::AccessClassScheduleIdSpaceDirect();
ADD_DEF_TASK( TASK_INVALID );
ADD_DEF_TASK( TASK_SET_SCHEDULE );
}
void CAI_Agent::InitDefaultConditionSR(void)
{
#define ADD_CONDITION_TO_SR( _n ) idSpace.AddCondition( #_n, _n, "CAI_Agent" )
CAI_ClassScheduleIdSpace &idSpace = CAI_Agent::AccessClassScheduleIdSpaceDirect();
ADD_CONDITION_TO_SR( COND_NONE );
ADD_CONDITION_TO_SR( COND_TASK_FAILED );
ADD_CONDITION_TO_SR( COND_SCHEDULE_DONE );
ADD_CONDITION_TO_SR( COND_NO_CUSTOM_INTERRUPTS ); // Don't call BuildScheduleTestBits for this schedule. Used for schedules that must strictly control their interruptibility.
}
void CAI_Agent::InitDefaultScheduleSR(void)
{
#define ADD_DEF_SCHEDULE( name, localId ) idSpace.AddSchedule(name, localId, "CAI_Agent" )
CAI_ClassScheduleIdSpace &idSpace = CAI_Agent::AccessClassScheduleIdSpaceDirect();
ADD_DEF_SCHEDULE( "SCHED_NONE", SCHED_NONE);
ADD_DEF_SCHEDULE( "SCHED_FAIL", SCHED_FAIL );
}
bool CAI_Agent::LoadDefaultSchedules(void)
{
// AI_LOAD_DEF_SCHEDULE( CAI_Agent, SCHED_NONE);
//AI_LOAD_DEF_SCHEDULE( CAI_Agent, SCHED_FAIL);
return true;
}