Condition: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
No edit summary
Line 1: Line 1:
[[Category:AI Programming]]
{{template:toc-right}}
==Overview==
 
Conditions are key pieces of information generated by the [[NPC Sensing]] system. Conditions reflect the state of the world and the NPC, and they're used by the NPC to assess whether the NPC's current action is a good one, and then to choose a course of action if necessary.
'''Conditions''' are key pieces of information, generated afresh by the [[NPC Sensing]] system every time <code>[[NPCThink()]]</code> runs, about the state of the world and the NPC. They are primarily used to select or interrupt [[schedule]]s.
 
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, [[npc_antlion|antlions]] drown when in water, so they have a condition of their own that tells them ''"I am underwater"''.
 
{{tip|You can see a list of all conditions by searching in Visual Studio's 'Class View'.}}
 
{{note|By default there is a maximum of 256 conditions in any one game or mod. You can increase this number in <code>ai_condition.h</code>, 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 interrupt it if set. Whenever any one of them is found, the schedule is cancelled and another 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.


Conditions abstract away complex information about the world into simple concepts. Some examples of conditions are: ''"I can see an enemy"'', ''"I have taken some damage"'', or ''"My weapon's clip is empty"''. The AI system will generate a variety of base conditions for any NPC, and specific NPC classes can then specify and control their own custom conditions to deal with things unique to their own behavior. For example, antlions drown when in water, so they have a custom condition that tells them ''"I am underwater"''.
== Adding new conditions ==


Conditions validate an NPC's current course of action by acting as interrupts. Understanding this requires some knowledge of [[Schedules]]. Each schedule has an associated list of conditions that will act as interrupts for that schedule. If an interrupt condition is set for an NPC's current schedule, that schedule will be thrown away and the NPC will be forced to select a new 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.
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:


==Adding Custom Conditions==
Conditions are enumerated inside the NPC class. For example, if our new NPC has a custom condition to reflect ''"I'm hungry"'', our condition enum should look something like this:
  enum  
  enum  
  {
  {
Line 14: Line 30:
  NEXT_CONDITION
  NEXT_CONDITION
  };
  };
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> 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> to declare their custom conditions without causing collisions with ours.


Conditions must then be declared inside the <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:
Conditions must then be declared inside the <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:
  DECLARE_CONDITION( COND_MYNPC_HUNGRY )
 
  DECLARE_CONDITION( COND_MYNPC_HUNGRY );


==Condition Handling==
==Condition Handling==
AI_BaseNPC stores conditions in the m_Conditions bitstring, but it's rarely necessary to access it directly. Instead, condition handling inside an NPC class is usually done through the following functions:
*<code>void GatherConditions( void )</code> : The primary entry point for condition generation. The AI_BaseNPC::GatherConditions() function will handle entity sensing, so make sure you call back to the base class.
*<code>void SetCondition( int iCondition )</code> : Sets a condition on.
*<code>void ClearCondition( int iCondition )</code> : Clears a condition off.
*<code>bool HasCondition( int iCondition )</code> : Returns true if the specified condition has been set on.
*<code>void BuildScheduleTestBits( void )</code> : Used to modify the list of interrupts for the current schedule. Schedules define their own static list of interrupts. This function allows you to dynamically modify that list based upon the current state of the NPC & world. It's also useful for appending your custom NPC conditions to base NPC shared schedules.


==NPC Code Examples==
<code>[[CAI_BaseNPC]]</code> stores conditions in the <code>m_Conditions</code> [[bitstring]], but it's rarely necessary to access it directly. Instead, condition handling inside an NPC class is done with these functions:
'''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.
;<code>void GatherConditions()</code>
  void CNPC_Antlion::GatherConditions( void )
:The primary entry point for condition generation, which is called every time the NPC thinks. Make sure you call <code>BaseClass::GatherConditions()</code> within it!
;<code>void SetCondition( [[int]] iCondition )</code>
;<code>void ClearCondition( int iCondition )</code>
:Sets/clears a condition. Instead of entering a number, you'll of course make use of your enum.
;<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.
;<code>void 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.
 
== Examples ==
 
===GatherConditions()===
 
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.
 
  void CNPC_Antlion::GatherConditions()
  {
  {
  BaseClass::GatherConditions();
  BaseClass::GatherConditions();
Line 37: Line 63:
  // See if I've landed on another NPC after jumping.
  // See if I've landed on another NPC after jumping.
  CBaseEntity *pGroundEnt = GetGroundEntity();
  CBaseEntity *pGroundEnt = GetGroundEntity();
  if ( ( ( pGroundEnt != NULL ) && ( pGroundEnt->GetSolidFlags() & FSOLID_NOT_STANDABLE ) ) && GetFlags() & FL_ONGROUND ) )
  if ( pGroundEnt && pGroundEnt->GetSolidFlags() & FSOLID_NOT_STANDABLE && GetFlags() & FL_ONGROUND )
{
  SetCondition( COND_ANTLION_ON_NPC );
  SetCondition( COND_ANTLION_ON_NPC );
}
  else
  else
{
  ClearCondition( COND_ANTLION_ON_NPC );
  ClearCondition( COND_ANTLION_ON_NPC );
        }
   
   
  // See if I've landed in water
  // See if I've landed in water
  if( m_lifeState == LIFE_ALIVE && GetWaterLevel() > 1 )
  if( m_lifeState == LIFE_ALIVE && GetWaterLevel() > 1 )
{
  SetCondition( COND_ANTLION_IN_WATER );
  SetCondition( COND_ANTLION_IN_WATER );
}
  }
  }


'''BuildScheduleTestBits()'''
===BuildScheduleTestBits()===
 
The [[Assaults|Assault behavior]] wants to only use <code>COND_NEW_ENEMY</code> and <code>COND_SEE_ENEMY</code> as interrupts on the three specified schedules if the current assault point allows the NPC to divert from the assault. 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 [[Behavior_Assault|Assault Behavior]] wants to only use COND_NEW_ENEMY and COND_SEE_ENEMY as interrupts on the three specified schedules if the current assault point allows the NPC to divert from the assault. 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()
  void CAI_AssaultBehavior::BuildScheduleTestBits()
  {
  {
Line 74: Line 95:


==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]]

Revision as of 07:38, 31 May 2008

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".

Tip.pngTip:You can see a list of all conditions by searching in Visual Studio's 'Class View'.
Note.pngNote:By default there is a maximum of 256 conditions in any one game or mod. You can increase this number in 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 interrupt it if set. Whenever any one of them is found, the schedule is cancelled and another 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 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 to declare their custom conditions without causing collisions with ours.

Conditions must then be declared inside the AI_BEGIN_CUSTOM_NPC block. This is done through the DECLARE_CONDITION macro. For the above example, we'd use this line:

DECLARE_CONDITION( COND_MYNPC_HUNGRY );

Condition Handling

CAI_BaseNPC stores conditions in the m_Conditions bitstring, but it's rarely necessary to access it directly. Instead, condition handling inside an NPC class is done with these functions:

void GatherConditions()
The primary entry point for condition generation, which is called every time the NPC thinks. Make sure you call BaseClass::GatherConditions() within it!
void SetCondition( int iCondition )
void ClearCondition( int iCondition )
Sets/clears a condition. Instead of entering a number, 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.
void 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.

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

BuildScheduleTestBits()

The Assault behavior wants to only use COND_NEW_ENEMY and COND_SEE_ENEMY as interrupts on the three specified schedules if the current assault point allows the NPC to divert from the assault. 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