Skip to content

Making add-ons (.mastlib)

An add-on packages reusable MAST (consoles, comms trees, prefabs, routes) so other missions can load it. sbs_utils ships the Legendary Missions add-ons this way. An add-on is a module: a folder with an __init__.mast that imports the rest.

Start from the template

sbs create MyAddon -t addon

That gives you the layout below already wired up — including a harness map, so sbs debug MyAddon --map 0 puts the add-on's console in front of you straight away. The rest of this page is what those pieces are.

v1.4.0 and later

The addon template uses provides / requires, which don't exist on v1.3.0, so it only appears on the v1.4.0 line. See the CLI.

Lay out the module

MyAddon/                 # a dev mission that hosts the add-on
├── script.py            # standard boilerplate
├── story.mast           # minimal test harness (not the add-on)
├── story.json           # sbslib + any mastlibs the add-on needs
├── __lib__.json         # declares the add-on folder for the packager
└── my_addon/            # THE ADD-ON -> becomes my_addon.mastlib
    ├── __init__.mast    # entry point - imports the rest
    ├── panels.mast
    └── helpers.py       # Python helpers are fine too

my_addon/__init__.mast just imports its files:

import panels.mast
import helpers.py

Declare what it needs

Add-ons share one global namespace and load in no fixed order, so "my add-on uses a label from theirs" is only safe if you say so. Three directives at the top of __init__.mast (column 0, like import) make the contract explicit, and they're checked at compile time:

provides hangar.sortie_board     # this add-on supplies a capability
suggests hangar                  # SOFT: warn if absent, never fail
requires gamemaster              # HARD: fail the compile if nothing provides it

Tokens are opaque strings, dotted by convention; several per line is fine (provides casino, casino.bar). Collection is order-independent — the compiler gathers every provides as files compile, then validates once at the end.

Use requires when you call the other add-on's globals or routes, or your feature is meaningless without it. Use suggests for an optional augmentation, guarded with default shared X = None so it degrades gracefully.

An unmet requires is a compile error, surfaced by sbs lint and --test and shown as a runtime error screen. These are compile-time only — no runtime effect — and backward compatible: a line is a directive only when a token follows the keyword, so requires = 5 is still an assignment.

v1.4.0 and later

Missions on the v1.3.0 line can't use these.

__lib__.json names the folder(s) to package:

{
    "version": "v1.0.0",
    "mastlib": ["my_addon"]
}

Develop, then package

While developing, keep the add-on folder inside a mission — the mission directory is on the MAST search path, so its labels load automatically. When ready to share:

sbs lib MyAddon -u your-github-user      # builds your-github-user.my_addon.v1.0.0.mastlib

Drop the .mastlib in __lib__/ and add it to another mission's story.json:

{ "mastlib": ["your-github-user.my_addon.v1.0.0.mastlib"] }

Its labels, routes, and prefabs are now available globally in that mission. Add-ons are a great way to share partial missions — Gamemaster comms, custom consoles, prefab libraries, and so on. See Sharing reusable Python for the .sbslib side.

An add-on that also carries content the engine has to open — ships, interiors, art — is a mod, and needs a media pack alongside the .mastlib. See Making a mod.