Condition: Difference between revisions
m (Robot: fixing template case.) |
No edit summary |
||
Line 17: | Line 17: | ||
== Conditions and schedules == | == Conditions and schedules == | ||
As well as being used when selecting a new [[schedule]], conditions validate the current one by acting as interrupts. Each schedule has an associated list of conditions that will | As well as being used when selecting a new [[schedule]], conditions validate the current one by acting as interrupts. Each schedule has an associated list of conditions that will cause it to exit if detected. When this happens, a new schedule is chosen. | ||
For example, an NPC may be running a schedule to ''"Chase my enemy"''. This kind of schedule usually specifies the ''"I can see a new enemy"'' condition as an interrupt, because the NPC shouldn't keep chasing the old enemy if it has found a a newer, more important one. | For example, an NPC may be running a schedule to ''"Chase my enemy"''. This kind of schedule usually specifies the ''"I can see a new enemy"'' condition as an interrupt, because the NPC shouldn't keep chasing the old enemy if it has found a a newer, more important one. | ||
Line 23: | Line 23: | ||
== Adding new conditions == | == Adding new conditions == | ||
Conditions are enumerated inside the NPC class. For example, if our new NPC has a custom condition to reflect ''"I am hungry"'', our condition enum should look something like this: | Conditions are normally enumerated inside the NPC class. For example, if our new NPC has a custom condition to reflect ''"I am hungry"'', our condition enum should look something like this: | ||
enum | enum | ||
Line 33: | Line 33: | ||
It's good practice to include the <code>NEXT_CONDITION</code> enum, so that NPCs derived from our NPC can use <code>BaseClass::NEXT_CONDITION</code> (as we do, on the first line) to declare their custom conditions without causing collisions with ours. | It's good practice to include the <code>NEXT_CONDITION</code> enum, so that NPCs derived from our NPC can use <code>BaseClass::NEXT_CONDITION</code> (as we do, on the first line) to declare their custom conditions without causing collisions with ours. | ||
{{warning|If your condition enum ''isn't'' inside your class definition you ''must'' choose a different name for your "next condition" item.}} | |||
Conditions must then be declared inside the NPC's <code>AI_BEGIN_CUSTOM_NPC</code> block. This is done through the <code>DECLARE_CONDITION</code> macro. For the above example, we'd use this line: | |||
<source lang=cpp> | |||
AI_BEGIN_CUSTOM_NPC( npc_custom, CNPC_Custom ) | |||
DECLARE_CONDITION( COND_MYNPC_HUNGRY ) | |||
AI_END_CUSTOM_SCHEDULE_PROVIDER() | |||
</source> | |||
== Condition functions == | == Condition functions == | ||
<code>[[CAI_BaseNPC]]</code> stores conditions in | <code>[[CAI_BaseNPC]]</code> stores conditions as [[flag]]s in <code>m_Conditions</code>, but it should not be accessed directly. Instead, condition handling is done with these functions: | ||
;<code> | ; <code>GatherConditions()</code> | ||
:The primary entry point for condition generation, which is called every time the NPC thinks. Make sure you call <code>BaseClass::GatherConditions()</code> | : The primary entry point for condition generation, which is called every time the NPC thinks. {{note|Make sure you call <code>BaseClass::GatherConditions()</code> at the end!}} | ||
;<code> | ;<code>SetCondition( [[int]] iCondition )</code> | ||
;<code> | ;<code>ClearCondition( [[int]] iCondition )</code> | ||
:Sets/clears a condition. Instead of passing an integer, you'll of course make use of your enum. | :Sets/clears a condition. Instead of passing an integer, you'll of course make use of your enum. | ||
;<code>[[bool]] HasCondition( int iCondition )</code> | ;<code>[[bool]] HasCondition( [[int]] iCondition )</code> | ||
:True if the specified condition is currently set. Remember that conditions are thrown away and re-generated on every think. | :True if the specified condition is currently set. Remember that conditions are thrown away and re-generated on every think. | ||
;<code> | ;<code>BuildScheduleTestBits()</code> | ||
:This function allows you to dynamically modify a schedule's interrupts at any time. It's useful for appending your custom NPC conditions to base NPC or shared schedules. | :This function allows you to dynamically modify a schedule's interrupts at any time. It's useful for appending your custom NPC conditions to base NPC or shared schedules. See below for an example. | ||
== Examples == | == Examples == | ||
Line 57: | Line 63: | ||
Here's a slightly trimmed version of the [[npc_antlion|Antlion]]'s <code>GatherConditions()</code>. Antlions jump around a lot, and sometimes land on top of other NPCs. They need to know whether they've landed on an NPC when decision making, so they generate a condition when they have. Antlions also need to drown if they ever find themselves in water, so they set a condition when their water level reaches waist high. | Here's a slightly trimmed version of the [[npc_antlion|Antlion]]'s <code>GatherConditions()</code>. Antlions jump around a lot, and sometimes land on top of other NPCs. They need to know whether they've landed on an NPC when decision making, so they generate a condition when they have. Antlions also need to drown if they ever find themselves in water, so they set a condition when their water level reaches waist high. | ||
<source lang=cpp> | |||
void CNPC_Antlion::GatherConditions() | |||
{ | |||
BaseClass::GatherConditions(); | |||
// See if I've landed on another NPC after jumping. | |||
CBaseEntity *pGroundEnt = GetGroundEntity(); | |||
if ( pGroundEnt && pGroundEnt->GetSolidFlags() & FSOLID_NOT_STANDABLE && GetFlags() & FL_ONGROUND ) | |||
SetCondition( COND_ANTLION_ON_NPC ); | |||
else | |||
ClearCondition( COND_ANTLION_ON_NPC ); | |||
// See if I've landed in water | |||
if( m_lifeState == LIFE_ALIVE && GetWaterLevel() > 1 ) | |||
SetCondition( COND_ANTLION_IN_WATER ); | |||
} | |||
</source> | |||
{{ | {{todo|Why is <code>ClearCondition()</code> used here?}} | ||
===BuildScheduleTestBits()=== | ===BuildScheduleTestBits()=== | ||
Line 79: | Line 87: | ||
The [[Assaults|Assault behavior]] allows mappers to specify whether an NPC should divert from their path to fight new enemies or keep running. This kind of dynamic interrupt specification can't be done in the static schedule definitions, and is exactly what <code>BuildScheduleTestBits()</code> is designed for. | The [[Assaults|Assault behavior]] allows mappers to specify whether an NPC should divert from their path to fight new enemies or keep running. This kind of dynamic interrupt specification can't be done in the static schedule definitions, and is exactly what <code>BuildScheduleTestBits()</code> is designed for. | ||
<source lang=cpp> | |||
void CAI_AssaultBehavior::BuildScheduleTestBits() | |||
{ | |||
BaseClass::BuildScheduleTestBits(); | |||
// If we're allowed to divert, add the appropriate interrupts to our movement schedules | |||
if ( m_hAssaultPoint && m_hAssaultPoint->m_bAllowDiversion ) | |||
{ | |||
if ( IsCurSchedule( SCHED_MOVE_TO_ASSAULT_POINT ) || | |||
IsCurSchedule( SCHED_MOVE_TO_RALLY_POINT ) || | |||
IsCurSchedule( SCHED_HOLD_RALLY_POINT ) ) | |||
{ | |||
GetOuter()->SetCustomInterruptCondition( COND_NEW_ENEMY ); | |||
GetOuter()->SetCustomInterruptCondition( COND_SEE_ENEMY ); | |||
} | |||
} | |||
} | |||
</source> | |||
==See Also== | == See Also == | ||
* [[Shared conditions]] - a list of conditions inherited by all NPCs | * [[Shared conditions]] - a list of conditions inherited by all NPCs | ||
[[Category:AI Programming]] | [[Category:AI Programming]] |
Revision as of 09:24, 15 September 2011
Conditions are key pieces of information, generated afresh by the NPC Sensing system every time NPCThink()
runs, about the state of the world and the NPC. They are primarily used to select or interrupt schedules.
Some examples of conditions are:
- "I can see an enemy"
- "I have taken some damage"
- "My weapon's clip is empty"
The automatically-generated conditions above are usually supplemented by others specific to an NPC. For example, antlions drown when in water, so they have a condition of their own that tells them "I am underwater".


ai_condition.h
, but doing so will break older saved games and increase memory usage.Conditions and schedules
As well as being used when selecting a new schedule, conditions validate the current one by acting as interrupts. Each schedule has an associated list of conditions that will cause it to exit if detected. When this happens, a new schedule is chosen.
For example, an NPC may be running a schedule to "Chase my enemy". This kind of schedule usually specifies the "I can see a new enemy" condition as an interrupt, because the NPC shouldn't keep chasing the old enemy if it has found a a newer, more important one.
Adding new conditions
Conditions are normally enumerated inside the NPC class. For example, if our new NPC has a custom condition to reflect "I am hungry", our condition enum should look something like this:
enum { COND_MYNPC_HUNGRY = BaseClass::NEXT_CONDITION, NEXT_CONDITION };
It's good practice to include the NEXT_CONDITION
enum, so that NPCs derived from our NPC can use BaseClass::NEXT_CONDITION
(as we do, on the first line) to declare their custom conditions without causing collisions with ours.

Conditions must then be declared inside the NPC's AI_BEGIN_CUSTOM_NPC
block. This is done through the DECLARE_CONDITION
macro. For the above example, we'd use this line:
AI_BEGIN_CUSTOM_NPC( npc_custom, CNPC_Custom )
DECLARE_CONDITION( COND_MYNPC_HUNGRY )
AI_END_CUSTOM_SCHEDULE_PROVIDER()
Condition functions
CAI_BaseNPC
stores conditions as flags in m_Conditions
, but it should not be accessed directly. Instead, condition handling is done with these functions:
GatherConditions()
- The primary entry point for condition generation, which is called every time the NPC thinks.
Note:Make sure you call
BaseClass::GatherConditions()
at the end! SetCondition( int iCondition )
ClearCondition( int iCondition )
- Sets/clears a condition. Instead of passing an integer, you'll of course make use of your enum.
bool HasCondition( int iCondition )
- True if the specified condition is currently set. Remember that conditions are thrown away and re-generated on every think.
BuildScheduleTestBits()
- This function allows you to dynamically modify a schedule's interrupts at any time. It's useful for appending your custom NPC conditions to base NPC or shared schedules. See below for an example.
Examples
GatherConditions()
Here's a slightly trimmed version of the Antlion's GatherConditions()
. Antlions jump around a lot, and sometimes land on top of other NPCs. They need to know whether they've landed on an NPC when decision making, so they generate a condition when they have. Antlions also need to drown if they ever find themselves in water, so they set a condition when their water level reaches waist high.
void CNPC_Antlion::GatherConditions()
{
BaseClass::GatherConditions();
// See if I've landed on another NPC after jumping.
CBaseEntity *pGroundEnt = GetGroundEntity();
if ( pGroundEnt && pGroundEnt->GetSolidFlags() & FSOLID_NOT_STANDABLE && GetFlags() & FL_ONGROUND )
SetCondition( COND_ANTLION_ON_NPC );
else
ClearCondition( COND_ANTLION_ON_NPC );
// See if I've landed in water
if( m_lifeState == LIFE_ALIVE && GetWaterLevel() > 1 )
SetCondition( COND_ANTLION_IN_WATER );
}
ClearCondition()
used here?BuildScheduleTestBits()
The Assault behavior allows mappers to specify whether an NPC should divert from their path to fight new enemies or keep running. This kind of dynamic interrupt specification can't be done in the static schedule definitions, and is exactly what BuildScheduleTestBits()
is designed for.
void CAI_AssaultBehavior::BuildScheduleTestBits()
{
BaseClass::BuildScheduleTestBits();
// If we're allowed to divert, add the appropriate interrupts to our movement schedules
if ( m_hAssaultPoint && m_hAssaultPoint->m_bAllowDiversion )
{
if ( IsCurSchedule( SCHED_MOVE_TO_ASSAULT_POINT ) ||
IsCurSchedule( SCHED_MOVE_TO_RALLY_POINT ) ||
IsCurSchedule( SCHED_HOLD_RALLY_POINT ) )
{
GetOuter()->SetCustomInterruptCondition( COND_NEW_ENEMY );
GetOuter()->SetCustomInterruptCondition( COND_SEE_ENEMY );
}
}
}
See Also
- Shared conditions - a list of conditions inherited by all NPCs