RunAI()
, you can see there is a call to MaintainSchedule()
. If you look at this method, you will see things about schedules, tasks, completion and failure.Task_t
structure in the code represent a task, here is its definition:
struct Task_t
{
int iTask; // The ID of this task (TASK_WAIT for example)
float flData; // Additional data that this task may use (time to wait for TASK_WAIT for example)
};
Same thing with Schedule_t
for a schedule:
struct Schedule_t
{
Task_t *pTasklist; // Array of tasks for this schedule
int cTasks; // The number of tasks (always use "ARRAYSIZE" of the value you use in "pTasklist" to be safe)
int iInterruptMask; // Condition bit(s) allowed to interrupt this schedule if needed
int iSoundMask; // Sound bit(s) allowed to interrupt this schedule if needed
const char *pName; // Name of the schedule shown in the console when testing/debugging
};
Let us take a deeper look at how declared and defined a task and a schedule are by looking at how a monster make a small flinch:
Task_t tlSmallFlinch[] =
{
{ TASK_REMEMBER, (float)bits_MEMORY_FLINCHED }, // Remember in my memory that I flinched
{ TASK_STOP_MOVING, 0 }, // Stop moving
{ TASK_SMALL_FLINCH, 0 }, // Make a small flinch
};
Schedule_t slSmallFlinch[] =
{
{
tlSmallFlinch, // This schedule use the "tlSmallFlinch" tasks array
ARRAYSIZE( tlSmallFlinch ), // "ARRAYSIZE" will return 3 here and tell this schedule has 3 tasks
0, // No condition bit can interruot this schedule
0, // No sound bit can interrupt this schedule
"Small Flinch" // This schedule is named "Small Flinch"
},
};
How the monster is aware that it should make a small flinch? This is where Schedule_t *GetSchedule()
intervenes:
Schedule_t *CBaseMonster::GetSchedule()
{
switch ( m_MonsterState )
{
// [...]
case MONSTERSTATE_COMBAT:
{
// [...]
else if ( HasConditions( bits_COND_LIGHT_DAMAGE ) && !HasMemory( bits_MEMORY_FLINCHED ) )
{
return GetScheduleOfType( SCHED_SMALL_FLINCH );
}
// [...]
break;
}
default:
{
ALERT( at_aiconsole, "Invalid State for GetSchedule!\n" );
break;
}
}
return &slError[0];
}
In the above example, if the monster is in combat, has the "took light damage" condition bit enabled and does not remember in it's memory "having already flinched", then he will try to perform a schedule tied to SCHED_SMALL_FLINCH
.Schedule_t *GetScheduleOfType( int Type )
to see how the schedule is actually executed:
Schedule_t *CBaseMonster::GetScheduleOfType( int Type )
{
switch ( Type )
{
// [...]
case SCHED_SMALL_FLINCH:
{
return &slSmallFlinch[0];
}
default:
{
ALERT( at_console, "GetScheduleOfType()\nNo CASE for Schedule Type %d!\n", Type );
return &slIdleStand[0];
break;
}
}
return nullptr;
}
You can notice that SCHED_
constants are used to identify schedules and tie them to a Schedule_t
.StartTask( Task_t *pTask )
method is called whenever a task is requested to be performed. Let us look at task startup for small flinch:
void CBaseMonster::StartTask( Task_t *pTask )
{
switch ( pTask->iTask )
{
// [...]
case TASK_REMEMBER:
{
Remember( (int)pTask->flData );
TaskComplete();
break;
}
// [...]
case TASK_STOP_MOVING:
{
if ( m_IdealActivity == m_movementActivity )
{
m_IdealActivity = GetStoppedActivity();
}
RouteClear();
TaskComplete();
break;
}
// [...]
case TASK_SMALL_FLINCH:
{
m_IdealActivity = GetSmallFlinchActivity();
break;
}
// [...]
default:
{
ALERT( at_aiconsole, "No StartTask entry for %d\n", (SHARED_TASKS)pTask->iTask );
break;
}
}
}
There are two interesting things to note here:
TASK_REMEMBER
, pTask->flData
is passed as value to the only parameter of the Remember()
method which is bits_MEMORY_FLINCHED
as defined in the task itself.TASK_REMEMBER
and TASK_STOP_MOVING
notify they are completed successfully through TaskComplete()
because those are tasks that don't need another "thinking tick" to be completed (or failed). This is not the case for TASK_SMALL_FLINCH
.TASK_SMALL_FLINCH
notifies the AI that it has finished? This is the job of RunTask( Task_t *pTask )
which is called by RunAI()
. A reminder that RunAI()
is called every "thinking tick" until the monster dies.
void CBaseMonster::RunTask( Task_t *pTask )
{
switch ( pTask->iTask )
{
// [...]
case TASK_SMALL_FLINCH:
{
if ( m_fSequenceFinished )
{
TaskComplete();
}
}
break;
// [...]
}
}
You can see here that the task will be marked as completed successfully when the sequence/animation has finished playing. We can also determine that the duration of the flinch is determined by the animation/sequence itself.TaskFail()
.int TaskIsComplete()
and int TaskIsRunning()
. And you guessed it already, monsters can have their own tasks and schedules as well. You can find most of the common tasks in the defaultai.cpp
file.float CoverRadius()
is overridden. There is also "normal cover" (BOOL FindCover( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist )
) and "lateral cover" (BOOL FindLateralCover( const Vector &vecThreat, const Vector &vecViewOffset )
), the difference between them is that the latter try to find cover directly to the left or right of the monster.
You must log in to post a comment. You can login or register a new account.