States (Papyrus)
Overview
Scripts operate in varying states - but a script can only be in one state at a time. What code is run when a function is called or an event occurs depends on the state the script is in.
Defining States
Defining a state in a script is very simple, just set up a state block as follows:
state MyState
; Various functions here
endState
If you want your script to start in a particular state, put "auto" in front of the state block:
auto state StartHere
; Functions here
endState
The 'Empty State'
The 'empty state' is the implicit state that every function not inside a state block is in.
function MyFunction()
; This function is in the 'empty state'
endFunction
state MyState
function MyFunction()
; This function is in the MyState state
endFunction
endState
Defining Functions/Events Inside States
To define a function or event inside a state, simply put the function or event inside the state block.
state MyState
function SpecialFunction()
; This function is in the MyState state
endFunction
endState
Please note: the function or event must be defined in the 'empty state' with the same return type and the same parameter types or the compiler will complain at you. This is because of the way the game resolves function and event calls at run-time.
function SampleFunction(int myParameter)
; Code here
endFunction
state SampleState
function SampleFunction(int aParameter)
; This function is fine - the type of parameter is the same as in the 'empty state'
endFunction
function OtherFunction()
; Error! OtherFunction does not exist in the 'empty state'
endFunction
endState
state OtherState
int function SampleFunction(int myParameter)
; Error! SampleFunction return type doesn't match the one in the 'empty state'
endFunction
endState
state YetAnotherState
function SampleFunction(int myParameter, float parameter2)
; Error! SampleFunction parameters don't match the one in the 'empty state'
endFunction
endState
For your convenience, events that various kinds of scripts can receive are already defined for you in the empty state inside the scripts that you extend (i.e. ObjectReference, Actor, etc). So you do not have to define events in the empty state when all you want to do is modify the event in one particular case.
How Functions Are Picked
When someone calls a function, or the object receives an event, the one that is picked is resolved as follows:
- If the script has the function in its current state, call that one
- If the script extends another script that has the function in its current state, call that one
- If the script has the function in the 'empty state', call that one
- If the script extends another script that has the function in the 'empty state', call that one
In short, functions inside states override functions inside the 'empty state', and functions inside derived scripts override the ones in the scripts they extend.
How to Set a Script's State
Setting a script's state is simple, just call GotoState(string asStateName) with the name of the state you want it to go to as a string. The following sequence of events will then take place:
- OnEndState is sent to the script for the state that they were in before.
- The state is switched to the requested state
- OnBeginState is sent to the script for the state it just entered.
The name of the state to enter is not case-sensitive. If you want to go to the 'empty state', simply pass in the empty string: "".
Also - calling GotoState will not end your function - it will still continue to run, so it's perfectly acceptable to just change your object's state during a certain piece of code and to change it back when you're done.
function MyFunction()
GotoState("TurnOffActivate")
; Do a bunch of long-running stuff
GotoState("")
endFunction
state TurnOffActivate
event OnActivate(ObjectReference akTriggerRef)
; Do nothing
endEvent
endState
How to Get a Script's State
Getting a script's state is also simple, call GetState() which will return the state name, as a string, that the script is currently in. Please note that the case of the state name depends on the call that set that state in the first place, and that string compares are case-sensitive, so be careful when doing compares. (In the future we may have ways to compare strings that ignores case to make this easier - for now, be careful)
Tricks and Tips
- If you want to disable an event or function when you are in a particular state, simply define that event or function in the state and leave it empty. This is especially useful if you want to, say, ignore any activate events while you are in the middle of doing something.
- If only having one state at a time is a bit too restricting for you, remember that you can always attach multiple scripts to the same object - and each script can have its own, separate state! This may help you with more complex operations. Of course you can also fall back on just using if statements as well with your own state variables.
- Because state names are simply strings, you can construct them at run-time using the "+" operator. For example, you can define "State1", "State2", and so on, and pick them via the following:
Function GotoNumberedState(int number)
GotoState("State" + number)
EndFunction
Conclusion
States are a powerful and reasonably easy way to modify the behavior of your script without having to write overly large if statements or other complicated code. Sometimes problems that seem overly complex or troublesome may become easier with creative use of states!