315 lines
7.6 KiB
Markdown
315 lines
7.6 KiB
Markdown
|
|
# Kiln Script API Reference
|
|||
|
|
|
|||
|
|
Scripts are written in [Rhai](https://rhai.rs) 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.
|
|||
|
|
|
|||
|
|
```rhai
|
|||
|
|
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.
|
|||
|
|
|
|||
|
|
```rhai
|
|||
|
|
fn tick(dt) {
|
|||
|
|
if Queue.length() == 0 && !blocked(North) {
|
|||
|
|
move(North);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### `fn bump(id)`
|
|||
|
|
|
|||
|
|
Called when another entity walks into this object's cell.
|
|||
|
|
|
|||
|
|
- `id` is the bumper's `ObjectId` (an `i64`).
|
|||
|
|
- `id == -1` means the player bumped this object.
|
|||
|
|
|
|||
|
|
```rhai
|
|||
|
|
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. |
|
|||
|
|
|
|||
|
|
```rhai
|
|||
|
|
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.
|
|||
|
|
|
|||
|
|
```rhai
|
|||
|
|
fn tick(dt) {
|
|||
|
|
if !blocked(East) { move(East); }
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### `has_tag(tag) -> bool`
|
|||
|
|
|
|||
|
|
Returns `true` if this object has the named tag.
|
|||
|
|
|
|||
|
|
```rhai
|
|||
|
|
if has_tag("enemy") { say("I'm an enemy!"); }
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### `get_tags() -> Array`
|
|||
|
|
|
|||
|
|
Returns an array of strings listing every tag on this object.
|
|||
|
|
|
|||
|
|
```rhai
|
|||
|
|
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`.
|
|||
|
|
|
|||
|
|
```rhai
|
|||
|
|
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.
|
|||
|
|
|
|||
|
|
```rhai
|
|||
|
|
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.
|
|||
|
|
|
|||
|
|
```rhai
|
|||
|
|
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.
|
|||
|
|
|
|||
|
|
```rhai
|
|||
|
|
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.
|
|||
|
|
|
|||
|
|
```rhai
|
|||
|
|
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.
|
|||
|
|
|
|||
|
|
```rhai
|
|||
|
|
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.
|
|||
|
|
|
|||
|
|
```rhai
|
|||
|
|
fn init() {
|
|||
|
|
set_tile(1); // ☺
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### `log(msg)`
|
|||
|
|
|
|||
|
|
Appends a plain-text line to the game log. The message is a string.
|
|||
|
|
|
|||
|
|
```rhai
|
|||
|
|
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.
|
|||
|
|
|
|||
|
|
```rhai
|
|||
|
|
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.
|
|||
|
|
|
|||
|
|
```rhai
|
|||
|
|
// 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:
|
|||
|
|
|
|||
|
|
```rhai
|
|||
|
|
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 one `Delay(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
|
|||
|
|
|
|||
|
|
```rhai
|
|||
|
|
// 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();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|