Files
horizon-airdef/docs/api/mods.md
T

11 KiB

Mod API

Mods are Rust crates compiled to sandboxed WebAssembly components. Client mods run in the player's game; server mods (experimental) run wherever a world is simulated: single player, a LAN host, the dedicated server. Design and the runtime: mods.md.

Overview

A mod today can read the local player and the world clock, show chat lines only that player sees, draw a text panel on the HUD, keep small files of its own, register chat commands, and run on every tick, frame or chat line it asks for. A content mod adds blocks and items from JSON in its .hmod, copying vanilla templates, plus tags, recipes, loot, models, textures and structures in its own namespaces. Every capability is declared in the manifest, shown to the player before the mod is turned on, and checked against the component's real imports.

Count
Interfaces the game offers 6: log, game, chat, hud, storage; players for server mods
Functions in them 12
Hooks a mod exports 5: init, tick, frame, command, chat; a server mod 8: init, tick, player-joined, player-left, chat, command, block-broken, block-placed
Capabilities 6: game-read, chat, hud, storage, registries, players
Manifest api lines served 3: 0.1 (code), 0.2 (content), 0.3 (server mods, experimental)
SDK the Mod trait (5 methods), Registration (4 builders), export_mod!, log helpers; server::ServerMod (8 methods), server::Registration (6 builders), server::Argument, server::Args, export_server_mod!
cargo horizon-mod new (templates code and content), build, run, test, check
Example mods coords-hud (code), ruby-content (content), welcome (server)

Guide

The step-by-step guide, from installing the tools to publishing on the registry, is Make a mod on the website. In short:

  1. rustup target add wasm32-unknown-unknown, cargo install --git https://github.com/horizon-rust/horizon cargo-horizon-mod, then cargo horizon-mod new my-mod (Making a mod).
  2. Implement Mod for your type, list the capabilities you import in horizon-mod.toml (The manifest).
  3. cargo horizon-mod run builds the .hmod and starts the game with it as a dev mod; after an edit, cargo horizon-mod build and the running game reloads it.
  4. cargo horizon-mod test runs the mod's tests/*.toml scripts in the sandbox, with no game: ticks, commands, chat lines and the player's position in, chat, feedback and the HUD panel checked (Testing a mod).
  5. Content mods: Content mods (API 0.2).

Reference

mods-reference.md, generated from crates/horizon-mod-sdk/wit/horizon.wit: every capability, interface, type, function and hook, with the limits the guide lists (16 HUD lines of 256 characters, 8 chat lines per call, 4 MiB of storage, fuel per call).

Examples

  • crates/horizon-mod-sdk/examples/coords-hud: coordinates, facing and time on the HUD; /where, /coords; storage.
  • crates/horizon-mod-sdk/examples/ruby-content: no code; a gem and a block of ruby from vanilla templates, with their recipes, loot, tags, models, textures and names.
  • crates/horizon-mod-sdk/examples/welcome: a server mod; greets players who join, announces newcomers (kept in storage), /online and /mined.
  • crates/horizon-mods/tests/guest: a mod that misbehaves on command (spins, panics, grows its memory), for the sandbox tests.

Ways in

One API (the WIT world) and one sandbox (WebAssembly), whatever the mod is written in. Every way in produces a component for the same world, checked against the same capabilities and given the same limits; there is no second scripting runtime in the game.

Way in For Status Template and example
Rust most mods; the fastest first-class: the SDK, cargo horizon-mod cargo horizon-mod new, coords-hud
Data only blocks and items with no code first-class: content mods (API 0.2) cargo horizon-mod new --template content, ruby-content
Lua scripts people who don't want a compiler planned: a script mod whose code is a .lua file, run by Horizon's own interpreter mod planned, with the same example as Rust
C, C++, Zig authors of those languages to evaluate: bindings generated from the WIT, built without WASI one each if it passes
Go (TinyGo), AssemblyScript, JavaScript / TypeScript authors of those languages to evaluate: each needs its toolchain to emit a component without WASI imports, or an interpreter mod as for Lua one each if it passes
  • Scripts run inside the sandbox. The Lua interpreter is itself a WebAssembly mod, built in Rust with the SDK and shipped by Horizon. A script mod's manifest names the interpreter and its own capabilities; the game links the interpreter with only those, so a script can do exactly what a compiled mod with the same manifest can.
  • Hot reload. The game keeps the interpreter compiled and loads the script into a fresh instance when the file changes, without restarting the game; what it saved with storage survives, as for dev mods.
  • Measured, not guessed. Each way in is listed with its measured cost on the same tick-heavy example (time and fuel per tick, memory, load time) against Rust, and a language is supported only with a template, an example and a test that builds and loads it in CI.

Stability

