Guides

Recipes

On this page

Short, complete answers to common questions. Each one is a manifest line and a hook body. A Wasm guest answers with the same JSON.

An alias command

To make /day mean /time set day, register the name so it appears in the client's command list. Then rewrite it before the dispatcher sees it:

Manifest::new("aliases", "1")
    .with_command(Command::new("day", "Sets the time to day", 2))
    .hooking("command.day", Mode::Prepend)
fn hook(&self, _: &dyn Host, call: HookCall<'_>) -> Reply {
    let mut input = call.input.clone();
    input.set("name", "time");
    input.set("rest", "set day");
    Reply::Replace(input)
}

The rewritten command goes through the permission check like any other.

Refuse a command on your server

An override that answers false means the command failed, and the game's own command never runs:

.hooking("command.seed", Mode::Override)
// in hook(): Reply::Replace(Data::Bool(false))

Drop or rewrite a packet

Every packet is a hook point. To stop sending the boss bar:

.hooking("packet.play.clientbound.boss_event", Mode::Override)
// in hook(): Reply::Replace(Data::List(vec![]))   // write nothing

To change a serverbound packet before the server reads it, prepend and replace body (hex). To answer a serverbound packet yourself, override with {"handled": false, "reply": [[id, "hex"]]}.

Watch everything in a family

.hooking("packet.play.*", Mode::Append)   // every play packet, after the fact
.hooking("*", Mode::Append)               // every hook point there is

call.point tells you which point you were called at.

Offer a resource pack without a web host

With pack-host.enabled in rustmc.yml, the server serves the pack itself. Host it, then push the URL and hash you are handed:

let pack = host.host_pack("myplugin", "main", PackSource::Bytes(&zip))?;
host.push_pack(player, PACK_ID, &pack.url, &pack.sha1, false);

A pack under your plugin's own plugins/<plugin>/ directory is PackSource::File("packs/main.zip"); one the operator put in the served directory is PackSource::Directory, by name. See the pack host.

Hand a client mod the server's recipes

The game no longer sends a client the full recipe set (only the stonecutter's, and the recipe book's unlocked displays), so a client mod that shows recipes, such as a recipe viewer, has to be told. recipes gives you every recipe the server has loaded, the game's own and every enabled datapack's, as vanilla recipe JSON keyed by id. It comes a page at a time: offset counts from 0 in id order, and limit is capped at MAX_RECIPE_PAGE (500).

Each page carries the set's revision and its total. Read pages until you have total, and if the revision changes part-way, start again: the pages came from different sets.

use rustmc_plugin::MAX_RECIPE_PAGE;

fn all_recipes(host: &dyn Host) -> (String, Vec<(String, String)>) {
    'again: loop {
        let first = host.recipes(0, MAX_RECIPE_PAGE);
        let mut recipes = first.recipes;
        while recipes.len() < first.total {
            let page = host.recipes(recipes.len(), MAX_RECIPE_PAGE);
            if page.revision != first.revision || page.recipes.is_empty() {
                continue 'again;
            }
            recipes.extend(page.recipes);
        }
        return (first.revision, recipes);
    }
}

The set changes only when /reload or /datapack enable|disable changes a recipe, and then recipes_revision changes too. It is cheap, so hear tick and compare it with the revision you hold rather than reading the whole set again:

// .hearing(&["tick"]) in the manifest; `known: Mutex<String>` on the plugin.
fn on(&self, host: &dyn Host, event: &Event) -> Outcome {
    if let (Event::Tick { .. }, Ok(mut known)) = (event, self.known.lock()) {
        if host.recipes_revision() != *known {
            let (revision, recipes) = all_recipes(host);
            *known = revision;
            // send `recipes` to every player who has the mod
        }
    }
    Outcome::Continue
}

Send them on your mod's channel with send_payload. One message holds at most 32767 bytes, so split a large set across several.

A Wasm guest makes the same calls by name: call("recipes", json!({"offset": 0, "limit": 500})) and call("recipes_revision", json!({})). Each recipe in the answer is an [id, json] pair.

From docs/api/guide/recipes.md in the RustMC repository at bbd81209ad.