Player State Machines

How to manipulate existing PSMs, or create your own.

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 tweaks (all .tweak files in this guide) and Redscript classes.

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

stateMachine.tweak
playerStateMachine {

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

You can use stateMachineList_postPhysics instead if you'd like - only the Crosshair state machine uses this.

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

stateMachine.tweak
playerStateMachine.MyMod {

	string typeName = "MyMod";
	string definitionName = "MyMod";
	bool default = false;
}
  • 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:

stateMachine.tweak
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";
}

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:

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:

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

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.

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:

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()

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()

The transitionCondition can also specify a custom function like "ShouldGo", which will call the state's ShouldGo() function instead of To[stateName]().

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:

And stop it like this:

Last updated