7.6 KiB
Kiln Script API Reference
Scripts are written in Rhai and live in the [scripts] section of a .toml map
file. Each scripted object references a script by name via script_name. A script may define up to
three lifecycle hooks; any or all may be omitted.
Lifecycle hooks
fn init()
Called once for every scripted object after the entire map is loaded, before the first frame. Use it to set up initial tile appearance, log a startup message, etc.
fn init() {
set_tile(42);
log("ready");
}
fn tick(dt)
Called every frame. dt is the elapsed time since the last tick, in seconds (a f64). Movement
and other timed actions pace themselves through the queue; you do not need to track time manually
for simple walking patterns.
fn tick(dt) {
if Queue.length() == 0 && !blocked(North) {
move(North);
}
}
fn bump(id)
Called when another entity walks into this object's cell.
idis the bumper'sObjectId(ani64).id == -1means the player bumped this object.
fn bump(id) {
if id == -1 {
say("Hey! Watch it.");
} else {
log(`object ${id} bumped me`);
}
}
Constants in scope
These are pre-set in every object's scope and cannot be reassigned.
| Name | Type | Description |
|---|---|---|
Board |
Board |
Read-only handle to the game world. |
Queue |
Queue |
Handle to this object's own pending-action queue. |
North |
Direction |
Cardinal direction: up (y − 1). |
South |
Direction |
Cardinal direction: down (y + 1). |
East |
Direction |
Cardinal direction: right (x + 1). |
West |
Direction |
Cardinal direction: left (x − 1). |
MY_ID |
i64 |
This object's stable ObjectId. Use with set_tag. |
Read functions
Board getters
Access via Board.<property>:
| Property | Type | Description |
|---|---|---|
Board.player_x |
i64 |
Player's current column (0-indexed). |
Board.player_y |
i64 |
Player's current row (0-indexed). |
Board.width |
i64 |
Board width in cells. |
Board.height |
i64 |
Board height in cells. |
let dist_x = (Board.player_x - 30).abs();
blocked(dir) -> bool
Returns true if moving in dir from this object's current cell would be impossible — because
the target cell is out of bounds, holds a solid, or another object has already queued a move there.
Note: the object's own queued moves are not yet on the board queue when blocked is called, so
an object cannot accidentally block itself.
fn tick(dt) {
if !blocked(East) { move(East); }
}
has_tag(tag) -> bool
Returns true if this object has the named tag.
if has_tag("enemy") { say("I'm an enemy!"); }
get_tags() -> Array
Returns an array of strings listing every tag on this object.
for t in get_tags() { log(t); }
objects_with_tag(tag) -> Array
Returns an array of i64 object IDs for every object on the board that currently carries tag.
let enemies = objects_with_tag("enemy");
log(`there are ${enemies.len()} enemies`);
my_name() -> String
Returns this object's name string, or "" if the object has no name.
log(`I am ${my_name()}`);
object_id_for_name(name) -> i64
Looks up an object by its name and returns its ObjectId. Returns 0 if no object has that name.
0 is never a valid id, so it can be used as a null check.
let door_id = object_id_for_name("door");
if door_id != 0 {
set_tag(door_id, "open", true);
}
Write functions
Scripts do not mutate the world directly. Each write function appends an action to this object's output queue. Actions are drained to the shared board queue between script calls and resolved by the engine after the batch.
move(dir)
Enqueues a move one cell in dir. Also inserts a Delay of 0.25 s after the move, limiting this
object to ~4 moves per second regardless of frame rate. A blocked move still costs the cooldown.
fn tick(dt) {
if !blocked(South) { move(South); }
}
delay(secs)
Inserts an explicit pause of secs seconds into this object's queue. Adjacent delays are merged
(you never get two consecutive Delay entries). Use it to slow down an action sequence or pause
between speech bubbles.
fn init() {
say("One…");
delay(2.0);
say("Two!");
now(); // promote the last say() past the delay
}
now()
Moves the most recently enqueued action to the front of the queue, bypassing any pending
delays. Useful after a zero-cost action (like say or log) to make it visible immediately even
if a move delay is in progress.
fn bump(id) {
say("Ouch!");
now(); // show the bubble right away
}
set_tile(n)
Sets this object's glyph tile index to n (an integer). The tile index corresponds to a CP437
code point when using the default kiln font.
fn init() {
set_tile(1); // ☺
}
log(msg)
Appends a plain-text line to the game log. The message is a string.
log(`player is at ${Board.player_x}, ${Board.player_y}`);
say(msg)
Displays a speech bubble above this object containing msg. The bubble lasts 3 seconds. If the
object already has an active bubble, it is replaced (text and timer reset). Zero-cost; combine with
now() to surface it immediately from a bump or init hook.
fn bump(id) {
say("Hey!");
now();
}
set_tag(target_id, tag, present)
Adds (present = true) or removes (present = false) a tag on any object identified by
target_id. Use MY_ID to mutate the calling object's own tags.
// Add a tag to self
set_tag(MY_ID, "activated", true);
// Remove a tag from another object by name
let lever_id = object_id_for_name("lever");
set_tag(lever_id, "pulled", true);
Queue object
Accessed via the Queue constant in scope. Provides inspection and control of this object's own
pending-action queue.
| Method | Returns | Description |
|---|---|---|
Queue.length() |
i64 |
Number of actions currently in the queue. |
Queue.clear() |
— | Discard all pending actions. |
Use Queue.length() == 0 in tick to avoid queuing new moves while a previous one is in flight:
fn tick(dt) {
if Queue.length() == 0 {
if !blocked(East) { move(East); }
else if !blocked(West) { move(West); }
}
}
Rate limiting and timing
move(dir)costs 0.25 s of queue delay (~4 moves/second max).delay(secs)adds an arbitrary pause.now()can bypass a delay for zero-cost actions already in the queue.Queue.clear()cancels everything, including pending delays.- Adjacent delays are always merged: two
delay(0.5)calls become oneDelay(1.0)entry.
Error handling
Script errors (compile-time or runtime) are logged to the game log as plain-text lines and do not
crash the game. A script that throws an exception on tick will log the error and resume normally
on the next frame.
Full example
// A guard that patrols east/west and greets the player when bumped.
fn init() {
set_tile(2);
log(`${my_name()} standing watch`);
}
fn tick(dt) {
if Queue.length() > 0 { return; } // still moving
if !blocked(East) {
move(East);
} else if !blocked(West) {
move(West);
}
// Stuck in a corner — do nothing this tick.
}
fn bump(id) {
if id == -1 {
say("Halt! Who goes there?");
now();
} else {
say("Watch where you're going!");
now();
}
}