Skip to main content

Scripting API

Scripts are Rhai files (.rhai) that edit the current project — generate terrain, paint layers, place objects — with an optional parameter form shown in the Scripts panel.

Script structure​

A script may define four functions; only apply is required.

// Title and description shown in the Scripts panel (optional).
fn meta() {
#{
name: "My script",
description: "What this script does.",
undo: true, // capture undo history (default true; false is faster)
iterative: false, // if true, the panel shows an iteration count (see below)
}
}

// Input form (optional). See "Parameters".
fn params() {
[
#{ id: "radius", label: "Radius", type: "int", min: 1, max: 64, value: 8 },
]
}

// Output ports (optional). See "Outputs".
fn outputs() {
[
#{ id: "cells", label: "Cells", type: "map2d" },
]
}

// Required. `map` is the editing API, `p` holds the form values keyed by id
// (plus `p.iteration`, the 0-based pass index in iterative mode).
fn apply(map, p) {
let r = p.radius;
}

Iterative scripts​

With iterative: true in meta(), the Scripts panel shows an iteration count (1..100000) and apply runs that many times. p.iteration is the current 0-based pass index. Between passes, each map2d output is fed back into the map2d input of the same id — so a script can carry state from one iteration to the next.

Parameters​

Each entry from params() is an object map. The initial value goes in value (default is a reserved word in Rhai).

typeExtra keysp[id] in apply
intmin, max, valueinteger
floatmin, max, valuefloat
boolvalueboolean
textvaluestring
choiceoptions: [...], valuethe selected option string
layerlayer_typea map #{ kind, index, name }
colorvalue ("#rrggbb" or [r, g, b])a map #{ r, g, b } (0–255)
map2d—a read-only 2D luminance map (see below)

For int and float, omitting min/max shows a free number input instead of a slider.

layer_type is "material", "cover", "biome", "object", "resource", "feature", "combined", "annotation" or "any". Pass p[id].index to the editing functions below (every kind except materials and biomes is 1-based).

A map2d input is fed through the Script node of the node editor (connect any scalar field to its port); run from the Scripts panel, it holds the tool constraints instead (1 where the cell satisfies them, 0 elsewhere).

A map2d value exposes:

FunctionDescription
m.width(), m.height()dimensions (width() == 0 means no map provided)
m.get(x, z)value at the cell (0.0 out of bounds)
m.set(x, z, v)set the value (float or int)
m.neighbor_count(threshold)new map2d where each cell holds the count of its 8 neighbours whose value is >= threshold
m.neighbor_count_equal(value)new map2d counting the 8 neighbours whose value equals value
m.neighbor_count_equal(value, wrap)same, with wrap = true for toroidal edges (cells at borders wrap around)
new_map2d(w, h)free function creating a zero-filled map

Outputs​

outputs() declares output ports (same object-map shape as params(), with type "map2d", "color" or "material"). Write them in apply:

FunctionDescription
map.set_output(id, map2d)write a map2d output
map.set_output_color(id, r, g, b)write a color output (0–255)
map.set_output_material(id, index)write a material index output

Outputs feed the Script node's output ports in the node editor, and drive the iteration feedback loop (see "Iterative scripts").

Script node​

In the node editor, the Script node exposes a script's inputs directly in the graph: int, float, color, map2d and material layer inputs become named input ports (with an inline widget as fallback while unconnected); the other kinds are edited inside the node. The node is never evaluated by the live preview nor by Apply to map — the script only runs from the node's own button.

The map API​

Coordinates are x (west→east) and z (north→south), origin at 0,0. Out-of-bounds coordinates are ignored.

Dimensions & bounds​

FunctionReturns
map.width()map width in cells
map.height()map height in cells
map.water_level()global water level
map.min_height()minimum allowed height
map.max_height()maximum allowed height

Heights​

Heights are f64 (sub-block precision), clamped to [min_height, max_height].

FunctionDescription
map.get_height(x, z)height at the cell
map.set_height(x, z, h)set the height
map.add_height(x, z, dh)add dh to the height
map.slope(x, z)local slope in degrees

Surface material​

0-based index into the material palette.

FunctionDescription
map.get_surface(x, z)material index
map.set_surface(x, z, mat)set the material index
map.set_surface_mask(mask, mat_on)paint the whole surface from a map2d: cell >= 0.5 → mat_on, else 0
map.set_surface_mask(mask, mat_on, mat_off)same, with an explicit mat_off material for cells < 0.5
map.set_surface_mask(mask, mat_on, x0, z0, x1, z1)only paint the [x0..=x1, z0..=z1] rectangle

A single set_surface_mask call takes one lock for the whole map, far faster than a per-cell set_surface loop.

Selection​

Exposes the active tool selection so a script can restrict its work to the selected region without changing the map size.

FunctionReturns
map.has_selection()true if a non-empty "only" selection is active
map.selection_bounds()[x0, z0, x1, z1] bounding box (inclusive); the whole map if no usable selection

