SteonMod
View as Markdown

Fusion Lua API (v0)

Addons are small Lua programs that add things to Fusion: tools, props, game modes, rules. Fusion uses Luau (the Lua used by Roblox), so anything you know from Lua or Garry's Mod mostly carries over.

This page covers v0. Hooks for game events, Blocks.<router>.* and HUD.* come in step 4.8; Fusion.Tool.define and Fusion.Entity.define come with the sandbox (step 3.8).

Making an addon

  1. Make a folder in godot/addons_dev/ (later: the Workshop and your addons folder), for example godot/addons_dev/my_addon/.
  2. Put an init.lua in it.
  3. Start Fusion and play any scenario: addons run in every World, Fusion's playground or a game's. Your addon loads by itself.
  4. Edit and save while Fusion runs: the addon reloads within half a second. Messages from addons appear at the bottom left of the screen and in the console.
-- godot/addons_dev/my_addon/init.lua
hook.add("Fusion.KeyPressed", "drop_box", function(key)
    if key == "B" then
        Fusion.Prop.spawn({ shape = "box", pos = Fusion.Player.eyePos() + Vector(0, 2, 0), color = "red" })
    end
end)

log("Press B to drop a red box")

Two working examples ship with Fusion:

  • prop_spawner: press G to throw a prop where you're looking.
  • endless_loop_demo: press L and it loops forever, on purpose, to show that Fusion stops it and keeps running.

Types for your editor: docs/types/fusion-addons.d.luau declares the whole API for luau-lsp ("luau-lsp.types.definitionFiles": ["docs/types/fusion-addons.d.luau"]): completion, and a wrong name or argument type is marked while you type. A test keeps it matching the API.

Reference

log(...) and print(...)

Writes a line to the screen and the console, like Lua's print.

hook.add(event, name, function) / hook.remove(event, name)

Runs function whenever event happens. name is your hook's name; adding a hook with the same event and name replaces the old one.

EventArgumentsWhen
Fusion.KeyPressedkey: the key's name, like "G", "Space", "F1", "1"a key is pressed (not when it repeats)
Fusion.Thinkdt: seconds since the last frameevery frame
Fusion.EntitySpawnedentity (a table, below)an entity appears in any game (or a Godot prop)
Fusion.EntityRemovedidan entity is gone (its game stopped listing it)
Fusion.EntityDamagedentity, amountits health went down by amount
Fusion.EntityDiedentityits health reached 0
Fusion.Signalnamean addon or Device sent Fusion.signal(name) (it arrives the next frame)

These entity hooks work the same for every game: Fusion compares its entity registry from one frame to the next.

Fusion.Prop.spawn(options)

Spawns a physics prop and returns its id (a number).

OptionRequiredMeaning
posyeswhere, a vector in meters (Y is up)
shapeno"box" (default), "sphere" or "cylinder"
colorno"#3a86ff", "#38f", or a name: red, orange, yellow, green, blue, purple, pink, brown, white, gray, black. Default: the playground's colors in turn
velocitynoa vector in meters per second, to throw it

Fusion.Entities: every game's NPCs and props

An entity is a table: id (Fusion's id, unique across games), source (the block it comes from, e.g. "fake.cube", or "fusion" for Godot's props), kind, name, pos (a vector), health, maxHealth, team.

CallDoes
Fusion.Entities.all([source])every entity, or only those of one block
Fusion.Entities.get(id)one entity, or nil
Fusion.Entities.near(pos, radius)entities within radius meters, nearest first
Fusion.Entities.act(id, action, ...)asks the entity's game to do action (up to 8 numbers after it), e.g. act(e.id, "stop"). Which actions a game knows is listed in Blocks.list()
Fusion.Entities.steer(id, pos)walks the entity to pos around walls, on any game's ground (Fusion's steering; its block must need steering)
hook.add("Fusion.KeyPressed", "come_here", function(key)
    if key ~= "J" then return end
    for _, e in Fusion.Entities.near(Fusion.Player.eyePos(), 20) do
        Fusion.Entities.steer(e.id, Fusion.Player.eyePos())
    end
end)

Fusion.Tool.define(id, tool): a tool for the sandbox (M3)

Fusion.Tool.define("carrot_launcher", {
  name = "Carrot Launcher",                       -- shown in the spawn menu's Tools tab
  description = "Throws carrots.",                -- its tooltip
  onFire = function(pos, normal, entity) end,     -- left click: where the player aimed, and the
                                                  -- entity there (any game's, or a prop), or nil
  onAltFire = function(pos, normal, entity) end,  -- right click (optional)
})

id is lowercase letters, digits and _. Defining the same id again replaces the tool; an addon that's reloaded without it loses it. The functions run with the same time limits as hooks. Fusion's own props take set_health, remove and freeze through Fusion.Entities.act, like games' entities take their blocks' actions. Example: godot/addons_dev/carrot_launcher. Not yet: Fusion.Entity.define (Lua-made entities).

Fusion.signal(name)

Tells every addon and Device something happened: their Fusion.Signal hooks get name on the next frame. Devices use signals to work together (docs/devices.md), and an Experience's goals and ending listen to them. In an Experience, Fusion sends experience_start when the player presses Start.

Experience: ending the game

CallDoes
Experience.win([text])ends the Experience being played: the ending screen says "You win" and text (or the Experience's win_text)
Experience.lose([text])the same, "Game over"

Outside an Experience they do nothing. An Experience (and a stack) runs only the addons its scenario lists (addons = [...], docs/block-manifest.md); a plain scenario runs all of yours.

Blocks: the scenario's blocks

CallDoes
Blocks.list()every block: id, name, kind, router, actions
Blocks.sleep(router) / Blocks.wake(router)puts a game to sleep (no CPU) or wakes it, also early before it's needed. The World's and the played Character's games can't sleep

HUD: the player's screen

CallDoes
HUD.toast(text, [seconds])a message at the top of the screen for a few seconds (3 by default)
HUD.text(key, text)a line at the top right that stays until changed; HUD.text(key) removes it

Fusion.Player.eyePos() / Fusion.Player.aimDir()

Where the player's eyes are, and which way they're looking (a vector of length 1). Fusion.Player:eyePos() works too.

Vectors

Vector(x, y, z) makes a vector (Luau's built-in vector.create also works). Vectors add, subtract and multiply: eye + aim * 3. Read parts with v.x, v.y, v.z.

Fusion.version

The Lua API version, e.g. "0.1.0".

Rules (the sandbox)

  • No files, network or programs: io, os.execute, require and loadstring don't exist. os.time, os.clock and os.date do.
  • Each addon runs on its own. Its global variables are its own, and if it breaks, the others keep running.
  • Time limit: loading init.lua may take 250 ms, and handling one event 50 ms. An addon that takes longer (usually an endless loop) is stopped with a message. Fix it and save to load it again.
  • Memory limit: 64 MB per addon. Over that, it's stopped the same way.
  • Spawn limit: 32 props per addon per frame.
  • Request limit: 64 actions, steering, sleep/wake and HUD calls per addon per frame.
  • Mistakes are explained: a call that can't work (an unknown game, an entity that can't be steered) writes why in the addon log instead of failing silently.
  • Log limit: 100 lines per addon per frame, 2000 characters per line.

Why these rules: docs/decisions/0002-one-luau-vm-per-addon.md.