Schedule: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
m (Have no idea why my sig appeared in the schedule list, slip of the mouse finger :))
 
(12 intermediate revisions by 4 users not shown)
Line 1: Line 1:
[[Category:AI]][[Category:Programming]]
{{npc tut}}


A Schedule is a list of [[Tasks]] the NPC is to perform, a schedule could be to go to a specific location, such as <code>SCHED_FORCED_GO</code> or a schedule to create an attack plan <code>SCHED_PLAN_ATTACK</code>. The NPC's schedule selection is governed by its current [[States|State]] and [[Conditions]].
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]].


Custom schedules can be added by first declaring a new enum that will define the custom schedules.
== Creating a new schedule ==


<pre>
=== Enumeration ===
 
Schedules are identified with a number. Naturally, enums are employed for human readability.
 
<source lang=cpp>
enum
enum
{
{
    SCHED_DODGE_ENEMY_FIRE = LAST_SHARED_SCHEDULE,  
SCHED_DODGE_ENEMY_FIRE = LAST_SHARED_SCHEDULE,
}
LAST_MY_NPC_SCHEDULE,
</pre>
};
</source>
 
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.
 
=== Definition ===


Then we would need to define our custom schedule using the <code>DEFINE_SCHEDULE</code> macro.
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>
AI_BEGIN_CUSTOM_NPC( npc_custom, CNPC_Custom )
AI_BEGIN_CUSTOM_NPC( npc_custom, CNPC_Custom )
DEFINE_SCHEDULE
DEFINE_SCHEDULE
(
(
SCHED_DODGE_ENEMY_FIRE,
SCHED_DODGE_ENEMY_FIRE,
 
" Tasks"
" TASK_FIND_DODGE_DIRECTION 3"
" TASK_JUMP 0"
""
" Interrupts"
        "              COND_LIGHT_DAMAGE"
);


" Tasks"
" TASK_FIND_DODGE_DIRECTION 3"
" TASK_JUMP 0"
""
" Interrupts"
" COND_LIGHT_DAMAGE"
)
AI_END_CUSTOM_NPC()
AI_END_CUSTOM_NPC()
</pre>
</source>
 
When you define a schedule you provide two sections, [[Tasks]] and Interrupts.
 
An Interrupt is a condition that will cause the NPC to break out of the schedule and run a more appropriate one to the given condition and/or state. In this case if the NPC experiences <code>COND_LIGHT_DAMAGE</code> (any damage greater than zero) the NPC will break out of this schedule and play a more appropriate one which would be handled in <code>SelectSchedule()</code> or state specific variants.
 
Some interesting methods are described below that make up <code>CAI_BaseNPC</code> class.
 
==The SelectSchedule() method==
 
In <code>CAI_BaseNPC</code> the method <code>SelectSchedule()</code> determines what schedule the NPC should run depending on its state and conditions.
 
In <code>CAI_BaseNPC</code>, 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 <code>NPC_STATE_IDLE</code>, the method <code>SelectIdleSchedule()</code> is called, this method is similar to <code>SelectSchedule()</code>, all it does is select a schedule based on the NPC's state and conditions.
 
Child classes can implement these schedule selection methods to select their own custom schedules based on their own custom conditions.


For instance, we could create our own schedule <code>SCHED_MEDICALPOINT_GO</code> and a condition <code>COND_NEEDS_MEDICAL_ASSISTANCE</code>.  
{{warning|The first character of each string (except for the 'gap') must be a space or a tab. Your schedule will otherwise be invalid!}}


We could implement the schedule selection in <code>SelectSchedule()</code> or a schedule selection method variation as follows
There are two parts to the definition:


<pre>
; <code>Tasks</code>
if(HasCondition(COND_NEEDS_MEDICAL_ASSISTANCE))
: 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 SCHED_MEDICALPOINT_GO;  
; <code>Interrupts</code>
</pre>
: A list of [[conditions]] that will cause the schedule to be abandoned, and a new one chosen, if any are ever detected.


The following switch statement has been taken from <code>SelectSchedule()</code> in <code>CAI_BaseNPC</code>, it shows how the different variations of schedule selection is chosen depending on the NPC's state.  
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.


The behavioural code which decides what the NPC actually ''does'' is defined in the component [[task]]s.


<pre>
== SelectSchedule() ==
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>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.
            AssertMsgOnce( GetEnemy() == NULL, "NPC has enemy but is not in combat state?" );
            return SelectAlertSchedule();


      case NPC_STATE_COMBAT:
A schedule is selected if the function returns it's name - e.g. <code>return SCHED_DODGE_ENEMY_FIRE;</code>.
            return SelectCombatSchedule();
     
      case NPC_STATE_DEAD:
            return SelectDeadSchedule();  


      case NPC_STATE_SCRIPT:
{{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.}}
            return SelectScriptSchedule();


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