Combine selection_bounds() with the rectangle form of set_surface_mask to process only the active area.

Biome​

0-based index into the biome list.

FunctionDescription
map.get_biome(x, z)biome index
map.set_biome(x, z, id)set the biome index

Ground covers​

1-based, stackable, alpha 0..=255 (default 255).

FunctionDescription
map.add_cover(x, z, id)add a cover (alpha 255)
map.add_cover(x, z, id, alpha)add a cover with alpha
map.remove_cover(x, z, id)remove a cover

Object layers​

1-based, stackable; same signatures as covers.

FunctionDescription
map.add_object(x, z, id)add an object (alpha 255)
map.add_object(x, z, id, alpha)add an object with alpha
map.remove_object(x, z, id)remove an object

Resource layers​

1-based, stackable; same signatures as covers.

FunctionDescription
map.add_resource(x, z, id)add a resource (alpha 255)
map.add_resource(x, z, id, alpha)add a resource with alpha
map.remove_resource(x, z, id)remove a resource

Feature layers​

1-based, stackable; same signatures as covers.

FunctionDescription
map.add_feature(x, z, id)add a feature (alpha 255)
map.add_feature(x, z, id, alpha)add a feature with alpha
map.remove_feature(x, z, id)remove a feature

Combined layers​

1-based, stackable; same signatures as covers.

FunctionDescription
map.add_combined(x, z, id)add a combined layer (alpha 255)
map.add_combined(x, z, id, alpha)add a combined layer with alpha
map.remove_combined(x, z, id)remove a combined layer

Annotations​

1-based, one color per cell (id = index into the 16 fixed annotation colors).

FunctionDescription
map.add_annotation(x, z, id)paint the annotation color (replaces any)
map.remove_annotation(x, z)clear the cell's annotation
map.set_annotation_mask(mask, on)paint from a map2d: cell >= 0.5 → on, else clear
map.set_annotation_mask(mask, on, off)same, with an explicit off color for cells < 0.5 (0 clears)
map.set_annotation_mask(mask, on, off, x0, z0, x1, z1)same, restricted to the [x0..=x1, z0..=z1] rectangle
map.set_annotation_ids(states, x0, z0)stamp per-cell annotation ids: each cell of states paints id round(value) (0 clears) at (x0+lx, z0+lz)

Per-cell water​

FunctionDescription
map.get_water(x, z)local water level (falls back to the global level)
map.set_water(x, z, level)set the local water level
map.clear_water(x, z)remove the local water

Lookups by name​

Return the index of a palette entry, or -1 if not found. Cover and object indices are 1-based, matching add_*/remove_*.

FunctionReturns
map.material_index(name)0-based material index
map.cover_index(name)1-based cover index
map.biome_index(name)0-based biome index
map.object_index(name)1-based object index
map.resource_index(name)1-based resource index
map.feature_index(name)1-based feature index
map.combined_index(name)1-based combined layer index
map.annotation_index(name)1-based annotation color index

Palette counts​

map.material_count(), map.cover_count(), map.biome_count(), map.object_count(), map.resource_count(), map.feature_count(), map.combined_count(), map.annotation_count().

Nearest material​

map.nearest_material(r, g, b) returns the palette index of the material closest (CIELAB distance) to the given sRGB color, or -1 if the palette is empty. Like the "Nearest block" node, a material's color is the weighted average of its blocks' texture colors (falling back to the preview color if no texture is resolvable).

map.nearest_block_material(r, g, b) searches all vanilla full-cube blocks instead (like the Nearest block node), reuses or creates the matching palette entry and returns its index (-1 if the block color table is unavailable or the palette is full).

Progress & cancellation​

FunctionDescription
map.report_progress(current, total, message)update the progress bar
map.is_cancelled()true if the user cancelled

print(...) and debug(...) go to the application log.

Example​

Raise a cone of terrain and paint its surface, with a radius input:

fn meta() {
#{ name: "Raise cone", description: "Raises a cone and paints stone." }
}

fn params() {
[
#{ id: "radius", label: "Radius", type: "int", min: 1, max: 128, value: 32 },
#{ id: "peak", label: "Peak height", type: "float", min: 0.0, max: 256.0, value: 64.0 },
]
}

fn apply(map, p) {
let r = p.radius;
let cx = map.width() / 2;
let cz = map.height() / 2;
let stone = map.material_index("minecraft:stone");

for dz in -r..=r {
if map.is_cancelled() { return; }
map.report_progress(dz + r, 2 * r, "Raising terrain...");
for dx in -r..=r {
let dist = ((dx * dx + dz * dz) as float).sqrt();
if dist <= r {
map.add_height(cx + dx, cz + dz, p.peak * (1.0 - dist / r));
if stone >= 0 { map.set_surface(cx + dx, cz + dz, stone); }
}
}
}
}