Skip to main content
Everything on this page is available in every script without any import. These are the building blocks every script starts with: scheduling work, reacting to game events, and reading per-frame state.

Overview

Timers

Schedule one-shot or repeating callbacks with setTimeout and setInterval.

console

Log, warn, error, and assert output to the script console.

Events

addEventListener for ticks, input, and engine events.

script

Metadata about the running script - name, version, author.

Timers

Standard browser-style timer API. IDs returned by setTimeout and setInterval are independent - pass the right ID to the right clear function.

setTimeout


Calls callback once after ms milliseconds. Returns a timer ID.

setInterval


Calls callback repeatedly every ms milliseconds. Returns a timer ID.

clearTimeout / clearInterval


Cancels a pending timer by its ID. Safe to call with an invalid or already-fired ID.
Cancel intervals you no longer need. They keep firing even if the entities or objects they reference have become invalid, and an interval that runs heavy work every few milliseconds costs the same as doing it in tick.
Timers do not survive a reload: every timer belonging to a script is dropped when it unloads, so a forgotten setInterval cannot pile up across hot reloads. Clearing them in onUnload is still good hygiene, not damage control.

console

Objects print as objects

Values are formatted by an inspector, not by toString(), so structures are readable without wrapping everything in JSON.stringify:
Nesting is cut off after two levels ([Object]), long arrays and strings are truncated with a note, and cycles print as [Circular] instead of hanging.
Getters are shown as [Getter] and never invoked. Reading a property off an entity is a memory read, and logging must not have side effects - so console.log(player) will not silently read every field of the entity.

Extras

Printf-style placeholders work too: %s %d %i %f %o %O %j %%.

Events

Events use the standard addEventListener API. Everything a browser gives you works here - once, signal, preventDefault, custom EventTargets - see Web APIs.

addEventListener


The cleanest way to unregister a whole script’s worth of listeners is one AbortController:
A listener that throws does not take the script down: the error is reported through the error event and the remaining listeners still run.

on / once / off (legacy)


The original API, still fully supported. Handlers receive positional arguments instead of an event object, and on returns a numeric ID for off. Old event names ("OnKeyDown", "OnMouseMove", …) keep working and resolve to the same events as the modern names.
Prefer addEventListener in new scripts: typed event objects, signal cleanup, and the same names you already know from the web.

Built-in events

Input events carry the fields you would expect in a browser:
The legacy sections below describe the positional arguments the on() API passes for the same events.

"render"

Fires every frame when the overlay is being drawn. All Render module calls must happen inside this callback - drawing outside of it will cause flickering or missing visuals because the frame may have already been submitted.
Never call Render.* methods from "tick", setInterval, or other non-render callbacks. The overlay only accepts draw commands during the "render" event; anywhere else the call does nothing and logs render.text() outside of a 'render' listener does nothing once.

"tick"

Fires on the script loop, which targets 128 Hz independently of the server tickrate and of your framerate. The primary place for logic - entity reads, state machines, calculations. Do not draw here - use "render" for that.

"OnMouseMove"


"OnMouseClick"


"OnMouseScroll"


"OnKeyDown" / "OnKeyUp"

Virtual Key codes are listed in the Microsoft documentation. Common ones: 0x01 LMB · 0x02 RMB · 0x04 MMB · 0x10 Shift · 0x11 Ctrl · 0x12 Alt · 0x2D Insert · 0x2E Delete.

onUnload

Script cleanup is handled by exporting a function named onUnload. It is called once when the script is about to be unloaded or reloaded.
onUnload must be a named export at the module level. It will not fire if declared as a local variable or inside a nested function.
There is also an unload event, fired just before onUnload is called. Use whichever fits - the event works with AbortController cleanup, the export does not need a listener:

Execution budget

Script code runs on one shared worker thread, so a listener that never returns would freeze every script at once. Two thresholds guard against that: Only JavaScript time counts. A slow native call - reading a big file, scanning a whole module, an FFI call into blocking code - pauses the clock, so you are never punished for the host being slow:
A script blocked inside a native call still holds the worker thread - there is no way to interrupt native code. Never call into something that can block forever through @native/ffi.
The 250 ms warning means you dropped a frame. If you see it every few seconds, move the work into a setInterval at a lower rate, or cache the result - see the throttling recipe below.

script

The script object exposes metadata about the currently running script. All fields are readonly.

Recipes

Feature toggle with a keybind

Throttle expensive work with setInterval

Frame-time independent animation