Preview (stability.md): API 0.1 and content 0.2 are served together; a mod built for 0.1.0 keeps loading on every 0.1.x game. Server mods are their own line, api = "0.3", experimental: they may change in any release.

Not there yet

What real mods need next, in the order modpacks and servers need it:

  • More for server mods (api = "0.3", Server mods): they run on the server thread, a LAN host and the dedicated server today, with joins, chat, typed commands in players' command trees, players' block breaking and placing, and world (read blocks and biomes, set blocks as /setblock does); next entity events, game rules, entity, item and block-state arguments.
  • World and blocks: blocks, block states and biomes are there for server mods (world); next block entities, and world for client mods (raycasts, single player and LAN hosts).
  • Items and inventories: the player's inventory, held item and open container; item stacks with components.
  • Entities: query and change entities near a position; spawn from a mod's own entity types (data first).
  • Block entities and menus: ticking machines with slots and their screens.
  • Screens and UI: widgets built from vanilla's, a screen the mod owns, a settings page under Options > Mods.
  • Networking: a channel between a mod's two halves, client and server, typed by the mod's own WIT.
  • Config: typed settings the game shows and stores for the mod, on the server too.
  • Keys and the crosshair target: keybinds in Controls; what the player is looking at.
  • Script mods and other languages: see Ways in.
  • SDK: templates per kind of mod, typed wrappers in the SDK for every interface. Dev mods already reload when rebuilt, and cargo horizon-mod test runs a mod headless.

Changelog

  • API level 20 (AirDef fork, capability render): sound-sources starts, updates and stops persistent sound sources (pose, velocity, gain, pitch, looping, priority, attack, release, optional bounded Doppler, loop overlap) over the game's own mixer, and sound-source-generation tells the mod when the game released them all (world closed, film begun or ended, sounds reloaded) so it starts them again. A stale generation is ignored. Gain, pan and pitch are smoothed across each audio buffer and the position is carried along the velocity between calls, so a source updated once a tick has no steps; 24 sources hold a voice (loudest and nearest times priority), the rest of the loops stay virtual; a stop is never lost to a limit; the sources of a mod that stops or is reloaded are released. A film mixes them on its own clock like play-sound.
  • API level 22 (AirDef fork, capability render): load-chunks has the game load chunks for 20 seconds after the last request that names them (at most 64 at once): generated or read from disk on the worker threads, not meshed. ground answers i32::MIN for a column of such a chunk that is not there yet, -64 for one that is not coming. Nothing happens on a server or in a LAN-hosted world.
  • API level 21 (AirDef fork, capability render): acoustics tells whether the game muffles sounds by what lies between them and the listener (HORIZON_ACOUSTICS=1: a wall or a mountain dims a sound and cuts its highs, traced from a snapshot of the loaded chunks, never in the audio callback). A mod that muffles by itself, such as a _far variant for a distant blast, plays the near one when it is true, so nothing is muffled twice.
  • API level 19 (AirDef fork, capability command): set-tree gives a client mod's command a tree of subcommands (literals), typed words (word, choice, integer and number with bounds, names from a list, block-pos), optional branches (runs), the rest of the line (text) and key=value options; set-suggestions sets the list a names node suggests from. The chat box colours the line, shows the error and completes the next word before Enter, and a line that does not parse never reaches the mod; one that does gets the line as it was typed. The plain registration.commands name stays the free-text fallback. SDK tree::{Tree, Node, Value, Opt}: register, parse, complete, usage from the same nodes.
  • API 0.3: typed server commands: registration.commands lists each command's arguments (argument-kind: word, text, integer, number, boolean, player, block-pos), sent in players' command trees and parsed by the server; command gets their values. SDK Registration::command_with, Argument, Args.
  • API 0.3: capability world (get-block, get-biome, set-blocks); example beacons; test scripts' blocks and expect.blocks.
  • Server mods run in the game: single player (server thread and in-frame), LAN hosts, the dedicated server.
  • API 0.3 (server mods, experimental): world horizon-server-mod, capability players, the server-events hooks; SDK server::ServerMod; example welcome.
  • SDK: cargo horizon-mod test and horizon_mods::testing, test scripts in TOML; the template has one.
  • Experimental: ui button widget; render.set-corner, text-width, frame-stats; input.mapping, clicks-per-second (what the official FPS, keystrokes and zoom mods need).
  • Experimental input (experimental = ["input"]): claim-items, register-key, held, poll (use-start, use-stop, key-down, key-up).
  • Experimental render (experimental = ["render"]): HUD shapes (fill, quad, sprite, item, text), gui-size, camera shake, drawn by the game's own renderer.
  • API 0.2 (content): data-driven blocks and items, tags, horizon:add_to, terrain adaptation.
  • API 0.1.0: log, game, chat, hud, storage; init, tick, frame, command, chat.