Layouts
Draw the mod's own layouts and style sheets, and how the game draws interfaces.
<layout src="shop.xml" data={stock} /> loads a layout from the mod's
content addon. No other element runs code in the player's game. The
game draws every other element from the typed tree in
src/wasm/ui/ui.proto,
and when the player presses one, the press comes back to its onPress. The host clears an interface when
the player leaves or the mod stops.
- A layout with no content to load draws an empty panel and logs one line in the game's console.
- The
modlock devpreview page draws the layout from the mod's latest build, with its panels, inline styles and style sheets, but runs none of its scripts. - A host with its own interface script loads layouts by passing
Layoutsto theDrawinginpanorama/src/draw.ts.AddonLayouts, for example, loads each layout from theaddongiven in the screen's tree. Modlock's own renderer loads the layouts of the content its session mounted.
Style sheets
sheet('hud.css') adds a sheet from content/styles/ to every interface the
mod shows. When two sheets set the same property, the later sheet wins. In TypeScript,
import './content/styles/hud.css' builds to the same call, and the build
refuses a sheet outside content/styles/. An element takes the sheet's
classes through class="board gold". Changing its classes runs the sheet's
transitions, and the element's own style wins over a class.
Sheets load where layouts do. Beside each sheet, the build writes a layout that
includes only that sheet, and the game loads that layout into the interface's
root panel. A host that passes no Layouts draws the interface without them, and
a sheet that fails to load leaves it drawn with one log line.
Hand values to a layout
Your mod hands values to a layout in two ways: the data of the layout's own
element, and routes. route(player, 'scores', scores) hands its value to every
layout your mod shows that player, so several layouts share one value without a parent passing
it down. A value travels as JSON, and the layout sees a change only
when your mod sends a different value. route(player, name, undefined) removes the route. Go's ui.Route
takes the value as JSON text, and an empty one removes it. The layout's
script includes the library scripts/modlock/layout.js and reads them:
<scripts>
<include src="file://{resources}/scripts/modlock/layout.js" />
<include src="file://{resources}/scripts/{addon}/shop.js" />
</scripts>Modlock.data((stock) => drawShop(stock))
Modlock.route('scores', (scores) => drawScores(scores))Each handler runs at once with the current value and again after each change,
with undefined while the mod sends none.
When your mod sends a new value, the renderer writes it the next time it draws
the screen, to an attribute of the layout's panel: modlock_data for the
element's data, or modlock_route_<name> for a route. After a draw that changed any of these, the renderer plays the sound
event modlock_data_changed. The game delivers no custom event to a script, but
it delivers PlaySoundEffect to every script, and no sound has that name, so
none plays. The library hears it, rereads each attribute, and calls the handler
of each value that changed.
The layout answers the mod with its own console commands.
The renderer
The game draws interfaces with the renderer in panorama/:
- The layout
panorama/layout/modlock/ui.xmland its script, which a client content package installs asscripts/modlock/ui.js, beside the layout libraryscripts/modlock/layout.js. Themodlockcommand line in a release has the library built in.go run ./cmd/modlock-librarybuilds it from source intojs/built, where the next build of the command line embeds it. - The game keeps a client content package, and the layout in it, loaded through matches. The renderer draws over the HUD.
modlock devandmodlock playserve the local player's interfaces on127.0.0.1:4320(--ui-port, 0 for none). Deadlock's HTML panels load only https pages, so the renderer reads them through a relay page at--ui-relay,https://hyperline.ggby default. The controller answers only that origin's page. The session's package loads the page at/app/relay/bridge.html?port=<port>, and the page reads the screen from that loopback port.- Both commands also install the renderer in the game for the session, so its
interfaces show with no setup. They write
game/citadel/modlock_dev, a package that adds the renderer to the stock news layout. They mount it between// MODLOCK DEVmarkers ingameinfo.gi, ahead of other mounted content, and remove it when the session ends. The game reads the package at startup, so a game that was already running shows the interfaces only after a restart. - A press runs the console command
modlockpress <mod> <node>in the player's game.pressCommandinpanorama/src/draw.tsbuilds that command, and a host with its own renderer calls it to press the same way. The server presses the node for the player who sent it, and only when that player's interface shows it as a button.