==The TranslateSchedule() method==
; <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.


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.
== TranslateSchedule() ==


The following code demonstrates how to translate schedules into child specific schedules.
<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.


<pre>
<source lang=cpp>
int CNPC_Custom::TranslateSchedule( int scheduleType )
int CNPC_Custom::TranslateSchedule( int scheduleType )
{
{
switch( scheduleType )
switch( scheduleType )
{
case SCHED_IDLE_WALK:
{
{
case SCHED_IDLE_WALK:
return SCHED_CUSTOM_IDLE_WALK;
return SCHED_CUSTOM_IDLE_WALK;
break;
break;
}
}
}
return BaseClass::TranslateSchedule( scheduleType );
 
return BaseClass::TranslateSchedule( scheduleType );
}
}
</pre>
</source>
 
==Default Schedule List==
SCHED_NONE
 
SCHED_IDLE_STAND
 
SCHED_IDLE_WALK
 
SCHED_IDLE_WANDER
 
SCHED_WAKE_ANGRY
 
SCHED_ALERT_FACE
 
SCHED_ALERT_FACE_BESTSOUND
 
SCHED_ALERT_SCAN
 
SCHED_ALERT_STAND
 
SCHED_ALERT_WALK
 
SCHED_INVESTIGATE_SOUND
 
SCHED_COMBAT_FACE
 
SCHED_COMBAT_SWEEP
 
SCHED_FEAR_FACE
 
SCHED_COMBAT_STAND
 
SCHED_COMBAT_WALK
 
SCHED_CHASE_ENEMY
 
SCHED_CHASE_ENEMY_FAILED
 
SCHED_VICTORY_DANCE
 
SCHED_TARGET_FACE
 
SCHED_TARGET_CHASE
 
SCHED_SMALL_FLINCH
 
SCHED_BIG_FLINCH
 
SCHED_BACK_AWAY_FROM_ENEMY
 
SCHED_BACK_AWAY_FROM_SAVE_POSITION
 
SCHED_TAKE_COVER_FROM_ENEMY
 
SCHED_TAKE_COVER_FROM_BEST_SOUND
 
SCHED_FLEE_FROM_BEST_SOUND
 
SCHED_TAKE_COVER_FROM_ORIGIN
 
SCHED_FAIL_TAKE_COVER
 
SCHED_RUN_FROM_ENEMY
 
SCHED_RUN_FROM_ENEMY_FALLBACK
 
SCHED_MOVE_TO_WEAPON_RANGE
 
SCHED_ESTABLISH_LINE_OF_FIRE
 
SCHED_ESTABLISH_LINE_OF_FIRE_FALLBACK
 
SCHED_PRE_FAIL_ESTABLISH_LINE_OF_FIRE
 
SCHED_FAIL_ESTABLISH_LINE_OF_FIRE
 
SCHED_SHOOT_ENEMY_COVER
 
SCHED_COWER
 
SCHED_MELEE_ATTACK1
 
SCHED_MELEE_ATTACK2
 
SCHED_RANGE_ATTACK1
 
SCHED_RANGE_ATTACK2
 
SCHED_SPECIAL_ATTACK1
 
SCHED_SPECIAL_ATTACK2
 
SCHED_STANDOFF
 
SCHED_ARM_WEAPON
 
SCHED_DISARM_WEAPON
 
SCHED_HIDE_AND_RELOAD
 
SCHED_RELOAD
 
SCHED_AMBUSH
 
SCHED_DIE
 
SCHED_DIE_RAGDOLL
 
SCHED_WAIT_FOR_SCRIPT
 
SCHED_AISCRIPT
 
SCHED_SCRIPTED_WALK
 
SCHED_SCRIPTED_RUN
 
SCHED_SCRIPTED_CUSTOM_MOVE
 
SCHED_SCRIPTED_WAIT
 
SCHED_SCRIPTED_FACE
 
SCHED_SCENE_GENERIC
 
SCHED_NEW_WEAPON
 
SCHED_NEW_WEAPON_CHEAT
 
SCHED_GET_HEALTHKIT
 
SCHED_WAIT_FOR_SPEAK_FINISH
 
 
SCHED_GIVE_WAY
 
SCHED_MOVE_AWAY
 
SCHED_MOVE_AWAY_FAIL
 
SCHED_MOVE_AWAY_END
 
SCHED_FORCED_GO
 
SCHED_FORCED_GO_RUN
 
SCHED_NPC_FREEZE
 
SCHED_PATROL_WALK
 
SCHED_COMBAT_PATROL
 
SCHED_PATROL_RUN
 
SCHED_RUN_RANDOM
 
SCHED_FALL_TO_GROUND
 
SCHED_DROPSHIP_DUSTOFF
 
 
SCHED_FLINCH_PHYSICS


{{navbar|Creating a condition|Creating an NPC|Creating a task}}


SCHED_FAIL
[[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 );
}