Scripting

Scripting API

Script doors, levers, Areas, conversations and module events in Luau. Everything here is generated from the engine, so it matches the build it describes.

LuauProfile rpg.script/559 entries · generated from the engine

Scripts are Luau files attached to Blueprints, Areas and the Module in the editor. The engine calls your handlers (OnUsed, OnOpened, OnEnter, OnModuleStart…) when things happen in the world. Each event runs in a fresh, bounded VM; a script that goes over a limit stops with an error instead of stalling the server.

20,000VM steps per event
16native calls
1 MiBlive memory
4,096allocations

Light-effect scripts may make 2048 native calls.

Edit with VS Code

  1. Install Visual Studio Code and its Luau Language Server extension (JohnnyMorganz.luau-lsp). We test and document Script editing with VS Code. Scripts are plain .luau files, so other editors with a Luau language server can work too, but we do not test them or help set them up.
  2. Create or open your project in Ironspawn Editor, including Crypt or First Steps. The editor creates/refreshes .vscode/settings.json, .vscode/extensions.json and .luaurc, and copies matching types to .ironspawn/ironspawn.d.luau in that project's root. Open that folder in VS Code and accept its extension recommendation. No hand-written configuration or downloaded type file is needed. The installed editor supplies the matching EditorResources/Scripting/ironspawn.d.luau; the dev editor copies the repository's docs/scripting/ironspawn.d.luau. The copy is written only when its bytes differ.
  3. Create/register the Script in Ironspawn Editor's Content Browser. Open its Script editor, apply source drafts, then choose Save and open externally. This saves committed Document values before opening the .luau file in VS Code. A loose, unregistered .luau file is not a Script asset. A Script's .rpgscript descriptor beside it holds its stable identity; keep that descriptor.
  4. Save in VS Code, return to Ironspawn Editor and use File > Reload from disk after the changed-file notification. Resolve any dirty-work choice explicitly, then Build & Play. External saving does not automatically replace the Document.

The generated settings select the standard platform, disable Rojo sourcemaps, map @ironspawn to .ironspawn/ironspawn.d.luau and disable absent standard-library globals from the SA1 registry. Other workspace settings, definition packages and extension recommendations survive refresh. Commit .vscode, .luaurc and .ironspawn with your project to share setup through git. The definition path is relative to the workspace folder, so moving/cloning the project needs no path edit. Open the project root as the first VS Code workspace folder. These are editor tooling files, outside source inventory, cook input and exact source conflicts; they never enter a published module. This repository git-ignores all three under modules/*/source.

Invalid JSON (including JSONC comments/trailing commas) is left alone, with a Script tooling notice in the editor and script-tooling-warning in its log. Correct the indicated file and reopen the project to refresh it. Non-object roots and incompatible owned containers are also reported rather than replaced.

.luaurc uses nonstrict: ordinary unannotated event handlers and literal metadata remain convenient while unknown globals and known API argument types are checked. UnknownGlobal, UninitializedLocal, DuplicateLocal, ImplicitReturn and UnreachableCode are enabled. LocalUnused and FunctionUnused are disabled because the host consumes metadata locals and exported entry functions outside normal Luau call flow. Other lint choices survive refresh. Luau documents its lint behavior.

Runtime rules beyond the language server

  • Use numeric for (for i = 1, #objects do). Generalized iteration and for ... in are rejected, even if the language server accepts them.
  • Use ordinary quoted strings; backtick string interpolation is rejected.
  • There is no standard library: no print, assert, pairs, ipairs, require, math, string or table, including string-library method calls. Operators, numeric loops and #array work without libraries.
  • Declare capabilities literally. A light Script declares light_effect tags (literal tags or $exported_property) and self = true to access its own Lights. Dialogue entries declare their parameters, reads and proposed effects through exported_functions. Definitions cannot prove these per-entry permissions, quotas, lifetimes or transaction rules. Cook/runtime admission remains authoritative.

self:GetProperty returns boolean | number | string; assert the known exported type, as below. Dialogue.Param is any in tooling because its type comes from the entry's literal parameter declaration; runtime still validates those arguments.

Worked example: lever and torches

Create a stationary mesh-backed lever Placeable with a Script component. Copy the complete lever Script into its Script asset:

luauluau
local exported_properties = {
    target_tag = { type = "string", default = "crypt_torch" },
}
local light_effect = { tags = { "$target_tag" }, self = true }

function OnUsed(user)
    local objects = Area.FindByTag(self:GetProperty("target_tag") :: string)
    for i = 1, #objects do
        if objects[i] ~= self then
            local lights = objects[i]:GetLights()
            for j = 1, #lights do
                lights[j].Enabled = not lights[j].Enabled
            end
        end
    end
    local lamps = self:GetLights()
    for i = 1, #lamps do
        lamps[i].Enabled = not lamps[i].Enabled
    end
end

Tag each torch placement crypt_torch and give it a Light component. The exported target_tag can be changed in the Inspector; $target_tag grants the corresponding query capability. The first numeric loop visits matching objects in this Area, the second toggles their Lights, and the final loop toggles the lever's optional lamp. Skipping self in the query avoids double toggles if the lever has the tag. Apply, Save, reload after external edits, then Build & Play and Use the lever. Watch ground illumination/shadows: this changes Lights, not flame meshes.

Other editors that run luau-lsp can load the same definition file and disabled-globals list from .vscode/settings.json; their users configure luau-lsp themselves. This is unsupported.

Lifecycle events

Named lifecycle handlers use one Script slot per owner. The Inspector lists Handles and warns when a function cannot fire on that owner. Door Open/Close and Container per-viewer Open/Close use OnOpened(user) / OnClosed(user); explicit Use calls only OnUsed. Page changes do not close and reopen a Container.

Choose a Script in the Module Script card or the selected Area Script card in Module editor. Area OnEnter(character) / OnExit(character) track membership. Module OnPlayerJoin(character) precedes Area entry; Area exit precedes OnPlayerLeave(character). OnModuleStart() runs after restore and recovery departures, before new joins; IsFirstStart() is true for the first admitted start only, including a faulting handler. Lifecycle effects and completion are saved together. They may run while paused; there is no persistent VM or runtime Connect.

Examples: Door light toggle, Area arrival lights, and Module start counter. Character arguments expose a captured Name; Area/Module self supports GetProperty and equality. Module Scripts are counter-only; Area light Scripts query declared tags and have no self lights. Lifecycle handlers return nothing and may make no writes.