An AI's Mind is in a certain State all the time. It's the State class which does the actual thinking each frame, it pushes the Tasks to the Subsystems, it interprets the incoming stimuli, it decides which States the Mind will be switch to.
Note: States are the only instances which are allowed to actually switch the Mind's State (Tasks are not allowed to do it, nor is the Mind or the AI class itself allowed to). Also, States are the only instances which are allowed to delegate Tasks into the Subsystems.
States are allowed to Think() once per frame. This method is invoked by the Mind and gives them an opportunity to monitor their Tasks and to switch to other States.
There are a few so-called Backbone States, corresponding to the AI's alert indices:
- Alert Index 0 => IdleState (which is replaced by AlertIdleState when the AI has seen evidence of intruders)
- Alert Index 1 => ObservantState
- Alert Index 2 => SuspiciousState
- Alert Index 3 => SearchingState
- Alert Index 4 => AgitatedSearchingState
- Alert Index 5 => CombatState (only allowed if Enemy is known)
The Backbone States form some kind of "ladder" and follow this pattern:
- When the AI's alert level exceeds a given threshold (e.g. from thresh_4 to thresh_5), the Backbone State switches to the next higher State (e.g. from AgitatedSearchingState to CombatState). This happens via a PushState command, so that the AgitatedSearchingState still stays within the Mind's StateQueue.
- When the AI's alert level falls below the threshold for the current State, it is terminated via an EndState call.
These two rules ensure that the Mind is always in the correct State according to the AI's alert index. Consequently, you don't need to actually switch to a higher Backbone State (in fact you're not allowed to), this is accomplished by calling SetAlertLevel instead. The States monitor the alert level changes on their own and automatically switch to the correct State.
Example: Let's assume the AI is in SearchingState and encounters the player (visually). The alert index is increased immediately above thresh_5 which causes the SearchingState (in its role as Backbone State) to Push the next higher State (in this case this is AgitatedSearchingState). The AgitatedSearchingState peforms another check and as the alertlevel is higher than thresh_5 it passes the torch and pushes the CombatState into the StateQueue. Let's further assume that the player manages to escape and the AI's alert level is slowly decreasing again. As soon as it falls below thresh_5, the CombatState recognises itself not being appropriate anymore and calls EndState(). This lets the Mind fall back into the AgitatedSearchingState (which is still in the Mind's StateQueue). A further decrease of the Alert Level causes the AgitatedSearchingState to end itself and to fall back to SearchingState. This goes on until the bottom of the StateQueue is reached, which is always occupied by the IdleState.
The important part is that the Backbone States always use PushState to go upwards and EndState to go downwards.
It's of course not a problem, if the CombatState decides to switch to some other State like FleeState or LostTrackOfEnemyState, as long as the BackBone stays intact. It's ok for the CombatState to use either SwitchState, PushState to jump to (say) FleeState. The important thing is that the FleeState must not directly push another Backbone State (like IdleState), as this would disrupt the StateQueue. If the FleeState wants to go back into IdleState, it's enough to set the AI alert level and call EndState. The BackBone States will take care of switching by subsequently calling EndState until the corresponding State is reached.
A State must...
- ...have a unique name string identifier (depicted in the header, e.g. STATE_COMBAT).
- ...implement the abstract State interface
- ...provide a static CreateInstance() method.
- ...register itself with the StateLibrary using a StateRegistrar helper class.
- ...define a default constructor without arguments
- ...be save-able and restore-able via the Save/Restore methods it has to implement (no reference/raw pointer members, etc., see Saving and Loading)
Optional: A State can...
- ...implement the method OnSubsystemTaskFinished to get notified about finished Subsystem Tasks.
- ...override one of the various OnVisualStimX() methods to implement behaviour different to the default.
- ...override the OnAICommMessage() to handle the incoming messages differently than the base class.
The Backbone States override the default implementation bool State::CheckAlertLevel() and implement a check using a required alert index and the name of the higher State. If the alert index is not matching, it switches to the state below or above. For switching to the State "below" (e.g. from AgitatedSearching to Searching) it just takes a call to EndState() whereas for switching to the higher state, the actual name is needed (AgitatedSearchingState would pass the STATE_COMBAT string constant to the method).
This method is checked each time the Backbone State is about to think or initialise, so setting the alert level/index almost instantly triggers a switch to the right State.
The call to CheckAlertLevel() can also be used by other, non-backbone States, where appropriate.