Difference between revisions of "State machine"
Eric Walch (talk | contribs) (Added examples) |
Eric Walch (talk | contribs) m (→Examples) |
||
Line 39: | Line 39: | ||
}; |
}; |
||
− | + | The first state waits only for the message "ATTACKED". If it receives it, it executes the first command: "setStateTo: WARN_PLAYER". For this it sets the pointers to the second state and executes its ENTER message, the commsMessage: "Help, don't shoot". |
|
But the original commandline is not finished and so the second command is executed: "setStateTo: IDLE". Now the pointers are set to the third state and his ENTER message is executed. But this one only sets the next update time with a pause of 10 seconds. Now all commands are finished and control is given back to the game. On the next update time the statemachine is set to the third state and that UPDATE message is executed every 10 seconds. When is receives an attack in this state it executes his EXIT commands and returns to the first state. |
But the original commandline is not finished and so the second command is executed: "setStateTo: IDLE". Now the pointers are set to the third state and his ENTER message is executed. But this one only sets the next update time with a pause of 10 seconds. Now all commands are finished and control is given back to the game. On the next update time the statemachine is set to the third state and that UPDATE message is executed every 10 seconds. When is receives an attack in this state it executes his EXIT commands and returns to the first state. |
||
− | Of course this is a nonsense script but clearly shows how the machine jumps from state to state. It always finishes all pending messages in the original state, even when there are "setStateTo:" commands inside. |
+ | Of course this is a nonsense script but clearly shows how the machine jumps from state to state. It always finishes all pending messages in the original state, even when there are "setStateTo:" commands inside. When there are more jumps in one line, or more messages contain a "setStateTo:" command, all the enter messages are still executed, but it ends in the last defined state. |
The only exeptions are the commands setAITo: or switchAITo:. In this case the whole statemachine is put on hold and a new one is started. |
The only exeptions are the commands setAITo: or switchAITo:. In this case the whole statemachine is put on hold and a new one is started. |
Revision as of 12:06, 28 October 2007
Contents
The State Machine
The AI system consists of a 'stack' of state machines (or AI's).
The top AI of this stack is the active AI. This state machine has the structure of a plist file. The main level is an array of states. Each of this states is an array of messages.
States
On creation of an entity the system assigns a state machine (= AI) to every entity. It looks in the shipdata.plist file for an entry with name "ai_type". If none is defined it defaults to nullAI.plist. Then it starts the state with name "GLOBAL". From hereon the machine is on its own and in most cases there is an instruction in the GLOBAL state to jump to an state with a more descriptive name.
Default Messages
There are three messages that must always be present in a state. ENTER, UPDATE, EXIT. When the system enters a new state it first looks for a message with name ENTER. Then it will execute all the commands in this state. At the same time the update timer is set to its default value of 0.125 seconds.
After the update time is finished it looks for an entry with the name UPDATE. This one then will be executed. This means that without defined pauses, the update will be executed 8 times every second. Whenever a "pauseAI:" is executed, this time is added to the latest update time. So pause will not postpone the current command line but has only effect on the next update time.
When there is an instruction to go to another state it first executes the state with name EXIT.
Normal Messages
Some commands return messages to the state machine like "TARGET_FOUND" or TARGET_LOST. These messages are put on a stack with a maximum size of 32 entries. Whenever an update is executed, the messages in this stack are compared against message entries in the currently active state. If it finds one, the commands in this message line are executed. Take note that normal messages are only examined after the UPDATE commands are executed and not after execution of the ENTER commands.
To be more precise: After execution of the UPDATE commands, all received message are copied to a new execution list and the current message stack is cleared. Then all the commands that correspond to received messages are executed, and the new messages these commands may generate are not examined until the next update time.
Priority Messages
Some messages can't wait until the next update. Good examples are commands were the entity is removed from the universe like "performWitchspaceExit". When it jumps it returns "WITCHSPACE_OK". When it had to wait till the next update the entity could already be removed from the universe together with its state machine. But when the entity has escorts these must also get a command to follow. For these reason some messages are prioritized and will be executed immediately when there is a corresponding entry.
Examples
Best thing to understand how it works is to look at an unusual example with three states that only shows some messages:
WAIT_FOR_SHOT = { ENTER = (); EXIT = (); ATTACKED = ("setStateTo: WARN_PLAYER", "setStateTo: IDLE"); UPDATE = (); }; WARN_PLAYER = { ENTER = ("commsMessage: Help, don't shoot"); EXIT = (); UPDATE = (); }; IDLE = { ENTER = ("pauseAI: 10"); EXIT = ("commsMessage: Resetting things"); ATTACKED = ("setStateTo: WAIT_FOR_SHOT"); UPDATE = ("commsMessage: Nice not shooting at me", "pauseAI: 10"); };
The first state waits only for the message "ATTACKED". If it receives it, it executes the first command: "setStateTo: WARN_PLAYER". For this it sets the pointers to the second state and executes its ENTER message, the commsMessage: "Help, don't shoot".
But the original commandline is not finished and so the second command is executed: "setStateTo: IDLE". Now the pointers are set to the third state and his ENTER message is executed. But this one only sets the next update time with a pause of 10 seconds. Now all commands are finished and control is given back to the game. On the next update time the statemachine is set to the third state and that UPDATE message is executed every 10 seconds. When is receives an attack in this state it executes his EXIT commands and returns to the first state.
Of course this is a nonsense script but clearly shows how the machine jumps from state to state. It always finishes all pending messages in the original state, even when there are "setStateTo:" commands inside. When there are more jumps in one line, or more messages contain a "setStateTo:" command, all the enter messages are still executed, but it ends in the last defined state.
The only exeptions are the commands setAITo: or switchAITo:. In this case the whole statemachine is put on hold and a new one is started.