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
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; |
|
}
|
|
|