The neo module
API reference for the neo script module, the World class and its members, the on decorator, sleep, all and Python's gather.
neo is the module every script imports its API from. It is the only built-in module in JavaScript; in Python it sits beside NEO's standard modules. The module is frozen: scripts can read it but not change it.
import { World, on, sleep, all } from "neo";from neo import World, on, sleep, gatherFor a guided tour, see The World API.
Summary#
| Member | Kind | Languages | Description |
|---|---|---|---|
| World | class | both | The base class of every script's world class. |
| state | property | both | The script's saved state. |
| emit | method | both | Sends an event to the game. |
| query | method | both | Lists the visible entities of a kind. |
| spawn | method | both | Creates an entity of a spawnable kind. |
| rng | property | both | The world-seeded random stream. |
| time | property | both | Simulation time in seconds. |
| on | decorator | both | Subscribes a method to an event. |
| sleep | function | both | Waits for simulation time to pass. |
| all | function | JavaScript (also importable in Python) | Waits for a list of promises. |
| gather | function | Python | Waits for several awaitables. |
Names are the same in both languages. Fields and state keys that you define are camelCase in JavaScript and snake_case in Python.
World#
export default class MyWorld extends World { ... }class MyWorld(World): ...
default = MyWorldThe base class of a script's world class. Each script has exactly one:
- JavaScript: the class must be the file's
export default class, and it must extendWorld. Loading fails witha script's default export must be a class extending Worldotherwise. - Python: the class named by
default = Name, or, without that line, the last class in the file whose bases includeWorld.
The engine creates one instance when the script loads. Inside methods, this (Python: self) is that instance. A state class field, if present, gives initial state values (see state).
state#
this.state| Type | A world-backed object (JavaScript) / an object with attribute access (Python) |
| Writable | Its keys are; state itself can't be replaced after loading |
The script's state. The world holds it: it survives hot reloads and is included in the script's state hash. Declare initial values as a class field; keys the world doesn't hold yet are set to them at load.
- Reads return values. Lists and maps come back as frozen copies, so assign a new list or map to change one.
- Writes accept numbers, strings, booleans,
null/None, lists, string-keyed maps and entity objects. Each write is one world call. delete this.state.keysets the key tonull.- Key names follow the camelCase/snake_case rule:
this.state.lastOpenerandself.state.last_openerare the same key.
state = { opened: 0, lastOpener: null };
@on("interact", { tag: "Door" })
open(player, door) {
this.state.opened += 1;
this.state.lastOpener = player.id;
}state = {"opened": 0, "last_opener": None}
@on("interact", tag="Door")
def open(self, player, door):
self.state.opened += 1
self.state.last_opener = player.idSee Script state.
emit#
emit(event, payload)| Name | Type | Description |
|---|---|---|
event | string | The event name. |
payload | any world value (optional) | Data sent with the event. Omitted: null. |
Returns: nothing.
Sends an event to the game. The game's Rust systems receive it as a ScriptEmitted message (script, event, payload) after the script runs. It doesn't reach other scripts' handlers. Throws TypeError if the payload holds a function, a symbol or a Python set. Counts as one world call.
this.emit("puzzle_solved", { by: this.state.lastOpener });self.emit("puzzle_solved", {"by": self.state.last_opener})query#
query(kind)| Name | Type | Description |
|---|---|---|
kind | string | The kind name, as declared in Rust ("Platform"). |
Returns: a list of entity objects, in id order. Empty if no entity of that kind exists, or if the kind isn't exposed to the script.
Counts as one world call.
const platform = this.query("Platform")[0];
if (platform) platform.position = { x: 0, y: 1, z: 0 };platforms = self.query("Platform")
if platforms:
platforms[0].position = {"x": 0, "y": 1, "z": 0}spawn#
spawn(kind, fields)| Name | Type | Description |
|---|---|---|
kind | string | A kind the script may spawn. |
fields | map (optional) | Initial values for fields the script may write. Field names follow the camelCase/snake_case rule. |
Returns: the new entity's object. Its id is assigned at once and never reused.
Throws TypeError when the kind isn't spawnable by the script (Cube isn't spawnable by scripts), when a field can't be set by the script, when a number is outside its field's range, or when fields isn't a map. In Python, the first two are PermissionError. The game creates the entity right after the script's turn; if the game has no spawner for the kind, that is recorded as the script's fault. Counts as one world call.
const cube = this.spawn("Cube", { size: 0.5, position: { x: -4, y: 3, z: 0 } });
this.emit("cube_spawned", { id: cube.id });cube = self.spawn("Cube", {"size": 0.5, "position": {"x": -4, "y": 3, "z": 0}})
self.emit("cube_spawned", {"id": cube.id})rng#
this.rng.random()| Member | Returns |
|---|---|
random() | A float in [0, 1). |
The world's random stream for this script, seeded from the world seed and the script's name. In JavaScript, Math.random() draws from the same stream. Every run of the same game draws the same numbers.
if (this.rng.random() < 0.25) this.state.rolls += 1;if self.rng.random() < 0.25:
self.state.rolls += 1time#
this.time| Type | number (Python: float) |
| Writable | No |
Simulation time in seconds. It follows the game's fixed timestep, not the wall clock. A script loaded into a running game starts at the game's current time.
this.emit("round_go", { at: Math.round(this.time * 1000) });self.emit("round_go", {"at": round(self.time * 1000)})Functions#
on#
@on(event, options)@on(event, tag=None, every=None)| Name | Type | Description |
|---|---|---|
event | string | The event to handle. "tick" runs every simulation tick with dt (milliseconds since the last tick). |
tag | string (optional) | A kind name. The handler runs only when the event's second argument is an entity of that kind the script can see. |
every | number (optional) | Seconds. The handler runs at most once per every simulation seconds, counted from its first run. |
Returns: a method decorator.
Decorates an instance method of the world class (not a static method, getter or setter). The method receives the event's arguments. A method can have several @on decorators. Handlers for the same event run in the order the methods appear in the class. Handlers may be async.
@on("touch", { tag: "Platform" })
touched(player, platform) { this.state.touches += 1; }
@on("tick", { every: 2 })
drop() { /* every 2 s */ }@on("touch", tag="Platform")
def touched(self, player, platform):
self.state.touches += 1
@on("tick", every=2)
def drop(self):
pass # every 2 sSee Events and handlers.
sleep#
sleep(seconds)| Name | Type | Description |
|---|---|---|
seconds | number | Simulation seconds to wait. Rounded to whole milliseconds. |
Returns: a promise (Python: an awaitable) that resolves with no value.
Resolves at the first tick whose simulation time is at or past the target, before that tick's tick handlers. Timers due on the same tick resolve by target time, then creation order. sleep(0) resolves on the next tick. A hot reload drops pending sleeps.
@on("round_start")
async start() {
await sleep(1.0);
this.emit("round_go", { at: this.time });
}@on("round_start")
async def start(self):
await sleep(1.0)
self.emit("round_go", {"at": self.time})all#
all(promises)| Name | Type | Description |
|---|---|---|
promises | list of promises or values | What to wait for. |
Returns: a promise of a list with each result, in argument order.
The same as Promise.all: it rejects if any input rejects. Python scripts can import it too, but it hides Python's built-in all; use gather.
const got = await all([sleep(0.05).then(() => "late"), Promise.resolve("now")]);
// ["late", "now"]gather#
gather(*awaitables)| Name | Type | Description |
|---|---|---|
*awaitables | coroutines or awaitables | What to wait for, as separate arguments. |
Returns: an awaitable of a tuple with each result, in argument order.
Python only. Raises if any input raises.
async def after(s, v):
await sleep(s)
return v
got = await gather(after(0.05, "late"), after(0.016, "soon"))
self.state.got = list(got) # ["late", "soon"]Entity objects#
query, spawn and event arguments return entity objects. They have:
| Member | Description |
|---|---|
id | Always readable. A number in JavaScript, an int in Python. |
| Exposed fields | Read and written by name; access is checked on every use. position is a { x, y, z } map. |
| Exposed methods | Called by name. In a running game the call returns null; the Rust method runs right after the script's turn. |
Nothing else exists on an entity object. Denied reads, writes and calls throw (TypeError in JavaScript, PermissionError in Python). See Exposure and permissions.