//========= 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(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 ) ? "" : 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 %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; }