States, Modules and Events
BattleScript is an event-driven language. A BattleCore device runs a finite state machine, and your script registers logic that runs in response to events. That logic is organised in three nested layers:
state— a mode the device can be in (for example startup, in-game, dead).module— a source of events within that state (for example the FSM, a trigger input, the IR receiver).event— a specific event from that module (for example entering the state, a button going down, a hit being received).
When an event occurs, the runtime looks for a matching state → module → event handler for the current state. If no handler matches, the event is ignored.
Basic structure
Each layer takes an expression — most commonly an enum member, but a string literal works too. The event handler is a function-like block that receives the event object; by convention it is typed EventData and named evt.
state StateEnum.IN_GAME {
module ModuleEnum.FSM {
event EventEnum.ENTER (EventData evt) => {
std::console.log("Entered the IN_GAME state");
battlecore::screens.setScreen("IN_GAME");
}
}
module ModuleEnum.INPUT_TRIGGER {
event EventEnum.DOWN (EventData evt) => {
if (battlecore::weapon.getCanFire()) {
battlecore::weapon.fire();
}
}
}
}A single state may contain many module blocks, and a single module may contain many event handlers.
The event object
Every handler receives an evt object describing the event that fired. It always has exactly these four fields:
evt.stateName— the state the device was in when the event fired.evt.moduleName— the module that emitted it.evt.eventName— the event name.evt.data— the event-specific payload, for exampleevt.data.playerIdorevt.data.hitValueon an IR hit.
Everything the emitting module sends lives under evt.data — including the state transition details on FSM events, which are evt.data.oldState and evt.data.newState (not evt.oldState). See the Device Events reference for the payload of every event a BattleCore device emits.
EventDatais a placeholder type name meaning "an event payload of some sort". It is not a built-in type today, and the parameter may be omitted entirely when a handler does not need the payload. Per-event strict types are planned for a future release so that handler bodies can be checked against the event they are bound to.
state StateEnum.IN_GAME {
module ModuleEnum.IR {
event EventEnum.IN (EventData evt) => {
// Ignore hits from our own team in a team-based game
if (battlecore::game.getIsTeamBased() == true && evt.data.teamId == battlecore::player.getTeamId()) {
return;
}
if (evt.data.bulletTypeId == 0) {
battlecore::player.takeDamage(evt.data.hitValue);
}
}
}
}The wildcard catch-all
The "_" wildcard matches any state, module or event. It is handy for logging or for fallback behaviour that should run regardless of the current state.
state "_" {
module "_" {
event "_" (EventData evt) => {
std::console.log("Event fired:", evt.stateName, evt.moduleName, evt.eventName);
}
}
}The wildcard can be used at any single layer too — for example a specific state with a module "_" to handle every module within that state.
Changing state
The current state is driven by the device firmware via the battlecore::fsm module. Read it with battlecore::fsm.getState() and change it with battlecore::fsm.setState(...). Changing state causes the FSM to emit EXIT on the old state and ENTER on the new one.
state StateEnum.STARTUP {
module ModuleEnum.FSM {
event EventEnum.ENTER (EventData evt) => {
std::console.log("Startup complete, entering HOME");
battlecore::fsm.setState(StateEnum.HOME);
}
}
}See the battlecore::fsm module for the full state-machine API.
Related Topics
- Enums — define the state, module and event names.
- Timers — schedule work with
afterandeveryinside handlers. - Interfaces — describe the shape of event payloads.
- BattleCore Device API — the modules that emit events and act on them.