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.
1746 lines
48 KiB
1746 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)
|
|
{
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
#ifndef DBGFLAG_STRINGS_STRIP
|
|
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 ) );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
//=========================================================
|
|
// 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;
|
|
}
|