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)
map2da 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.5mat_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.5on, 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); }
}
}
}
}