# 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.

```lua
-- 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.

| Event | Arguments | When |
|---|---|---|
| `Fusion.KeyPressed` | `key`: the key's name, like `"G"`, `"Space"`, `"F1"`, `"1"` | a key is pressed (not when it repeats) |
| `Fusion.Think` | `dt`: seconds since the last frame | every frame |
| `Fusion.EntitySpawned` | `entity` (a table, below) | an entity appears in any game (or a Godot prop) |
| `Fusion.EntityRemoved` | `id` | an entity is gone (its game stopped listing it) |
| `Fusion.EntityDamaged` | `entity`, `amount` | its health went down by `amount` |
| `Fusion.EntityDied` | `entity` | its health reached 0 |
| `Fusion.Signal` | `name` | an 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).

| Option | Required | Meaning |
|---|---|---|
| `pos` | yes | where, a vector in meters (Y is up) |
| `shape` | no | `"box"` (default), `"sphere"` or `"cylinder"` |
| `color` | no | `"#3a86ff"`, `"#38f"`, or a name: red, orange, yellow, green, blue, purple, pink, brown, white, gray, black. Default: the playground's colors in turn |
| `velocity` | no | a 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`.

| Call | Does |
|---|---|
| `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`) |

```lua
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)

```lua
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`](https://steonmod.com/docs/devices)), 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
| Call | Does |
|---|---|
| `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`](https://steonmod.com/docs/block-manifest)); a plain scenario runs all of yours.

### `Blocks`: the scenario's blocks

| Call | Does |
|---|---|
| `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

| Call | Does |
|---|---|
| `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`](https://steonmod.com/docs/adr-0002).
