Schedule: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
No edit summary
 
(24 intermediate revisions by 7 users not shown)
Line 1: Line 1:
A Schedule is basically a list of [[Tasks]] the NPC is to perform, a schedule could be to go to a specific location, such as SCHED_FORCED_GO or a schedule to create an attack plan SCHED_PLAN_ATTACK. The NPC's schedule selection is governed by it's current [[States|State]] and [[Conditions]].
{{npc tut}}


Some interesting methods are shown below that make up CAI_BaseNPC class.
A '''schedule''' is a list of [[task]]s for an NPC to perform. A new schedule is chosen '''only''' when there is no active one; this might be because the NPC has only just spawned or because the last schedule just completed, failed, or encountered an interrupt [[condition]].


== Creating a new schedule ==


'''SelectSchedule()'''
=== Enumeration ===


In CAI_BaseNPC the method SelectSchedule() determines what schedule the NPC should run depending on its state and conditions.  
Schedules are identified with a number. Naturally, enums are employed for human readability.


In CAI_BaseNPC, all of the default States are considered, and additional methods are called to select the schedule based on condition, for example, when the NPC is in the NPC_STATE_IDLE, the method SelectIdleSchedule() is called, this method is similar to SelectSchedule(), all it does is select a schedule based on the npc's state and conditions.
<source lang=cpp>
enum
{
SCHED_DODGE_ENEMY_FIRE = LAST_SHARED_SCHEDULE,  
LAST_MY_NPC_SCHEDULE,
};
</source>


Child classes can implement these schedule selection methods to select their own custom schedules based on their own custom conditions.  
The "last schedule" values are used to prevent collisions between enums when inheriting from existing classes. <code>LAST_SHARED_SCHEDULE</code> is the end of the standard list of schedules that are shared by all NPCs; if you are inheriting from something other than <code>[[CAI_BaseNPC]]</code> you will probably want to check your baseclass and get its own "last schedule" value.


For instance, we could create our own schedule SCHED_MEDICALPOINT_GO and a condition COND_NEEDS_MEDICAL_ASSISTANCE.
=== Definition ===


We could implement the schedule selection in SelectSchedule() or a schedule selection method variation as follows
Once the schedule is enumerated we can start defining it within the <code>AI_BEGIN_CUSTOM_NPC</code> block. All schedules use the same structure:


<pre>
<source lang=cpp>
if(HasCondition(COND_NEEDS_MEDICAL_ASSISTANCE))  
AI_BEGIN_CUSTOM_NPC( npc_custom, CNPC_Custom )
    return SCHED_MEDICALPOINT_GO;
DEFINE_SCHEDULE
</pre>
(
SCHED_DODGE_ENEMY_FIRE,


The following switch statement has been taken from SelectSchedule() in CAI_BaseNPC, it shows how the different variations of schedule selection is chosen depending on the NPC's state.
" Tasks"
" TASK_FIND_DODGE_DIRECTION 3"
" TASK_JUMP 0"
""
" Interrupts"
" COND_LIGHT_DAMAGE"
)
AI_END_CUSTOM_NPC()
</source>


{{warning|The first character of each string (except for the 'gap') must be a space or a tab. Your schedule will otherwise be invalid!}}


<pre>
There are two parts to the definition:
switch( m_NPCState )
{
      case NPC_STATE_NONE:  
            DevWarning( 2, "NPC_STATE IS NONE!\n" );
            break;
     
      case NPC_STATE_PRONE:
            return SCHED_IDLE_STAND;
      case NPC_STATE_IDLE:
            AssertMsgOnce( GetEnemy() == NULL, "NPC has enemy but is not in combat state?" );
            return SelectIdleSchedule();


      case NPC_STATE_ALERT:  
; <code>Tasks</code>
            AssertMsgOnce( GetEnemy() == NULL, "NPC has enemy but is not in combat state?" );  
: The list of sequential [[task]]s an NPC must perform in order to complete the schedule. See [[Shared tasks]] for the engine's basic set. {{note|Every task requires a numeric argument. If the task doesn't actually make use of one, just pass <code>0</code>.}}
            return SelectAlertSchedule();
; <code>Interrupts</code>
: A list of [[conditions]] that will cause the schedule to be abandoned, and a new one chosen, if any are ever detected.


      case NPC_STATE_COMBAT:
In the example above the NPC will attempt to find a suitable direction to dodge in (checking in a maximum of three directions, judging by the parameter) before using the output of that task, stored somewhere in the class, to perform the movement itself. But if it encounters <code>COND_LIGHT_DAMAGE</code> (any damage greater than zero) during the process, it will abandon the schedule and select another.
            return SelectCombatSchedule();
     
      case NPC_STATE_DEAD:
            return SelectDeadSchedule();


      case NPC_STATE_SCRIPT:
The behavioural code which decides what the NPC actually ''does'' is defined in the component [[task]]s.
            return SelectScriptSchedule();


      default:
== SelectSchedule() ==
            DevWarning( 2, "Invalid State for SelectSchedule!\n" );
            break;
}
</pre>


'''TranslateSchedule()'''
<code>int SelectSchedule(void)</code> is called whenever an NPC finds itself without a schedule, and contains the logic that decides which should be selected to fill the gap. The NPC's current [[state]] and [[condition]]s usually play a large part in the decision.


