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).
type | Extra keys | p[id] in apply |
|---|---|---|
int | min, max, value | integer |
float | min, max, value | float |
bool | value | boolean |
text | value | string |
choice | options: [...], value | the selected option string |
layer | layer_type | a map #{ kind, index, name } |
color | value ("#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:
| Function | Description |
|---|---|
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:
| Function | Description |
|---|---|
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
| Function | Returns |
|---|---|
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].
| Function | Description |
|---|---|
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.
| Function | Description |
|---|---|
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.
| Function | Returns |
|---|---|
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.
| Function | Description |
|---|---|
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).
| Function | Description |
|---|---|
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.
| Function | Description |
|---|---|
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.
| Function | Description |
|---|---|
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.
| Function | Description |
|---|---|
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.
| Function | Description |
|---|---|
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).
| Function | Description |
|---|---|
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
| Function | Description |
|---|---|
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_*.
| Function | Returns |
|---|---|
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
| Function | Description |
|---|---|
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); }
}
}
}
}