Skip to main content

State machines

When an NPC needs to do more than one thing, amateur scripts quickly devolve into a tangle of ten boolean flags: isAngry, hasSeenPlayer, isReturning, isReloading. Soon these flags contradict each other, and the ped stutters between walking, aiming, and running away.

The antidote is a Finite State Machine (FSM). The rule is simple: an entity exists in exactly one state at any given moment.

The four states of an encounter

In Project 05, you will build a gang encounter with four clear states:

[ Calm ] <------------------------+
| |
| (player approaches) | (player retreats)
v |
[ Alert ] |
| |
| (weapon drawn or gets close) |
v |
[ Combat ] -----------------> [ Returning ]
  1. Calm: Pedestrians stand guard or chat using idle scenarios.
  2. Alert: Player gets close. Peds turn toward the player with suspicious postures.
  3. Combat: Guns drawn, firing and taking cover against the player.
  4. Returning: Player runs away. Peds cease fire, lower weapons, and resume their guard post.

Modeling states in C#

Represent states using an enum:

public enum EncounterState
{
Calm,
Alert,
Combat,
Returning
}

The state transition method

Never change state by directly overwriting a variable across twenty different places in your file. Use a single transition method so entry and exit actions always execute:

private EncounterState _state = EncounterState.Calm;

private void TransitionTo(EncounterState nextState, Ped actor, Ped player)
{
if (_state == nextState)
{
return;
}

// Actions taken on EXITING the old state
switch (_state)
{
case EncounterState.Combat:
actor.Task.ClearAll(); // Cease fire immediately
break;
}

_state = nextState;

// Actions taken on ENTERING the new state
switch (_state)
{
case EncounterState.Calm:
actor.Task.StartScenarioInPlace("WORLD_HUMAN_GUARD_STAND", 0, false);
break;

case EncounterState.Alert:
actor.Task.LookAt(player, 5000);
GTA.UI.Notification.PostTicker("Gang member is watching you closely.", false);
break;

case EncounterState.Combat:
actor.Task.Combat(player, TaskCombatFlags.None, TaskThreatResponseFlags.None);
break;

case EncounterState.Returning:
actor.Task.ClearAll();
actor.Task.StartScenarioInPlace("WORLD_HUMAN_GUARD_STAND", 0, false);
_state = EncounterState.Calm; // Return complete
break;
}
}

The power of state machines

Notice the immediate benefits:

  • The ped cannot be in Calm and Combat simultaneously. Contradictory behaviors become structurally impossible.
  • When combat ends, Task.ClearAll() is guaranteed to execute during the exit from Combat.
  • Adding a fifth state (like Fleeing when health is low) only requires adding one enum entry and two switch cases.

Next, we look at separating game logic from engine calls, which makes testing your rules outside the game possible.