This method is called after the schedule selection, it can be used to translate parent NPC schedules to child specific schedules, it's useful if you want to change the default schedules to run your own ones instead.
A schedule is selected if the function returns it's name - e.g. <code>return SCHED_DODGE_ENEMY_FIRE;</code>.
 
{{tip|If you expect to write a lot of schedule selection logic, you may find it useful to split it into sub-functions of your own making. <code>CAI_BaseNPC</code> for instance has <code>SelectIdleSchedule()</code>, <code>SelectCombatSchedule()</code>, etc. which are called depending on the NPC's state.}}
 
=== Useful functions ===
 
; <code>HasCondition([[int]] condition)</code>
: True if the specified [[condition]] has been set for the current think. Of course, you'd use your enumerated names instead of passing an integer directly!
; <code>GetState()</code>
: Returns the NPC's state. You can also access m_NPCState directly, but GetState() is read-only and therefore safer.
; <code>return BaseClass<nowiki>::</nowiki>SelectSchedule()</code>
: If none of your own schedules were picked, there are very few situations where you won't want to pass through to the base class.
 
== TranslateSchedule() ==
 
<code>TranslateSchedule()</code> is called immediately after <code>SelectSchedule()</code>. It is designed to allow child classes to replace their parent's or parents' schedules with those of their own without having to duplicate selection logic.
 
<source lang=cpp>
int CNPC_Custom::TranslateSchedule( int scheduleType )
{
switch( scheduleType )
{
case SCHED_IDLE_WALK:
{
return SCHED_CUSTOM_IDLE_WALK;
break;
}
}
 
return BaseClass::TranslateSchedule( scheduleType );
}
</source>
 
{{navbar|Creating a condition|Creating an NPC|Creating a task}}
 
[[Category:AI Programming]]

Latest revision as of 07:18, 16 September 2011

A schedule is a list of tasks for an NPC to perform. A new schedule is chosen only when there is no active one; this might be because the NPC has only just spawned or because the last schedule just completed, failed, or encountered an interrupt condition.

Creating a new schedule

Enumeration

Schedules are identified with a number. Naturally, enums are employed for human readability.

enum
{
	SCHED_DODGE_ENEMY_FIRE = LAST_SHARED_SCHEDULE, 
	LAST_MY_NPC_SCHEDULE,
};

The "last schedule" values are used to prevent collisions between enums when inheriting from existing classes. LAST_SHARED_SCHEDULE is the end of the standard list of schedules that are shared by all NPCs; if you are inheriting from something other than CAI_BaseNPC you will probably want to check your baseclass and get its own "last schedule" value.

Definition

Once the schedule is enumerated we can start defining it within the AI_BEGIN_CUSTOM_NPC block. All schedules use the same structure:

AI_BEGIN_CUSTOM_NPC( npc_custom, CNPC_Custom )
	DEFINE_SCHEDULE
	(
		SCHED_DODGE_ENEMY_FIRE,

		"	Tasks"
		"		TASK_FIND_DODGE_DIRECTION	3"
		"		TASK_JUMP	 		0"
		""
		"	Interrupts"
		"		COND_LIGHT_DAMAGE"
	)
AI_END_CUSTOM_NPC()
Warning.pngWarning:The first character of each string (except for the 'gap') must be a space or a tab. Your schedule will otherwise be invalid!

There are two parts to the definition:

Tasks
The list of sequential tasks an NPC must perform in order to complete the schedule. See Shared tasks for the engine's basic set.
Note.pngNote:Every task requires a numeric argument. If the task doesn't actually make use of one, just pass 0.
Interrupts
A list of conditions that will cause the schedule to be abandoned, and a new one chosen, if any are ever detected.

In the example above the NPC will attempt to find a suitable direction to dodge in (checking in a maximum of three directions, judging by the parameter) before using the output of that task, stored somewhere in the class, to perform the movement itself. But if it encounters COND_LIGHT_DAMAGE (any damage greater than zero) during the process, it will abandon the schedule and select another.

The behavioural code which decides what the NPC actually does is defined in the component tasks.

SelectSchedule()

int SelectSchedule(void) is called whenever an NPC finds itself without a schedule, and contains the logic that decides which should be selected to fill the gap. The NPC's current state and conditions usually play a large part in the decision.

A schedule is selected if the function returns it's name - e.g. return SCHED_DODGE_ENEMY_FIRE;.

Tip.pngTip:If you expect to write a lot of schedule selection logic, you may find it useful to split it into sub-functions of your own making. CAI_BaseNPC for instance has SelectIdleSchedule(), SelectCombatSchedule(), etc. which are called depending on the NPC's state.

Useful functions

HasCondition(int condition)
True if the specified condition has been set for the current think. Of course, you'd use your enumerated names instead of passing an integer directly!
GetState()
Returns the NPC's state. You can also access m_NPCState directly, but GetState() is read-only and therefore safer.
return BaseClass::SelectSchedule()
If none of your own schedules were picked, there are very few situations where you won't want to pass through to the base class.

TranslateSchedule()

TranslateSchedule() is called immediately after SelectSchedule(). It is designed to allow child classes to replace their parent's or parents' schedules with those of their own without having to duplicate selection logic.

int CNPC_Custom::TranslateSchedule( int scheduleType )
{
	switch( scheduleType )
	{
		case SCHED_IDLE_WALK:
		{
			return SCHED_CUSTOM_IDLE_WALK;
			break;
		}
	}

	return BaseClass::TranslateSchedule( scheduleType );
}