2026-06-12 23:35:40 -05:00
|
|
|
[world]
|
|
|
|
|
name = "Kiln Demo"
|
2026-06-13 15:33:04 -05:00
|
|
|
start = "start"
|
2026-06-12 23:35:40 -05:00
|
|
|
|
2026-06-13 01:25:58 -05:00
|
|
|
# Named Rhai scripts shared across all boards in this world.
|
|
|
|
|
# Objects reference a script by name via `script_name`.
|
|
|
|
|
[scripts]
|
|
|
|
|
greeter = """
|
2026-07-07 23:55:27 -05:00
|
|
|
fn init(me) {
|
|
|
|
|
log(`hello from object — player at ${Player.x}, ${Player.y}`);
|
2026-06-13 01:25:58 -05:00
|
|
|
set_tile(2); // change my glyph to ☻ — proves the write path
|
|
|
|
|
say("Hello there,\\ntraveller!");
|
2026-07-07 23:55:27 -05:00
|
|
|
log(`Player health: ${Player.health}`);
|
2026-06-13 01:25:58 -05:00
|
|
|
}
|
|
|
|
|
|
2026-07-11 11:47:45 -05:00
|
|
|
fn enter(me, dir) {
|
2026-07-07 23:55:27 -05:00
|
|
|
if Player.x == me.x && Player.y == me.y && !me.waiting {
|
2026-06-13 01:25:58 -05:00
|
|
|
say("Hey! Get offa me!");
|
2026-06-28 00:12:52 -05:00
|
|
|
me.delay(5.0);
|
2026-06-13 01:25:58 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
mover = """
|
2026-07-07 23:55:27 -05:00
|
|
|
fn init(me) {
|
2026-07-06 00:41:38 -05:00
|
|
|
log(`q: ${me.queue.length}`);
|
|
|
|
|
if Board.registry.get("dancer_x") != () {
|
|
|
|
|
let new_x = Board.registry.get("dancer_x");
|
|
|
|
|
let new_y = Board.registry.get("dancer_y");
|
2026-06-28 00:12:52 -05:00
|
|
|
if me.x != new_x || me.y != new_y {
|
2026-07-08 10:06:25 -05:00
|
|
|
teleport(me.id, new_x, new_y);
|
2026-06-13 18:43:29 -05:00
|
|
|
}
|
2026-06-13 17:58:04 -05:00
|
|
|
} else {
|
2026-07-06 00:41:38 -05:00
|
|
|
Board.registry.set("dancer_x", me.x);
|
|
|
|
|
Board.registry.set("dancer_y", me.y);
|
2026-06-28 00:12:52 -05:00
|
|
|
log(`Set reg to ${me.x} ${me.y}`);
|
2026-06-13 17:58:04 -05:00
|
|
|
}
|
|
|
|
|
}
|
2026-06-13 01:25:58 -05:00
|
|
|
// move() costs 250 ms, so the object steps ~4 cells/sec no matter how often
|
|
|
|
|
// tick() runs. Queue.length() avoids piling up moves; blocked() avoids walking
|
|
|
|
|
// into a wall (or another object's already-queued move this tick).
|
2026-07-10 01:20:26 -05:00
|
|
|
fn tick(me, dt) {
|
2026-06-28 00:12:52 -05:00
|
|
|
if !me.waiting { dance(me); }
|
2026-06-13 15:33:04 -05:00
|
|
|
}
|
2026-06-28 00:12:52 -05:00
|
|
|
fn dance(me) {
|
2026-06-13 15:33:04 -05:00
|
|
|
move(East);
|
|
|
|
|
move(South);
|
|
|
|
|
move(North);
|
|
|
|
|
move(East);
|
|
|
|
|
say("Hey!", 0.5);
|
2026-06-28 00:12:52 -05:00
|
|
|
delay(me, 0.5);
|
2026-06-13 15:33:04 -05:00
|
|
|
move(West);
|
|
|
|
|
move(South);
|
|
|
|
|
move(North);
|
|
|
|
|
move(West);
|
|
|
|
|
say("Ho!", 0.5);
|
2026-06-28 00:12:52 -05:00
|
|
|
me.delay(0.5);
|
2026-06-13 01:25:58 -05:00
|
|
|
}
|
2026-07-08 13:21:27 -05:00
|
|
|
// Fires when a solid (the player, an object, or a pushed crate) presses into this
|
|
|
|
|
// object; dir is the direction the bump came from.
|
|
|
|
|
fn bump(me, dir) {
|
|
|
|
|
log(`mover bumped from ${dir}`);
|
2026-06-13 01:25:58 -05:00
|
|
|
say("Ow!");
|
|
|
|
|
}
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
muffin = """
|
2026-07-08 13:21:27 -05:00
|
|
|
fn bump(me, _dir) {
|
2026-06-13 01:25:58 -05:00
|
|
|
scroll([
|
|
|
|
|
"You find a small muffin on the ground, your favorite kind.",
|
|
|
|
|
"It smells of cinnamon and warm mornings.",
|
|
|
|
|
["eat", "Pick up the muffin and eat it."],
|
|
|
|
|
["ignore", "This is obviously a trap — ignore it."]
|
|
|
|
|
]);
|
|
|
|
|
}
|
2026-07-07 23:55:27 -05:00
|
|
|
fn eat(me) {
|
2026-06-25 00:04:42 -05:00
|
|
|
say("Yeah it was poisoned.");
|
|
|
|
|
alter_health(-2);
|
2026-06-13 01:25:58 -05:00
|
|
|
}
|
|
|
|
|
fn ignore() {
|
|
|
|
|
log("You walk away from the muffin. Wise.");
|
|
|
|
|
say("Suspicious...");
|
|
|
|
|
}
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
noticeboard = """
|
2026-07-08 13:21:27 -05:00
|
|
|
fn bump(me, _dir) {
|
2026-06-13 01:25:58 -05:00
|
|
|
scroll([
|
|
|
|
|
" TOWN NOTICE BOARD",
|
|
|
|
|
" ",
|
|
|
|
|
"ROAD CLOSURE — The eastern road through Miller's Crossing remains closed",
|
|
|
|
|
"following flood damage to the main bridge. Repairs are underway but travellers",
|
|
|
|
|
"should expect delays of at least three weeks. The north fork via Ashwell is",
|
|
|
|
|
"passable, though it adds half a day to the journey.",
|
|
|
|
|
" ",
|
|
|
|
|
"LOST ANIMAL — One grey mule, answers to the name Pepper. Last seen grazing",
|
|
|
|
|
"near the old mill on the morning of the 14th. The animal has a notched left",
|
|
|
|
|
"ear and wears a blue rope halter. Any information to the innkeeper; a reward",
|
|
|
|
|
"of two silver pieces is offered for her safe return.",
|
|
|
|
|
" ",
|
|
|
|
|
"POSITION AVAILABLE — Captain Aldric of the town garrison seeks an experienced",
|
|
|
|
|
"wilderness guide for an expedition into the Darkwood. Duration: ten to fourteen",
|
|
|
|
|
"days. Pay is good and provisions will be supplied. Candidates must present",
|
|
|
|
|
"themselves at the barracks before the evening bell. Some risk is involved and",
|
|
|
|
|
"the captain asks that only those in sound health and good standing apply.",
|
|
|
|
|
" ",
|
|
|
|
|
"HARVEST FESTIVAL — The annual Harvest Festival will be held on the last",
|
|
|
|
|
"Saturday of the month. The market square will be closed to carts from dawn.",
|
|
|
|
|
"Stall permits must be obtained from the clerk's office no later than Thursday.",
|
|
|
|
|
"There will be music, a pie competition, and the traditional lantern parade at",
|
|
|
|
|
"dusk. All residents are warmly encouraged to attend.",
|
|
|
|
|
" ",
|
|
|
|
|
" Posted by order of the Town Council.",
|
2026-06-28 00:12:52 -05:00
|
|
|
" Unauthorized notices will be removed.",
|
2026-06-13 01:25:58 -05:00
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
"""
|
|
|
|
|
|
2026-06-13 13:59:13 -05:00
|
|
|
bookshelf = """
|
2026-07-08 13:21:27 -05:00
|
|
|
fn bump(me, _dir) {
|
2026-06-13 13:59:13 -05:00
|
|
|
scroll([
|
|
|
|
|
" THE BOOKSHELF",
|
|
|
|
|
" ",
|
|
|
|
|
"A well-worn shelf packed with old volumes. You scan the spines:",
|
|
|
|
|
" ",
|
|
|
|
|
" 'Practical Herbalism and Country Remedies'",
|
|
|
|
|
" 'A History of the Seven Kingdoms, Vol. II'",
|
|
|
|
|
" 'Monsters of the Northern Reach'",
|
|
|
|
|
" 'The Innkeeper's Handbook'",
|
|
|
|
|
" 'Common Stars and Their Names'",
|
|
|
|
|
" 'Letters to No One in Particular' (handwritten)",
|
|
|
|
|
" ",
|
|
|
|
|
"One has a pressed flower tucked in as a bookmark.",
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
fireplace = """
|
2026-07-08 13:21:27 -05:00
|
|
|
fn bump(me, _dir) {
|
2026-06-13 13:59:13 -05:00
|
|
|
say("The fire crackles warmly.\\nYou feel at ease.");
|
|
|
|
|
}
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
chest = """
|
2026-07-08 13:21:27 -05:00
|
|
|
fn bump(me, _dir) {
|
2026-06-13 13:59:13 -05:00
|
|
|
scroll([
|
|
|
|
|
" THE CHEST",
|
|
|
|
|
" ",
|
|
|
|
|
"A sturdy oak chest with iron fittings. The latch is unlocked.",
|
|
|
|
|
" ",
|
|
|
|
|
"Inside you find:",
|
|
|
|
|
"",
|
|
|
|
|
"- A wool blanket, slightly moth-eaten",
|
|
|
|
|
"- Three copper coins",
|
|
|
|
|
"- A half-empty bottle of something medicinal",
|
|
|
|
|
"- A child's wooden toy horse",
|
|
|
|
|
"- A folded letter, sealed but never sent",
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
yammerer = """
|
2026-07-07 23:55:27 -05:00
|
|
|
fn tick(me, dt) {
|
2026-06-28 00:12:52 -05:00
|
|
|
if !me.waiting {
|
2026-06-13 13:59:13 -05:00
|
|
|
say("blahblahblah");
|
|
|
|
|
delay(1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"""
|
|
|
|
|
|
2026-06-21 22:04:10 -05:00
|
|
|
shifter = """
|
2026-07-07 23:55:27 -05:00
|
|
|
fn tick(me, dt) {
|
2026-06-28 00:12:52 -05:00
|
|
|
let x = me.x;
|
|
|
|
|
let y = me.y;
|
2026-06-21 22:04:10 -05:00
|
|
|
|
2026-06-28 00:12:52 -05:00
|
|
|
if !me.waiting {
|
2026-06-21 22:04:10 -05:00
|
|
|
shift([[x-1, y], [x, y-1], [x+1, y], [x, y+1]]);
|
|
|
|
|
delay(0.25);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"""
|
|
|
|
|
|
2026-07-10 23:16:28 -05:00
|
|
|
# A trigger script: no glyph, not solid — it just watches for the player stepping
|
|
|
|
|
# onto its cell and logs. Triggers are placed via [[boards.NAME.triggers]].
|
|
|
|
|
tripwire = """
|
|
|
|
|
fn tick(me, dt) {
|
|
|
|
|
if Player.x == me.x && Player.y == me.y && !me.waiting {
|
|
|
|
|
log("tripwire: the player crossed here");
|
|
|
|
|
me.delay(2.0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"""
|
|
|
|
|
|
2026-06-12 23:35:40 -05:00
|
|
|
[boards.start.map]
|
2026-05-16 01:50:05 -05:00
|
|
|
name = "Starting Room"
|
|
|
|
|
width = 60
|
|
|
|
|
height = 25
|
2026-07-10 23:16:28 -05:00
|
|
|
# A single procedural grass floor across the whole board (the old mixed
|
|
|
|
|
# grass/dirt/stone/water floor is not representable with one floor attribute).
|
|
|
|
|
floor = { generator = "grass" }
|
2026-05-16 01:50:05 -05:00
|
|
|
|
2026-07-10 23:16:28 -05:00
|
|
|
# The single grid: all solids and most non-solids (terrain, the player, objects and
|
|
|
|
|
# the portal). A space is always a transparent empty cell, so the floor shows through.
|
|
|
|
|
[boards.start.grid]
|
2026-05-16 01:50:05 -05:00
|
|
|
content = """
|
|
|
|
|
############################################################
|
|
|
|
|
# #
|
2026-06-21 22:04:10 -05:00
|
|
|
# o #
|
|
|
|
|
# oS #
|
2026-05-16 01:50:05 -05:00
|
|
|
# #
|
2026-06-13 16:24:29 -05:00
|
|
|
# 1 #
|
2026-07-08 10:06:25 -05:00
|
|
|
# )######( B #
|
2026-05-16 01:50:05 -05:00
|
|
|
# # #
|
2026-07-08 10:06:25 -05:00
|
|
|
# +++ # v #
|
2026-05-16 01:50:05 -05:00
|
|
|
# # #
|
2026-06-06 20:01:07 -05:00
|
|
|
# | #
|
2026-05-16 01:50:05 -05:00
|
|
|
# #
|
2026-06-15 23:35:18 -05:00
|
|
|
# V oo G - @ #
|
2026-06-21 18:27:45 -05:00
|
|
|
# /# #
|
|
|
|
|
# t #
|
2026-05-16 01:50:05 -05:00
|
|
|
# #
|
2026-06-13 21:33:38 -05:00
|
|
|
# >| #
|
2026-05-16 01:50:05 -05:00
|
|
|
# #
|
2026-06-13 21:33:38 -05:00
|
|
|
# - #
|
|
|
|
|
# ^ #
|
2026-06-11 18:53:52 -05:00
|
|
|
# M #
|
2026-05-16 01:50:05 -05:00
|
|
|
# #
|
|
|
|
|
# #
|
|
|
|
|
# #
|
|
|
|
|
############################################################
|
|
|
|
|
"""
|
2026-07-10 23:16:28 -05:00
|
|
|
[boards.start.grid.palette]
|
2026-06-15 23:35:18 -05:00
|
|
|
"#" = { kind = "wall", tile = "#", fg = "#808080", bg = "#606060" }
|
|
|
|
|
"+" = { kind = "banana", tile = "#", fg = "#808080", bg = "#606060" } # unknown kind -> ErrorBlock demo
|
|
|
|
|
"o" = { kind = "crate", tile = 254, fg = "#aaaaaa", bg = "#000000" }
|
|
|
|
|
"-" = { kind = "hcrate", tile = 29, fg = "#aaaaaa", bg = "#000000" }
|
|
|
|
|
"|" = { kind = "vcrate", tile = 18, fg = "#aaaaaa", bg = "#000000" }
|
|
|
|
|
">" = { kind = "pusher_east" }
|
|
|
|
|
"^" = { kind = "pusher_north" }
|
|
|
|
|
"@" = { kind = "player" }
|
|
|
|
|
"1" = { kind = "portal", name = "to_house", target_map = "house", target_entry = "from_start" }
|
|
|
|
|
"G" = { kind = "object", tile = "#", fg = "#aa3333", bg = "#000000", solid = false, script_name = "greeter" }
|
|
|
|
|
"V" = { kind = "object", tile = 1, fg = "#33aa33", bg = "#000000", script_name = "mover" }
|
|
|
|
|
"M" = { kind = "object", tile = 15, fg = "#ffdd88", bg = "#000000", solid = true, name = "muffin", script_name = "muffin" }
|
|
|
|
|
"B" = { kind = "object", tile = 240, fg = "#cc9944", bg = "#000000", solid = true, name = "noticeboard", script_name = "noticeboard" }
|
2026-06-21 22:04:10 -05:00
|
|
|
"S" = { kind = "object", tile = 240, fg = "#cc9944", bg = "#000000", solid = true, name = "shifter", script_name = "shifter" }
|
2026-06-21 18:27:45 -05:00
|
|
|
"/" = { kind = "spinner_cw" }
|
|
|
|
|
"t" = { kind = "gem" }
|
2026-07-08 10:06:25 -05:00
|
|
|
"v" = { kind = "transporter_south" }
|
|
|
|
|
")" = { kind = "transporter_east" }
|
|
|
|
|
"(" = { kind = "transporter_west" }
|
2026-05-30 21:22:36 -05:00
|
|
|
|
2026-07-10 23:16:28 -05:00
|
|
|
# Triggers: invisible, non-solid, script-only objects placed off the grid. This one
|
|
|
|
|
# watches for the player and logs when they reach its cell.
|
|
|
|
|
[[boards.start.triggers]]
|
|
|
|
|
x = 40
|
|
|
|
|
y = 12
|
|
|
|
|
script_name = "tripwire"
|
2026-06-13 16:24:29 -05:00
|
|
|
|
2026-06-13 13:59:13 -05:00
|
|
|
[boards.house.map]
|
|
|
|
|
name = "The House"
|
|
|
|
|
width = 60
|
|
|
|
|
height = 25
|
2026-07-10 23:16:28 -05:00
|
|
|
# A single fixed floor glyph across the whole board.
|
|
|
|
|
floor = { tile = 176, fg = "#888888", bg = "#444444" }
|
2026-06-13 13:59:13 -05:00
|
|
|
|
2026-07-10 23:16:28 -05:00
|
|
|
# The single grid: terrain, the player, objects and the return portal.
|
|
|
|
|
[boards.house.grid]
|
2026-06-13 13:59:13 -05:00
|
|
|
content = """
|
|
|
|
|
############################################################
|
|
|
|
|
# #
|
|
|
|
|
# K ###F### #
|
|
|
|
|
# # # #
|
|
|
|
|
# ####### #
|
2026-06-13 16:24:29 -05:00
|
|
|
# 1 #
|
2026-06-13 13:59:13 -05:00
|
|
|
# #
|
|
|
|
|
# ######### T #
|
|
|
|
|
# # # T #
|
|
|
|
|
# ######### #
|
|
|
|
|
# T T #
|
|
|
|
|
# #
|
|
|
|
|
# @ T #
|
|
|
|
|
# #
|
|
|
|
|
# T #
|
|
|
|
|
# ######C###### #
|
|
|
|
|
# T # # #
|
|
|
|
|
# ############# #
|
|
|
|
|
# #
|
|
|
|
|
# #
|
|
|
|
|
# #
|
|
|
|
|
# #
|
|
|
|
|
# #
|
|
|
|
|
# #
|
|
|
|
|
############################################################
|
|
|
|
|
"""
|
2026-07-10 23:16:28 -05:00
|
|
|
[boards.house.grid.palette]
|
2026-06-15 23:35:18 -05:00
|
|
|
"#" = { kind = "wall", tile = 178, fg = "#888888", bg = "#555555" }
|
|
|
|
|
"@" = { kind = "player" }
|
|
|
|
|
"1" = { kind = "portal", name = "from_start", target_map = "start", target_entry = "to_house" }
|
|
|
|
|
"K" = { kind = "object", tile = 240, fg = "#aa7733", bg = "#3d1c00", solid = true, name = "bookshelf", script_name = "bookshelf" }
|
|
|
|
|
"F" = { kind = "object", tile = 15, fg = "#ff8800", bg = "#220000", solid = true, name = "fireplace", script_name = "fireplace" }
|
|
|
|
|
"C" = { kind = "object", tile = 240, fg = "#ccaa44", bg = "#222200", solid = true, name = "chest", script_name = "chest" }
|
|
|
|
|
"T" = { kind = "object", tile = 1, fg = "#99ff44", bg = "#000000", solid = true, script_name = "yammerer" }
|
2026-06-13 13:59:13 -05:00
|
|
|
|