874b6f20c2
Основа — Horizon (MIT). История апстрима не переносится: этот репозиторий ведёт свою.
838 lines
33 KiB
Plaintext
838 lines
33 KiB
Plaintext
/// The contract between Horizon and its mods (docs/mods.md).
|
||
///
|
||
/// A mod is a WebAssembly component targeting the `mod` world. Each imported
|
||
/// interface but `log` is a capability: the mod's manifest lists the ones it
|
||
/// uses, the player sees them before turning the mod on, and the game refuses
|
||
/// a component that imports one its manifest does not declare.
|
||
package horizon:mods@0.1.0;
|
||
|
||
/// The mod's log, in the game's log under the mod's id. Always granted.
|
||
interface log {
|
||
enum level {
|
||
debug,
|
||
info,
|
||
warn,
|
||
error,
|
||
}
|
||
/// One line in the game's log as `[mod <id>] message`, at that level.
|
||
write: func(level: level, message: string);
|
||
}
|
||
|
||
/// Capability `game-read`: the local player, the world clock and the block
|
||
/// the player looks at.
|
||
interface game {
|
||
record player {
|
||
/// Feet position, in blocks.
|
||
x: f64,
|
||
y: f64,
|
||
z: f64,
|
||
/// Degrees, as F3 shows them.
|
||
yaw: f32,
|
||
/// Degrees: -90 looks straight up, 90 straight down.
|
||
pitch: f32,
|
||
/// Hit points as vanilla counts them: 20 is full health without effects.
|
||
health: f32,
|
||
/// 0 to 20, as the hunger bar.
|
||
food: u32,
|
||
/// `minecraft:overworld`, `minecraft:the_nether`, ...
|
||
dimension: string,
|
||
}
|
||
record clock {
|
||
/// Ticks since the world was created.
|
||
game-time: u64,
|
||
/// 24000 ticks per day; 0 is 6:00 on the first day.
|
||
day-time: u64,
|
||
}
|
||
/// None on the title screen.
|
||
get-player: func() -> option<player>;
|
||
/// None on the title screen.
|
||
get-clock: func() -> option<clock>;
|
||
/// A block along the player's view.
|
||
record hit {
|
||
/// The block hit.
|
||
block-x: s32,
|
||
block-y: s32,
|
||
block-z: s32,
|
||
/// The point on its face where the view meets it.
|
||
x: f64,
|
||
y: f64,
|
||
z: f64,
|
||
/// `minecraft:stone`, ...
|
||
block: string,
|
||
}
|
||
/// The first block along the player's view (fluids pass), up to
|
||
/// `distance` blocks (at most 1024; past the loaded chunks, across the
|
||
/// distant terrain when LOD Distance is on), as of this tick. None when
|
||
/// nothing is hit, and on the title screen.
|
||
raycast: func(distance: f32) -> option<hit>;
|
||
}
|
||
|
||
/// Capability `chat`: messages only the local player sees.
|
||
interface chat {
|
||
/// Shown in chat after the mod's name, like a system message.
|
||
show: func(text: string);
|
||
}
|
||
|
||
/// Capability `hud`: one panel of text lines per mod, drawn by the game
|
||
/// every frame until the mod changes or clears it (no call per frame).
|
||
interface hud {
|
||
enum corner {
|
||
top-left,
|
||
top-right,
|
||
bottom-left,
|
||
bottom-right,
|
||
}
|
||
record line {
|
||
/// Plain text, up to 256 characters.
|
||
text: string,
|
||
/// 0xRRGGBB.
|
||
color: u32,
|
||
}
|
||
/// Replaces the mod's panel: up to 16 lines in that corner of the screen.
|
||
set-panel: func(corner: corner, lines: list<line>);
|
||
/// Removes the mod's panel.
|
||
clear: func();
|
||
}
|
||
|
||
/// Capability `storage`: a per-mod folder of small key/value files, quota'd
|
||
/// (docs/mods.md). Keys are `[a-z0-9_.-]{1,64}`, one path segment.
|
||
interface storage {
|
||
/// None if `key` has never been `set`, or the key is not well-formed.
|
||
get: func(key: string) -> option<list<u8>>;
|
||
/// Errs on a malformed key or over quota (bytes for the whole mod).
|
||
set: func(key: string, value: list<u8>) -> result<_, string>;
|
||
/// No error if `key` was already absent.
|
||
delete: func(key: string);
|
||
}
|
||
|
||
/// Capability `command`: vanilla commands run as the player, with the
|
||
/// player's permissions, after the hook call returns (8 per call). A mod's
|
||
/// own commands are not run this way.
|
||
interface command {
|
||
/// `line` without its slash; its feedback goes to the chat as if typed.
|
||
run: func(line: string);
|
||
/// `lines` (each without its slash, at most 256 a call) as `run` does,
|
||
/// but with nothing in the chat: no feedback and no failure. In single
|
||
/// player and on Horizon's servers, each runs exactly as the player
|
||
/// typing it would (the same rights), in order, 64 a tick, and the
|
||
/// server logs one line a batch; one that carries a chat message (`say`,
|
||
/// `msg`) is refused. On any other server they go as chat commands, and
|
||
/// there their feedback shows: a mod running many should say so.
|
||
run-quiet: func(lines: list<string>);
|
||
}
|
||
|
||
/// Capability `ui`: widgets the game draws with its own menus' widgets, for
|
||
/// a screen the mod owns and a settings page in Options > Mods. Layout and
|
||
/// drawing are the game's: a mod says what its widgets hold, never where
|
||
/// they sit or how they look, and the game clips everything it is handed.
|
||
interface ui {
|
||
/// A line of text.
|
||
record text-widget {
|
||
/// The mod's own name for the widget; `ui-events` calls echo it.
|
||
id: u32,
|
||
/// Up to 256 characters.
|
||
text: string,
|
||
/// 0xRRGGBB; 0 is the menus' white.
|
||
color: u32,
|
||
}
|
||
/// An item's icon and a name beside it, as recipe viewers show them.
|
||
record item-widget {
|
||
/// The mod's own name for the widget.
|
||
id: u32,
|
||
/// `minecraft:diamond`, `<mod>:<path>`; an unknown id draws a "?".
|
||
item: string,
|
||
/// The name shown beside the icon (empty: the item's own).
|
||
name: string,
|
||
}
|
||
/// Rows the player picks one of, as an options page lists choices: a
|
||
/// pick is `ui-events::select`, `selected` is the row to mark now.
|
||
record list-widget {
|
||
/// The mod's own name for the widget; `ui-events::select` echoes it.
|
||
id: u32,
|
||
/// The rows' texts, at most 512, each up to 256 characters.
|
||
rows: list<string>,
|
||
/// The row to mark as the current choice, if any.
|
||
selected: option<u32>,
|
||
}
|
||
/// A thing on a mod's screen or settings page.
|
||
/// A button as the options pages have them (200 × 20): a click is
|
||
/// `ui-events::select(id, 0)`. Mods cycle its label for a choice
|
||
/// ("Corner: Top Left").
|
||
record button-widget {
|
||
/// The mod's own name for the widget.
|
||
id: u32,
|
||
/// Up to 256 characters.
|
||
label: string,
|
||
}
|
||
variant widget {
|
||
/// Text the player reads.
|
||
text(text-widget),
|
||
/// A button.
|
||
button(button-widget),
|
||
/// An item's icon and name.
|
||
item(item-widget),
|
||
/// Rows the player picks from.
|
||
%list(list-widget),
|
||
}
|
||
/// A screen the mod owns: a title over widgets stacked down the middle,
|
||
/// the Done button the game adds, and Esc to leave it.
|
||
record screen {
|
||
/// Its title, up to 256 characters.
|
||
title: string,
|
||
/// Its widgets, in order, at most 64.
|
||
widgets: list<widget>,
|
||
}
|
||
/// Show `screen` over the game; a second call while it is open replaces
|
||
/// it. Only the last call of one hook counts.
|
||
open-screen: func(screen: screen);
|
||
/// Close the mod's screen when it is open (the mod hears no `closed`
|
||
/// for this: it was its own doing).
|
||
close-screen: func();
|
||
/// The mod's page in Options > Mods, reached from a settings button on
|
||
/// its row: the widgets it shows, an empty list for no page. The player
|
||
/// opening the page is `ui-events::settings-opened`: push fresh widgets
|
||
/// then; later calls update the page while it shows.
|
||
set-settings: func(widgets: list<widget>);
|
||
}
|
||
|
||
/// What the game calls on a mod that shows widgets (capability `ui`):
|
||
/// what the player did with them.
|
||
interface ui-events {
|
||
/// A row of one of the mod's `list` widgets was picked: the widget's
|
||
/// `id` and the row, on the screen or the settings page that showed it.
|
||
select: func(id: u32, row: u32);
|
||
/// The player left the mod's screen (Esc or Done); not for a screen the
|
||
/// mod closed itself or one that went away with the mod.
|
||
closed: func();
|
||
/// The player opened the mod's settings page in Options > Mods: call
|
||
/// `ui::set-settings` now to fill it with fresh widgets.
|
||
settings-opened: func();
|
||
}
|
||
|
||
/// Capability `input` (experimental: the manifest needs
|
||
/// `experimental = ["input"]`): the use button on items the mod claims, and
|
||
/// the mod's own keys. Events wait for `poll`, at most 256.
|
||
interface input {
|
||
/// The main hand's stack.
|
||
record held-item {
|
||
/// `minecraft:spyglass`, `<mod>:<path>`.
|
||
id: string,
|
||
count: u32,
|
||
/// The hotbar slot, 0 to 8.
|
||
slot: u32,
|
||
}
|
||
record item-use {
|
||
item: held-item,
|
||
/// Game ticks the button was held (0 on `use-start`).
|
||
ticks: u32,
|
||
}
|
||
variant event {
|
||
/// The use button went down with a claimed item in the main hand.
|
||
/// The game does nothing else with the click: no placing, no eating,
|
||
/// no swing.
|
||
use-start(item-use),
|
||
/// It came up again, or the item left the hand, or a screen opened.
|
||
use-stop(item-use),
|
||
/// A key `register-key` named went down or up while playing (no
|
||
/// screen open). The game's own mapping on that key still runs. A
|
||
/// screen opening lets every held key up.
|
||
key-down(string),
|
||
key-up(string),
|
||
/// Mouse wheel steps while the mod holds the wheel (`claim-wheel`):
|
||
/// positive away from the player, as the hotbar reads them.
|
||
wheel(f32),
|
||
}
|
||
/// The items whose use the mod handles instead of the game, by id (at
|
||
/// most 64). Replaces the previous list.
|
||
claim-items: func(ids: list<string>);
|
||
/// A key of the mod's: `name` comes back in `key-down` and `key-up`,
|
||
/// `label` says what it does, `key` is the key as options.txt writes
|
||
/// it (`key.keyboard.r`), the default: the player can bind another
|
||
/// one on the mod's page in Options > Mods, and that one wins. At most
|
||
/// 16; an unknown key is an error.
|
||
register-key: func(name: string, label: string, key: string) -> result<_, string>;
|
||
/// The main hand's stack, as of this tick; none when it is empty.
|
||
held: func() -> option<held-item>;
|
||
/// The events since the last call, oldest first.
|
||
poll: func() -> list<event>;
|
||
/// Takes the mouse wheel while playing (true) or gives it back: while
|
||
/// taken, its steps are the mod's `wheel` events and the hotbar doesn't
|
||
/// move. The game gives it back when the mod stops.
|
||
claim-wheel: func(on: bool);
|
||
/// A mapping as the player has bound it.
|
||
record mapping-state {
|
||
/// The bound key's name as Controls shows it: `W`, `Space`, `LMB`.
|
||
label: string,
|
||
/// Held down now (as of this frame for a mod with a `frame` hook,
|
||
/// else this tick).
|
||
down: bool,
|
||
}
|
||
/// A movement or click mapping as the player has bound it: `key.forward`,
|
||
/// `key.left`, `key.back`, `key.right`, `key.jump`, `key.sneak`,
|
||
/// `key.sprint`, `key.attack` or `key.use`. None for another name.
|
||
mapping: func(name: string) -> option<mapping-state>;
|
||
/// Clicks of the attack and use buttons in the last second.
|
||
clicks-per-second: func() -> tuple<u32, u32>;
|
||
}
|
||
|
||
/// Capability `render` (experimental: the manifest needs
|
||
/// `experimental = ["render"]`): shapes the game draws for the mod with its
|
||
/// own renderer, kept until the mod replaces them (no call per frame). A mod
|
||
/// never supplies shader code.
|
||
interface render {
|
||
/// A corner of the screen, as `hud`'s panels use them.
|
||
enum corner {
|
||
top-left,
|
||
top-right,
|
||
bottom-left,
|
||
bottom-right,
|
||
}
|
||
/// A rectangle in GUI pixels: the top left corner and the size.
|
||
record rect {
|
||
x: f32,
|
||
y: f32,
|
||
w: f32,
|
||
h: f32,
|
||
}
|
||
/// A point in GUI pixels from the screen's top left.
|
||
record point {
|
||
x: f32,
|
||
y: f32,
|
||
}
|
||
record fill-shape {
|
||
rect: rect,
|
||
/// 0xAARRGGBB.
|
||
color: u32,
|
||
}
|
||
/// Any four-cornered shape (a wheel's segment, an arrow): the corners
|
||
/// in order round it.
|
||
record quad-shape {
|
||
corners: tuple<point, point, point, point>,
|
||
/// 0xAARRGGBB.
|
||
color: u32,
|
||
}
|
||
record sprite-shape {
|
||
/// One of the game's GUI sprites, as `gui/sprites/` names them
|
||
/// (`hud/heart/full`, `widget/button`), the resource packs' versions
|
||
/// included. An unknown one draws nothing.
|
||
sprite: string,
|
||
rect: rect,
|
||
/// A tint, 0xAARRGGBB: 0xFFFFFFFF draws it as it is.
|
||
color: u32,
|
||
}
|
||
record item-shape {
|
||
/// `minecraft:diamond`, `<mod>:<path>`; an unknown one draws nothing.
|
||
item: string,
|
||
/// The icon's top left.
|
||
at: point,
|
||
/// 1.0 is the hotbar's 16 × 16.
|
||
scale: f32,
|
||
}
|
||
record text-shape {
|
||
/// Up to 256 characters, one line.
|
||
text: string,
|
||
at: point,
|
||
/// 0xRRGGBB, as `hud`'s lines.
|
||
color: u32,
|
||
/// Centred on `at.x` instead of starting there.
|
||
centered: bool,
|
||
shadow: bool,
|
||
}
|
||
variant shape {
|
||
fill(fill-shape),
|
||
quad(quad-shape),
|
||
sprite(sprite-shape),
|
||
item(item-shape),
|
||
text(text-shape),
|
||
}
|
||
/// Replaces the mod's HUD shapes: up to 1024, drawn every frame in
|
||
/// order over the HUD (after the hotbar, under chat and screens), hidden
|
||
/// with the HUD (F1). An empty list removes them.
|
||
set-hud: func(shapes: list<shape>);
|
||
/// The screen's size in GUI pixels (width, height), as of this tick.
|
||
gui-size: func() -> tuple<f32, f32>;
|
||
/// Shakes the camera: a jolt of `strength` (0 to 1, 1 the hardest) that
|
||
/// fades out over `seconds` (at most 10). Jolts add up to 1. The view
|
||
/// turns, the player doesn't, and the player's Distortion Effects option
|
||
/// scales it (0 turns it off).
|
||
shake: func(strength: f32, seconds: f32);
|
||
/// Replaces the mod's corner panel: shapes in GUI pixels from the top
|
||
/// left of a `width` × `height` box that the game places in `corner`,
|
||
/// stacked with the game's overlays and the other mods' panels (under
|
||
/// the status effects at the top right), as `hud`'s panels are.
|
||
set-corner: func(corner: corner, width: f32, height: f32, shapes: list<shape>);
|
||
/// Removes the mod's corner panel.
|
||
clear-corner: func();
|
||
/// The width of one line of text in GUI pixels, in the HUD's font: exact
|
||
/// for Latin letters (U+0000 to U+024F), 6 per other character.
|
||
text-width: func(text: string) -> f32;
|
||
record frame-rate {
|
||
/// Frames drawn in the last second.
|
||
fps: u32,
|
||
/// The slowest frame's rate, and the 1% low (the 99th percentile
|
||
/// frame time's rate), over the last 5 seconds.
|
||
min: u32,
|
||
low: u32,
|
||
}
|
||
/// The game's frame counter, recomputed twice a second.
|
||
frame-stats: func() -> frame-rate;
|
||
record zoom {
|
||
/// The field of view is divided by it: 1 is none, at most 10.
|
||
factor: f32,
|
||
/// Ease in and out through the game's field-of-view easing, as the
|
||
/// spyglass does, instead of at once.
|
||
smooth: bool,
|
||
/// Mouse turning is multiplied by it while zoomed: 1 keeps the
|
||
/// player's sensitivity, at least 0.05.
|
||
turn: f32,
|
||
}
|
||
/// Zooms the camera until the next call (`factor` 1 ends it), only
|
||
/// while playing: a screen opening or the spyglass's own view (which
|
||
/// wins) leave the camera vanilla's. With several mods, the strongest
|
||
/// zoom counts.
|
||
set-zoom: func(zoom: zoom);
|
||
record minimap {
|
||
corner: corner,
|
||
/// Its side in GUI pixels, 16 to 256.
|
||
size: f32,
|
||
/// World blocks per map pixel, 0.5 to 4; the map is 128 × 128 pixels.
|
||
blocks-per-pixel: f32,
|
||
/// The map turns with the player, who faces up, and an N on its rim
|
||
/// marks north. Otherwise north is up and the player's arrow turns.
|
||
rotate: bool,
|
||
/// Round, the corners left clear; otherwise square.
|
||
round: bool,
|
||
}
|
||
/// Shows a map of the world around the player in `corner`, stacked with
|
||
/// the game's overlays and the mods' panels, until the next call (none
|
||
/// hides it). It looks like a map item: vanilla's map colours with their
|
||
/// height shading and water depth, and the player's arrow. Underground,
|
||
/// under a roof and in the Nether it maps the floor around the player's
|
||
/// height. The game keeps the map's tiles and refreshes them within its
|
||
/// own frame budget; hidden, they are dropped. One map shows at a time:
|
||
/// the first loaded mod's.
|
||
set-minimap: func(minimap: option<minimap>);
|
||
record far-terrain {
|
||
/// How far the distant terrain reaches, in chunks: 64 to 1024. On
|
||
/// integrated graphics other than Apple's the game caps it lower.
|
||
distance: u32,
|
||
/// Coarser far tiles, as the Far Detail setting's Reduced: 2 to 4 ms
|
||
/// less GPU time per frame on integrated graphics.
|
||
reduced: bool,
|
||
}
|
||
/// Draws the world beyond the render distance as distant terrain, out to
|
||
/// `distance` chunks, until the next call (none ends it). The game builds
|
||
/// it on its own workers, from the chunks already loaded or saved and,
|
||
/// where nobody has been, from the world's generator; the simulation
|
||
/// distance doesn't change. Only worlds whose generator the game has
|
||
/// (single player and a LAN world you host) show it. The player's own LOD
|
||
/// Distance setting, when on, wins. One far terrain shows at a time: the
|
||
/// first loaded mod's.
|
||
set-far-terrain: func(far-terrain: option<far-terrain>);
|
||
}
|
||
|
||
/// What the game calls. Only the hooks `init` asks for are ever called.
|
||
interface events {
|
||
record registration {
|
||
/// Chat commands, without the slash. A name vanilla already uses is
|
||
/// refused (vanilla's command runs).
|
||
commands: list<string>,
|
||
/// Call `tick` every game tick (20 per second while playing).
|
||
tick: bool,
|
||
/// Call `frame` every rendered frame. Budget: 0.1 ms.
|
||
frame: bool,
|
||
/// Call `chat` for each chat line the player receives.
|
||
chat: bool,
|
||
}
|
||
/// Once, after loading. An error keeps the mod off and is shown.
|
||
init: func() -> result<registration, string>;
|
||
/// Each game tick while in a world, with the game's tick counter.
|
||
tick: func(tick: u64);
|
||
/// Each rendered frame, with the seconds since the last one.
|
||
frame: func(seconds: f32);
|
||
/// `/name args`: ok(some(text)) is shown as feedback, err(text) in red.
|
||
command: func(name: string, args: string) -> result<option<string>, string>;
|
||
/// Each chat line the player receives, as plain text.
|
||
chat: func(text: string);
|
||
}
|
||
|
||
/// Capability `recipes` (experimental): the recipes and items the game
|
||
/// knows, for recipe viewers. In single player and on Horizon's servers
|
||
/// they are the world's; on any other server they are vanilla 26.3's (a
|
||
/// client is sent no others), while `known` follows that server's book.
|
||
interface recipes {
|
||
/// An item of `items`, by its index there, with a count and the
|
||
/// components it has beyond the item's own, in vanilla's item syntax
|
||
/// (`[potion_contents={potion:"minecraft:swiftness"}]`), or "".
|
||
record stack {
|
||
item: u32,
|
||
count: u32,
|
||
components: string,
|
||
}
|
||
/// The station a recipe is made at.
|
||
enum kind {
|
||
crafting-shaped,
|
||
crafting-shapeless,
|
||
smelting,
|
||
blasting,
|
||
smoking,
|
||
campfire,
|
||
stonecutting,
|
||
smithing,
|
||
brewing,
|
||
}
|
||
record recipe {
|
||
/// `minecraft:oak_planks`.
|
||
id: string,
|
||
kind: kind,
|
||
/// The recipe book's tab (`crafting_building_blocks`,
|
||
/// `furnace_food`), or "".
|
||
category: string,
|
||
/// Recipes the book shows on one button share a group, or "".
|
||
group: string,
|
||
/// A shaped recipe's grid: `slots` holds its cells row by row.
|
||
/// 0 for the other kinds.
|
||
width: u32,
|
||
height: u32,
|
||
/// Each slot's stacks, any one of which fits; an empty list is an
|
||
/// empty cell. Cooking and stonecutting: the input. Smithing: the
|
||
/// template, the base, the addition. Brewing: the bottle, the
|
||
/// reagent.
|
||
slots: list<list<stack>>,
|
||
/// What it makes; several when that varies (a trim on any armour).
|
||
results: list<stack>,
|
||
/// Cooking: game ticks and experience; 0 for the other kinds.
|
||
cooking-time: u32,
|
||
experience: f32,
|
||
/// The recipe book's id for it (one per way the book shows it), as
|
||
/// `known` lists them; -1 for the kinds the book doesn't show
|
||
/// (campfire, stonecutting, smithing, brewing).
|
||
book-id: s32,
|
||
}
|
||
record item {
|
||
/// `minecraft:diamond`, `<mod>:<path>`.
|
||
id: string,
|
||
/// In the player's language.
|
||
name: string,
|
||
/// Its item tags, without the `#`.
|
||
tags: list<string>,
|
||
}
|
||
/// Every item, in the registry's order: `stack.item` indexes this list.
|
||
items: func() -> list<item>;
|
||
/// Every recipe: the book's in its order, then campfire, stonecutting,
|
||
/// smithing and brewing. They don't change while a world is open.
|
||
all: func() -> list<recipe>;
|
||
/// The `book-id`s of the recipes the player's recipe book holds now.
|
||
known: func() -> list<s32>;
|
||
/// Places a recipe in the open screen's grid as a click in the recipe
|
||
/// book does (`ServerboundPlaceRecipePacket`): the inventory's grid or
|
||
/// a crafting table. `all` places as many as the items allow, as with
|
||
/// Shift. Refused while no such screen is open, for a recipe the
|
||
/// player's book doesn't hold and for one that can't be placed; one
|
||
/// missing items shows as a ghost in the grid, as in the book.
|
||
place: func(book-id: s32, all: bool) -> result<_, string>;
|
||
/// Creative mode only: gives the player the stack, as taking it from
|
||
/// the creative inventory does (`ServerboundSetCreativeModeSlot`): the
|
||
/// selected slot first, then the hotbar, then the inventory. The count
|
||
/// is cut to one stack of the item (64, 16 or 1).
|
||
give: func(stack: stack) -> result<_, string>;
|
||
}
|
||
|
||
/// Capability `ui` (experimental): a recipe viewer's panel beside the
|
||
/// game's inventory and container screens (not the creative inventory), on
|
||
/// the right of the screen. The game draws it with its own widgets and
|
||
/// slots, pages it and keeps its search box; the mod says what it holds.
|
||
/// The first loaded mod with a panel shows.
|
||
interface browser {
|
||
/// An item to show: its id, a count (1 draws none) and its components
|
||
/// beyond the item's own, in vanilla's item syntax, or "".
|
||
record stack {
|
||
item: string,
|
||
count: u32,
|
||
components: string,
|
||
}
|
||
/// A click on an item of the panel, by its index in `set-items`'s list.
|
||
record click {
|
||
index: u32,
|
||
right: bool,
|
||
}
|
||
/// A key the mod registered (`input.register-key`) went down while the
|
||
/// pointer was on an item: a slot of the screen's, or the panel's.
|
||
record item-key {
|
||
key: string,
|
||
stack: stack,
|
||
}
|
||
/// A click on a slot of a recipe the panel shows (`set-recipes`).
|
||
record slot-click {
|
||
/// The recipe's index in `set-recipes`'s list and the slot's in
|
||
/// it; the results come after the slots.
|
||
recipe: u32,
|
||
slot: u32,
|
||
/// The stack the slot showed at that moment.
|
||
stack: stack,
|
||
right: bool,
|
||
}
|
||
variant event {
|
||
clicked(click),
|
||
/// The search box's text, each time the player changes it.
|
||
search(string),
|
||
key(item-key),
|
||
slot-clicked(slot-click),
|
||
/// The action button of the recipe at this index in
|
||
/// `set-recipes`'s list.
|
||
action(u32),
|
||
/// The player turned the recipes' page (`set-recipes`): the index
|
||
/// of the first recipe now shown.
|
||
page(u32),
|
||
/// The back button of `set-recipes`.
|
||
back,
|
||
/// The player picked the tab at this index of `set-recipes`' tabs.
|
||
tab(u32),
|
||
}
|
||
/// How the game lays out a recipe of `set-recipes`.
|
||
enum layout {
|
||
/// A crafting grid of `width` x `height` slots, an arrow and the
|
||
/// result.
|
||
grid,
|
||
/// One input over a flame, an arrow and the result (furnaces,
|
||
/// smokers, campfires).
|
||
cooking,
|
||
/// One input, an arrow and the result (the stonecutter).
|
||
single,
|
||
/// Template, base and addition, an arrow and the result.
|
||
smithing,
|
||
/// A potion and an ingredient, an arrow and the result.
|
||
brewing,
|
||
}
|
||
/// A recipe to show. Each slot, and the result, cycles through its
|
||
/// stacks, one a second; an empty slot shows nothing.
|
||
record recipe-view {
|
||
layout: layout,
|
||
/// The grid's size (`grid` only), at most 3 x 3.
|
||
width: u32,
|
||
height: u32,
|
||
slots: list<list<stack>>,
|
||
results: list<stack>,
|
||
/// A short line under the recipe (a cooking time), or "".
|
||
note: string,
|
||
/// The action button beside it: none, enabled, or shown greyed.
|
||
action: option<bool>,
|
||
}
|
||
/// The panel's items, at most 8192 (an empty list hides the panel).
|
||
set-items: func(items: list<stack>);
|
||
/// Shows these recipes in the panel instead of its items, as many as
|
||
/// fit a page, under `title` and a back button, with a row of `tabs`
|
||
/// (each an icon; `tab` is the selected one) between them; an empty
|
||
/// list of recipes shows the items again. At most 256 recipes and 16
|
||
/// tabs.
|
||
set-recipes: func(title: string, tabs: list<stack>, tab: u32, recipes: list<recipe-view>);
|
||
/// What the player did on the panel since the last poll, at most 256
|
||
/// kept.
|
||
poll: func() -> list<event>;
|
||
}
|
||
|
||
/// Capability `players`, server mods only (manifest `api = "0.3"`,
|
||
/// experimental): the players on the server, and messages to them.
|
||
interface players {
|
||
enum game-mode {
|
||
survival,
|
||
creative,
|
||
adventure,
|
||
spectator,
|
||
}
|
||
record player {
|
||
/// The player's UUID, `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`.
|
||
id: string,
|
||
name: string,
|
||
/// Feet position, in blocks.
|
||
x: f64,
|
||
y: f64,
|
||
z: f64,
|
||
/// `minecraft:overworld`, `minecraft:the_nether`, ...
|
||
dimension: string,
|
||
game-mode: game-mode,
|
||
/// Hit points as vanilla counts them: 20 is full health without effects.
|
||
health: f32,
|
||
}
|
||
/// The players online, in the order they joined.
|
||
online: func() -> list<player>;
|
||
/// A system message to one player, after the mod's name; false when no
|
||
/// player with that id is online. Up to 8 messages per call.
|
||
tell: func(player: string, text: string) -> bool;
|
||
/// A system message to every player and the server's log, after the
|
||
/// mod's name. Counts toward the same 8 per call.
|
||
broadcast: func(text: string);
|
||
}
|
||
|
||
/// Capability `world`, server mods only (manifest `api = "0.3"`,
|
||
/// experimental): the blocks of the server's loaded chunks, and changes to
|
||
/// them through the server's own block updates.
|
||
interface %world {
|
||
record block-change {
|
||
x: s32,
|
||
y: s32,
|
||
z: s32,
|
||
/// `minecraft:oak_log[axis=y]`; properties left out take the block's
|
||
/// defaults, `minecraft:stone` alone is the default state.
|
||
state: string,
|
||
}
|
||
/// The block state at a position in a dimension (`minecraft:overworld`,
|
||
/// ...), as `minecraft:oak_log[axis=y]`, as of this call: none where the
|
||
/// server has no chunk loaded there.
|
||
get-block: func(dimension: string, x: s32, y: s32, z: s32) -> option<string>;
|
||
/// The biome at a position (`minecraft:plains`, ...), as the server
|
||
/// stores it (one per 4 x 4 x 4 cell): none where no chunk is loaded.
|
||
get-biome: func(dimension: string, x: s32, y: s32, z: s32) -> option<string>;
|
||
/// Sets blocks as `/setblock` does: the old block entity removed,
|
||
/// neighbours and shapes updated, light, and every player told. Every
|
||
/// state is checked now (an error queues nothing); the changes are made
|
||
/// in order after this call returns, before that dimension's next tick;
|
||
/// one in a chunk not loaded then is skipped. At most 4096 per call.
|
||
set-blocks: func(dimension: string, blocks: list<block-change>) -> result<_, string>;
|
||
}
|
||
|
||
/// What the server calls in a server mod (manifest `api = "0.3"`,
|
||
/// experimental). Only the hooks `init` asks for are ever called. The player
|
||
/// in each event is as the server saw them when it happened.
|
||
interface server-events {
|
||
use players.{player};
|
||
|
||
record block-pos {
|
||
x: s32,
|
||
y: s32,
|
||
z: s32,
|
||
}
|
||
/// What an argument of a command takes. The server sends each command
|
||
/// and its arguments to players' games, whose chat box completes and
|
||
/// colours them as vanilla's; the server parses them before the mod's
|
||
/// `command` hook gets their values.
|
||
variant argument-kind {
|
||
/// One word: letters, digits and `_ - . +`.
|
||
word,
|
||
/// The rest of the line, spaces included: only last.
|
||
text,
|
||
/// A whole number, within the bounds given.
|
||
integer(tuple<option<s32>, option<s32>>),
|
||
/// A number with or without decimals.
|
||
number,
|
||
/// `true` or `false`.
|
||
boolean,
|
||
/// One online player: a name, or `@s` or `@p` (whoever typed it,
|
||
/// as vanilla picks for a player).
|
||
player,
|
||
/// A block position: three whole numbers, each one also `~` or
|
||
/// `~n`, relative to whoever typed it.
|
||
block-pos,
|
||
}
|
||
record argument {
|
||
/// Shown in the chat box's hint (`<height>`) and the key of its value.
|
||
name: string,
|
||
kind: argument-kind,
|
||
/// Optional arguments come after every required one.
|
||
optional: bool,
|
||
}
|
||
record server-command {
|
||
/// Without the slash. A name vanilla already uses is refused
|
||
/// (vanilla's command runs).
|
||
name: string,
|
||
/// In order; none: the command takes nothing after its name. Free
|
||
/// text is one optional `text` argument.
|
||
arguments: list<argument>,
|
||
}
|
||
/// An argument's value, parsed as its kind says.
|
||
variant argument-value {
|
||
word(string),
|
||
text(string),
|
||
integer(s32),
|
||
number(f64),
|
||
boolean(bool),
|
||
player(player),
|
||
block-pos(block-pos),
|
||
}
|
||
record registration {
|
||
/// Commands players may type.
|
||
commands: list<server-command>,
|
||
/// Call `tick` every server tick (20 per second).
|
||
tick: bool,
|
||
/// Call `player-joined` and `player-left`.
|
||
joins: bool,
|
||
/// Call `chat` for each chat message a player sends.
|
||
chat: bool,
|
||
/// Call `block-broken` and `block-placed`.
|
||
blocks: bool,
|
||
}
|
||
/// Once, after loading. An error keeps the mod off and is logged.
|
||
init: func() -> result<registration, string>;
|
||
/// Each server tick, with the server's tick counter.
|
||
tick: func(tick: u64);
|
||
/// A player joined the server; they are in `players.online` already.
|
||
player-joined: func(player: player);
|
||
/// A player left the server; they are no longer in `players.online`.
|
||
player-left: func(player: player);
|
||
/// A chat message a player sent, as plain text.
|
||
chat: func(player: player, text: string);
|
||
/// `/name args` typed by `player`: ok(some(text)) is their feedback,
|
||
/// err(text) their error in red. `values` holds each argument given, by
|
||
/// name, in order (an optional one left out isn't there); a line that
|
||
/// doesn't parse never reaches the mod: the player gets vanilla's error.
|
||
command: func(player: player, name: string, args: string, values: list<tuple<string, argument-value>>) -> result<option<string>, string>;
|
||
/// A player broke the block at `pos` in their dimension: `state` is what
|
||
/// was there, as `minecraft:oak_log[axis=y]`.
|
||
block-broken: func(player: player, pos: block-pos, state: string);
|
||
/// A player placed `state` at `pos` in their dimension.
|
||
block-placed: func(player: player, pos: block-pos, state: string);
|
||
}
|
||
|
||
/// A client mod: runs in the player's game.
|
||
world horizon-mod {
|
||
import log;
|
||
import game;
|
||
import chat;
|
||
import hud;
|
||
import storage;
|
||
import command;
|
||
export events;
|
||
}
|
||
|
||
/// A server mod (manifest `side = "server"`, `api = "0.3"`, experimental):
|
||
/// runs where the world is simulated, on the dedicated server, a LAN host
|
||
/// and single player's own server.
|
||
world horizon-server-mod {
|
||
import log;
|
||
import storage;
|
||
import players;
|
||
import %world;
|
||
export server-events;
|
||
}
|
||
|
||
/// A mod with widgets (capability `ui`): the SDK builds every mod against
|
||
/// this world. Mods built for `horizon-mod` alone (no `ui-events` export)
|
||
/// still load and simply hear nothing of their widgets.
|
||
world horizon-ui-mod {
|
||
include horizon-mod;
|
||
import ui;
|
||
import render;
|
||
import input;
|
||
import recipes;
|
||
import browser;
|
||
export ui-events;
|
||
}
|
||
|
||
/// The game's side of `horizon-ui-mod` beyond `horizon-mod`.
|
||
world ui-host {
|
||
import ui;
|
||
export ui-events;
|
||
}
|
||
|
||
/// The game's side of capability `render` (imports only).
|
||
world render-host {
|
||
import render;
|
||
import input;
|
||
}
|
||
|
||
/// The game's side of capability `recipes` (imports only).
|
||
world recipes-host {
|
||
import recipes;
|
||
import browser;
|
||
}
|