> For the complete documentation index, see [llms.txt](https://wiki.redmodding.org/cyberpunk-2077-modding/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wiki.redmodding.org/cyberpunk-2077-modding/modding-guides/everything-else/player-state-machines.md).

# Creating Player State Machines

## **Summary**

**Published**: ?? by [Jack Humbert](mailto:undefined)\
**Last documented update**: Aug 8 2026 by [Zhincore](mailto:undefined)

This guide shows how to create and use your own Player State Machine and explains how they work.

## Registering a PSM

A basic state machine (that'll we'll refer to as `MyMod` in this guide) that you can start/stop as needed can be created by using [TweakXL](/cyberpunk-2077-modding/for-mod-creators-theory/core-mods-explained/tweakxl.md) tweaks and [Redscript](https://wiki.redmodding.org/redscript/) classes. You can pick whether you prefer .tweak format (closer to original and REDmod tweaks) or .yaml.

The basic thing needed is your state machine being added to the definition list flats:

{% tabs %}
{% tab title=".tweak" %}
{% code title="stateMachine.tweak" %}

```fsharp
playerStateMachine {

	stateMachineListDefinitions += [ "playerStateMachineDefinitions.MyMod" ];
	stateMachineList_prePhysics += [ "MyMod" ];
}
```

{% endcode %}
{% endtab %}

{% tab title=".yaml" %}
{% code title="stateMachine.yaml" %}

```yaml
playerStateMachine.stateMachineListDefinitions:
	- !append playerStateMachineDefinitions.MyMod
playerStateMachine.stateMachineList_prePhysics:
	- !append MyMod
```

{% endcode %}
{% endtab %}
{% endtabs %}

{% hint style="info" %}
You can use `stateMachineList_postPhysics` instead if you'd like - only the `Crosshair` state machine uses this.
{% endhint %}

You'll also need to create a group like this:

{% tabs %}
{% tab title=".tweak" %}
{% code title="stateMachine.tweak" %}

```fsharp
playerStateMachine.MyMod {

	string typeName = "MyMod";
	string definitionName = "MyMod";
	bool default = false;
}
```

{% endcode %}
{% endtab %}

{% tab title=".yaml" %}
{% code title="stateMachine.yaml" %}

```yaml
playerStateMachine.MyMod.typeName: MyMod
playerStateMachine.MyMod.definitionName: MyMod
playerStateMachine.MyMod.default: False
```

{% endcode %}

{% hint style="info" %}
.yaml tweaks indeed don't have groups. Doing `playerStateMachine.MyMod: { typeName: MyMod }` does NOT work as State Machines use **flats**, not records.
{% endhint %}
{% endtab %}
{% endtabs %}

* `default` will tell the game whether or not to start the state machine automatically - in this case, we want to control when it starts and assign the owner manually, so we'll set it to false.

And the actual definition:

{% tabs %}
{% tab title=".tweak" %}
{% code title="stateMachine.tweak" %}

```fsharp
playerStateMachineDefinitions.MyMod {

	CName stateMachineBodyName = "gamestateMachineStateMachineBody";
	string name = "MyMod";
	string type = "MyMod";
	
	// the prefix used in your state definitions below
	string packageName = "playerStateMachineMyMod";
	
	// a list of all state tweak groups, without the package name
	string[] states = [
		"myModEnabled", 
		"myModDisabled"
	];
	
	string startingState = "myModDisabled";
}
```

{% endcode %}

{% endtab %}

{% tab title=".yaml" %}
{% code title="stateMachine.yaml" overflow="wrap" %}

```yaml
# Note the n"", it is marking the value as CName data type
playerStateMachineDefinitions.MyMod.stateMachineBodyName: n"gamestateMachineStateMachineBody"

playerStateMachineDefinitions.MyMod.name: MyMod
playerStateMachineDefinitions.MyMod.type: MyMod
	
# the prefix used in your state definitions below
playerStateMachineDefinitions.MyMod.packageName: playerStateMachineMyMod
	
# a list of all state tweak groups, without the package name
playerStateMachineDefinitions.MyMod.states:
	- myModEnabled
	-	myModDisabled
	
playerStateMachineDefinitions.MyMod.startingState: myModDisabled
```

{% endcode %}
{% endtab %}
{% endtabs %}

## State Definitions

Finally, you'll need to add each of your states as groups (not a record - just a list of flats) - we'll start with the disabled state, since that's the initial state of the machine:

{% tabs %}
{% tab title=".tweak" %}
{% code title="stateMachine.tweak" %}

```fsharp
playerStateMachineMyMod.myModDisabled {

	CName affinity = "None";
	CName[] alias = [];
	
	// each of these controls whether the function is used
	bool hasEnterCondition = true;
	bool hasExitCondition = false;
	bool hasOnEnter = true;
	bool hasOnExit = false;
	bool hasOnUpdate = false;
	bool hasOnTick = false;
	float tickRate = 0.2;
	
	// these two arrays must match in length
	string[] transitionTo = [ 
		"myModEnabled"
	];
	string[] transitionCondition = [ 
		"=" // uses MyModDisabledDecisions.ToMyModEnabled()
	];
}
```

{% endcode %}

{% endtab %}

{% tab title=".yaml" %}
{% code title="" overflow="wrap" %}

```yaml
playerStateMachineMyMod.myModDisabled.affinity: n"None"
playerStateMachineMyMod.myModDisabled.alias: []
	
# each of these controls whether the function is used
playerStateMachineMyMod.myModDisabled.hasEnterCondition: true
playerStateMachineMyMod.myModDisabled.hasExitCondition: false
playerStateMachineMyMod.myModDisabled.hasOnEnter: true
playerStateMachineMyMod.myModDisabled.hasOnExit: false
playerStateMachineMyMod.myModDisabled.hasOnUpdate: false
playerStateMachineMyMod.myModDisabled.hasOnTick: false
playerStateMachineMyMod.myModDisabled.tickRate: 0.2

# these two arrays must match in length
playerStateMachineMyMod.myModDisabled.transitionTo: [ "myModEnabled" ]
playerStateMachineMyMod.myModDisabled.transitionCondition: [ "=" ]
# uses MyModDisabledDecisions.ToMyModEnabled()
```

{% endcode %}
{% endtab %}
{% endtabs %}

On the Redscript side, you can use two different classes to affect your state, a `Decisions` class, and an `Events` class, both of which are prefixed by your state's name with the first letter capitalized - the same modification is needed for any `To[stateName]()` (e.g. `ToMyModEnabled()`) functions:

{% code title="MyModStateMachine.reds" overflow="wrap" %}

```swift
public class MyModDisabledDecisions extends DefaultTransition {

  // both of these can optionally be defined
  protected func OnAttach(const stateContext: ref<StateContext>, const scriptInterface: ref<StateGameScriptInterface>) -> Void { }
  protected func OnDetach(const stateContext: ref<StateContext>, const scriptInterface: ref<StateGameScriptInterface>) -> Void { }
  
  // this function will only be called when a transitionTo state has a `=` in its condition
  public func ToMyModEnabled(const stateContext: ref<StateContext>, const scriptInterface: ref<StateGameScriptInterface>) -> Bool {
    // implement custom logic here to determine when your machine should enter the myModEnabled state
    return false;
  }
  
  // this will be checked prior to the state being entered
  public func EnterCondition(const stateContext: ref<StateContext>, const scriptInterface: ref<StateGameScriptInterface>) -> Bool {
    return true;
  }
}

public class MyModDisabledEvents extends DefaultTransition {
  // we don't need to have anything here
}
```

{% endcode %}

For the myModEnabled state, we'll have similar definitions:

{% tabs %}
{% tab title=".tweak" %}
{% code title="stateMachine.tweak" %}

```fsharp
playerStateMachineMyMod.myModEnabled {

	CName affinity = "None";
	CName[] alias = [];
	
	// each of these controls whether the function is used
	bool hasEnterCondition = false;
	bool hasExitCondition = false;
	bool hasOnEnter = true;
	bool hasOnExit = true;
	bool hasOnUpdate = true;
	bool hasOnTick = false;
	float tickRate = 0.2;
	
	// these two arrays much match in length
	string[] transitionTo = [ 
		"myModDisabled"
	];
	string[] transitionCondition = [ 
		"" // causes MyModDisabledDecisions.EnterCondition() to run continuously
	];
}
```

{% endcode %}

{% endtab %}

{% tab title=".yaml" %}
{% code title="stateMachine.yaml" overflow="wrap" %}

```yaml
playerStateMachineMyMod.myModEnabled.affinity: n"None"
playerStateMachineMyMod.myModEnabled.alias: []
	
# each of these controls whether the function is used
playerStateMachineMyMod.myModEnabled.hasEnterCondition: false
playerStateMachineMyMod.myModEnabled.hasExitCondition: false
playerStateMachineMyMod.myModEnabled.hasOnEnter: true
playerStateMachineMyMod.myModEnabled.hasOnExit: true
playerStateMachineMyMod.myModEnabled.hasOnUpdate: true
playerStateMachineMyMod.myModEnabled.hasOnTick: false
playerStateMachineMyMod.myModEnabled.tickRate: 0.2

# these two arrays must match in length
playerStateMachineMyMod.myModEnabled.transitionTo: [ "myModDisabled" ]
playerStateMachineMyMod.myModEnabled.transitionCondition: [ "" ]
# "" causes MyModDisabledDecisions.EnterCondition() to run continuously
```

{% endcode %}
{% endtab %}
{% endtabs %}

{% code title="MyModStateMachine.reds" %}

```swift
public class MyModEnabledDecisions extends DefaultTransition {

  // both of these can optionally be defined
  protected func OnAttach(const stateContext: ref<StateContext>, const scriptInterface: ref<StateGameScriptInterface>) -> Void { }
  protected func OnDetach(const stateContext: ref<StateContext>, const scriptInterface: ref<StateGameScriptInterface>) -> Void { }
}

public class MyModEnabledEvents extends DefaultTransition {

  protected func OnEnter(stateContext: ref<StateContext>, scriptInterface: ref<StateGameScriptInterface>) -> Void {
    // do whatever you want here to set up the state
  }

  protected func OnUpdate(timeDelta: Float, stateContext: ref<StateContext>, scriptInterface: ref<StateGameScriptInterface>) -> Void {
    // will run as often as it can
  }
  
  // you can also use an Ontick with a custom tickRate instead
  // protected func OnTick(timeDelta: Float, stateContext: ref<StateContext>, scriptInterface: ref<StateGameScriptInterface>) -> Void { }
  
  protected func OnExit(stateContext: ref<StateContext>, scriptInterface: ref<StateGameScriptInterface>) -> Void {
    // clean-up whatever you did in this state
  }
}
```

{% endcode %}

{% hint style="info" %}
You can define parent classes like `MyModDecisions` (or `MyModTransition`) and `MyModEvents` (or `MyModEventsTransition`), from which the other state classes can be derived. Defining a common `OnEnter` in your `Events` class that prints `NameToString(this.GetStateName())` to the screen/a log can be useful to determine the state of your machine.
{% endhint %}

### How the state definitions work

In the tweak group, `transitionTo` is an array of the state names that it can transition to, and matches up with the `transitionCondition` array, which works a couple different ways:

{% columns %}
{% column %}
`transitionCondition` is `"="`

Call a `To[stateName]()` method prior to attempting to transition - this is how the disabled-to-enabled transition is defined, and will be decided by `MyModDisabledDecisions`'s `ToMyModEnabled()`
{% endcolumn %}

{% column %}
`transitionCondition` is `""`

Immediately try transitioning to the state - this is how the enabled-to-disabled transition is defined, and will skip any `To[stateName]()` calls, and only check `MyModDisabledDecisions`'s `EnterCondition()`
{% endcolumn %}
{% endcolumns %}

{% hint style="info" %}
The `transitionCondition` can also specify a custom function like `"ShouldGo"`, which will call the state's `ShouldGo()` function instead of `To[stateName]()`.
{% endhint %}

Usually state machines prefer one method over the other, but in more complicated machines, a combination can be used.

#### Advanced Options

In your `Decisions` class, you can also use `this.EnableOnEnterCondition([true || false])` to turn on/off the EnterCondition checking of that state, which can reduce execution time for more expensive checks - the game will often use this with listeners on blackboard variables (setup in `OnAttach` & destructed in `OnDeatch`).

The `alias` in the tweak group is a list of names that this state can also be referred to, and can then be used in the `transitionTo` list. This prevents you from having to list many states with the same transition conditions, and is useful for sub-state-machine definitions. See `allVehicleContexts` in the InputContext state machine for examples.

## Starting/Stopping the State Machine

Where you have access to the `player`, via `GetLocalPlayerControlledGameObject()` or some other method, you can start the state machine like this:

{% code title="MyMod.reds" %}

```swift
...      
      let stateMachine = new PSMAddOnDemandStateMachine();
      stateMachine.stateMachineName = n"MyMod";
      // you could also assign objects as the owner, like a vehicle
      // it can be referenced from the transition classes via scriptInterface.owner
      // the player is automatically assigned to scriptInterface.executionOwner
      // stateMachine.owner = vehicle;
      player.QueueEvent(stateMachine);
...
```

{% endcode %}

And stop it like this:

{% code title="MyMod.reds" %}

```swift
...
      let stateMachine = new PSMRemoveOnDemandStateMachine();
      stateMachine.stateMachineIdentifier.definitionName = n"MyMod";
      player.QueueEvent(stateMachine);
...
```

{% endcode %}

## Patching existing State Machines

Most of these principles can be used to add states to existing state machines. You simply need to create the new state, add it to list of states in the PSM definition and then hook it up to other states or replace the starting state.

For example, to add a new weapon state you can do:

{% code title="" overflow="wrap" %}

```yaml
# Add to definition
playerStateMachineDefinitions.Weapon.states:
  - !append myCustomState
  
# Hook up to other states. Remember to update both arrays!
playerStateMachineWeapon.ready.transitionTo:
  - !append myCustomState
playerStateMachineWeapon.ready.transitionCondition:
  - !append ""
  
# Define the state itself as normal
playerStateMachineWeapon.myCustomState.affinity: n"None"
playerStateMachineWeapon.myCustomState.alias: [n"allActionStates"]

playerStateMachineWeapon.myCustomState.stateBodyClassName: ""
playerStateMachineWeapon.myCustomState.hasEnterCondition: true
playerStateMachineWeapon.myCustomState.hasOnEnter: true
playerStateMachineWeapon.myCustomState.hasOnUpdate: true
playerStateMachineWeapon.myCustomState.hasOnTick: false
playerStateMachineWeapon.myCustomState.hasOnExit: true
playerStateMachineWeapon.myCustomState.hasExitCondition: false
playerStateMachineWeapon.myCustomState.tickRate: 0.2
playerStateMachineWeapon.myCustomState.transitionTo:
  ["allNotReadyStates", "ready"]
playerStateMachineWeapon.myCustomState.transitionCondition: ["", "="]
```

{% endcode %}

On the Redscript side it works the same as custom PSM, simply define the Decisions and Events classes (here it would be `MyCustomStateDecisions` , etc.).

Look into vanilla game's `weaponTransition.script` or other for your wanted PSM, for inspiration and proper hooking up.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://wiki.redmodding.org/cyberpunk-2077-modding/modding-guides/everything-else/player-state-machines.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
