by_player flag on bump and delay flag on move
This commit is contained in:
+1
-1
@@ -92,7 +92,7 @@ The core types that were formerly monolithic in `game.rs` are now split into foc
|
||||
**`kiln-core/src/script.rs`** — Rhai scripting **host** (the script-facing types live in `kiln-core/src/api/`, below). The script-author's reference is [`docs/script-api.md`](../docs/script-api.md).
|
||||
- `Registerable` trait — `fn register(engine, log_sink)`: a type's hook for installing itself (Rhai type name + getters/methods) on the `Engine`. Implemented by every `api` type plus `Glyph`/`Keyring`.
|
||||
- `ScriptHost` — owns the Rhai `Engine`, the compiled scripts (`HashMap<String, CompiledScript>`), one persistent `Scope` per scripted object (`HashMap<ObjectId, Scope>`), and the shared `LogSink`. (There is no shared board queue: each hook call drains into a local `Vec<BoardAction>` that it returns to `GameState`.) Built with `ScriptHost::new(&Rc<RefCell<Board>>, &HashMap<String, String>)`: the first arg is a shared ref to the active board (cloned into the write-API closures and each scope's `Registry`), the second is the world-level script pool. Each script is compiled once per `script_key` (the object's `script_name` — a world-pool name, or a synthetic `BUILTIN_*` name assigned by `expand_builtin_archetypes` so identical built-ins share one AST); the source is the pool entry for that key, or the object's embedded `builtin_script`. `CompiledScript` records which hooks the AST defines (`has_init`/`has_tick`/`has_bump`/`has_grab`/`has_enter`). **Per-object output queues no longer live here** — each `ObjectDef` owns its `queue: ObjQueue`. Reports compile/unknown-script failures onto the `LogSink`; runs nothing.
|
||||
- Lifecycle hooks, each taking `me` (an `ObjectInfo`) and `state` (a `ScriptState`) before any hook-specific arg: `init(me, state)`, `tick(me, state, dt)`, `bump(me, dir)` (`dir` is the `Direction` the bump came *from*, fired when any solid — the player, another object, or a pushed crate — presses into this object), `enter(me, dir)` (the non-solid counterpart to `bump`: fired on a non-solid object when a solid *relocates onto* its cell — a player/object step, a pushed crate, a `teleport`, or a `shift` — with `dir` the side the entrant came from, best-effort for teleport/shift), and `grab(me, state)` (fired when the player walks onto a `grab` thing — the only grab trigger). All optional — detected via `AST::iter_functions()` by name **and arity** (bump and enter are arity 2). Run one object at a time (the per-object loop lives in `GameState`): `run_tick_on(id, dt)` / `run_init_on(id)` / `run_bump(id, dir)` / `run_enter(id, dir)` / `run_grab(id)` / `run_send(id, fn, arg)`, all funneling through `run_hook_on_one` and **returning the `Vec<BoardAction>` they drained** for `GameState` to apply. `run_hook_on_one` builds a fresh `ObjectInfo`, pushes `[me, state, (arg)]`, and calls the hook tagged with the object's id. The `Tick` hook is **gated on an empty queue** (`hook != Hook::Tick || info.queue.is_empty()`), so an object with pending actions/`Delay` from an earlier tick isn't re-ticked; all other hooks fire whenever defined. After the call — **whether or not the hook was called** — it always drains the object's queue, so a delay still advances on an object that has no `tick` (or whose `tick` was skipped this frame). Runtime errors go to the shared `LogSink` (drained to the log), not fatal.
|
||||
- Lifecycle hooks, each taking `me` (an `ObjectInfo`) and `state` (a `ScriptState`) before any hook-specific arg: `init(me, state)`, `tick(me, state, dt)`, `bump(me, dir, by_player)` (`dir` is the `Direction` the bump came *from*, fired when any solid — the player, another object, or a pushed crate — presses into this object; `by_player` is `true` when the immediate bumper is the player — a pushed crate reports `false`), `enter(me, dir)` (the non-solid counterpart to `bump`: fired on a non-solid object when a solid *relocates onto* its cell — a player/object step, a pushed crate, a `teleport`, or a `shift` — with `dir` the side the entrant came from, best-effort for teleport/shift), and `grab(me, state)` (fired when the player walks onto a `grab` thing — the only grab trigger). All optional — detected via `AST::iter_functions()` by name **and arity** (bump is arity 3, enter arity 2). Run one object at a time (the per-object loop lives in `GameState`): `run_tick_on(id, dt)` / `run_init_on(id)` / `run_bump(id, dir, by_player)` / `run_enter(id, dir)` / `run_grab(id)` / `run_send(id, fn, arg)`, all funneling through `run_hook_on_one` and **returning the `Vec<BoardAction>` they drained** for `GameState` to apply. `run_hook_on_one` builds a fresh `ObjectInfo`, pushes `[me, state, (arg)]`, and calls the hook tagged with the object's id. The `Tick` hook is **gated on an empty queue** (`hook != Hook::Tick || info.queue.is_empty()`), so an object with pending actions/`Delay` from an earlier tick isn't re-ticked; all other hooks fire whenever defined. After the call — **whether or not the hook was called** — it always drains the object's queue, so a delay still advances on an object that has no `tick` (or whose `tick` was skipped this frame). Runtime errors go to the shared `LogSink` (drained to the log), not fatal.
|
||||
- `run_send(state, id, fn_name, arg)` — calls an arbitrary named function on an object (backs the `send()` action and scroll-choice dispatch). Picks the param list by the function's arity: 3 → `(me, state, arg)`, 2 → `(me, state)`, 1 → `(arg)`, 0 → `()`; a missing arg is `Dynamic::UNIT`. Errors if no function of that name exists.
|
||||
- **Reads** are methods/getters on the `api` types handed to the hook — `state.player.*`, `state.board.*`, `me.*` (see the `api/` section). There are **no read free functions** anymore.
|
||||
- **Write API** (`register_write_api`) — free functions that (mostly) enqueue an `Action` onto the **issuing object's** `queue` (resolved from the per-call tag): `move(dir)` (enqueues `Move` then a `MOVE_COST` delay), `delay(secs)`/`now()` (queue pacing), `set_tile(n)`, `set_fg`/`set_bg`/`set_color`, `set_tag(target, tag, present)`, `say(s)`/`say(s, dur)`, `scroll(lines)`, `send(target, fn [, arg])`, `teleport(id, x, y)` (jump entity `id` to a cell; `me.id` = self, `-1` = player), `push(x, y, dir)`, `shift([[x, y], …])`, `alter_gems(n)`, `alter_health(dh)`, `set_key(color, present)`, `die()`. **`log(s)` is the exception** — it is *not* an `Action`; it writes a line straight to the shared `LogSink`, so it surfaces immediately regardless of the object's queued delays (the write closure takes a `LogSink` clone). `scroll(lines)` takes a Rhai array whose elements are a string (text line) or a two-element `[choice_key, display_text]` (selectable choice). `shift` takes an array of two-int `[x, y]` arrays (a malformed entry logs an error immediately to the `LogSink`) and rotates that ring of cells via `Board::apply_shift`.
|
||||
|
||||
@@ -428,8 +428,11 @@ impl GameState {
|
||||
}
|
||||
};
|
||||
|
||||
// Call the target's bump hook and resolve whatever it did
|
||||
let actions = self.scripts.run_bump(id, dir.opposite());
|
||||
// Call the target's bump hook and resolve whatever it did. `by_player`
|
||||
// is true only when the immediate bumper is the player — a crate shoved
|
||||
// into this object (or another object moving in) is not the player.
|
||||
let by_player = from == self.board().player_pos();
|
||||
let actions = self.scripts.run_bump(id, dir.opposite(), by_player);
|
||||
self.apply_actions(actions);
|
||||
|
||||
// Now, check again if the target cell is empty. If it is, put the mover there. Also
|
||||
@@ -450,7 +453,8 @@ impl GameState {
|
||||
/// passable nor a pushable solid that can be shoved aside. No-ops silently (the
|
||||
/// caller does not need to check). If a solid object lies in the path — directly
|
||||
/// or at the end of a chain of crates the player is shoving — its `bump` hook
|
||||
/// fires with the direction the bump came from (whether or not the player moves).
|
||||
/// fires with the direction the bump came from and a bool reporting whether the
|
||||
/// bumper was the player (whether or not the player moves).
|
||||
pub fn try_move(&mut self, dir: Direction) {
|
||||
let (dx, dy): (i64, i64) = dir.into();
|
||||
let player_loc = self.board().player_pos();
|
||||
|
||||
+39
-21
@@ -6,8 +6,9 @@
|
||||
//!
|
||||
//! - `init(me, state)` — run once after the whole map is loaded (see [`ScriptHost::run_init`]).
|
||||
//! - `tick(me, state, dt)` — run every frame with the elapsed seconds (see [`ScriptHost::run_tick`]).
|
||||
//! - `bump(me, dir)` — run when a solid (the player, an object, or a pushed crate) presses into this
|
||||
//! object's cell, with the [`Direction`] the bump came *from*; see [`ScriptHost::run_bump`].
|
||||
//! - `bump(me, dir, by_player)` — run when a solid (the player, an object, or a pushed crate) presses
|
||||
//! into this object's cell, with the [`Direction`] the bump came *from* and whether the bumper was
|
||||
//! the player; see [`ScriptHost::run_bump`].
|
||||
//! - `enter(me, dir)` — run when a solid (the player, an object, or a pushed crate) relocates *onto*
|
||||
//! this **non-solid** object's cell, with the [`Direction`] the entrant came *from* (best-effort for
|
||||
//! teleport/shift); see [`ScriptHost::run_enter`].
|
||||
@@ -151,7 +152,7 @@ impl ScriptHost {
|
||||
CompiledScript {
|
||||
has_init: defines("init", 1),
|
||||
has_tick: defines("tick", 2),
|
||||
has_bump: defines("bump", 2),
|
||||
has_bump: defines("bump", 3),
|
||||
has_enter: defines("enter", 2),
|
||||
ast,
|
||||
},
|
||||
@@ -201,18 +202,21 @@ impl ScriptHost {
|
||||
/// The `dt` arg is only meaningful for `Tick` (it becomes the hook's `dt`
|
||||
/// parameter and paces the object's delay draining); pass `0.0` otherwise.
|
||||
pub(crate) fn run_hook_on(&mut self, hook: Hook, id: ObjectId, dt: f64) -> Vec<BoardAction> {
|
||||
let arg = match hook {
|
||||
Hook::Tick => Some(Dynamic::from(dt)),
|
||||
_ => None,
|
||||
let extra = match hook {
|
||||
Hook::Tick => vec![Dynamic::from(dt)],
|
||||
_ => vec![],
|
||||
};
|
||||
self.run_hook_on_one(hook, id, arg, dt)
|
||||
self.run_hook_on_one(hook, id, extra, dt)
|
||||
}
|
||||
|
||||
/// Calls one lifecycle `hook` on object `id` (if the script defines it), then
|
||||
/// drains that object's ready actions into a fresh `Vec` and returns them —
|
||||
/// [`GameState`](crate::game::GameState) applies them immediately, before the
|
||||
/// next object runs, so each object sees the board state its actions run against.
|
||||
fn run_hook_on_one(&mut self, hook: Hook, id: ObjectId, arg: Option<Dynamic>, dt: f64) -> Vec<BoardAction> {
|
||||
///
|
||||
/// `extra` holds the hook-specific arguments beyond `me` (`[dt]` for `tick`,
|
||||
/// `[dir]` for `enter`, `[dir, by_player]` for `bump`, empty otherwise).
|
||||
fn run_hook_on_one(&mut self, hook: Hook, id: ObjectId, extra: Vec<Dynamic>, dt: f64) -> Vec<BoardAction> {
|
||||
let mut actions = Vec::new();
|
||||
if let Some(mut info) = ObjectInfo::from_id(id, self.board.clone()) {
|
||||
if let Some(script) = self.scripts.get(&info.script_key)
|
||||
@@ -225,7 +229,7 @@ impl ScriptHost {
|
||||
// fires regardless of queued actions.
|
||||
if script.has(hook) && (hook != Hook::Tick || info.queue.is_empty()) {
|
||||
let mut args = vec![Dynamic::from(info.clone())];
|
||||
if let Some(d) = arg { args.push(d) }
|
||||
args.extend(extra);
|
||||
|
||||
// Call this with opts tagging this call as our id. The write API will read
|
||||
// that to find what queue to emit events to
|
||||
@@ -249,11 +253,13 @@ impl ScriptHost {
|
||||
actions
|
||||
}
|
||||
|
||||
/// Calls `bump(dir)` on the object with [`ObjectId`] `id`, if it defines the
|
||||
/// hook, and returns the actions it drained. `dir` is the [`Direction`] the
|
||||
/// bump came *from* (it points from the bumped object toward the bumper).
|
||||
pub(crate) fn run_bump(&mut self, id: ObjectId, dir: Direction) -> Vec<BoardAction> {
|
||||
self.run_hook_on_one(Hook::Bump, id, Some(Dynamic::from(dir)), 0.0)
|
||||
/// Calls `bump(dir, by_player)` on the object with [`ObjectId`] `id`, if it
|
||||
/// defines the hook, and returns the actions it drained. `dir` is the
|
||||
/// [`Direction`] the bump came *from* (it points from the bumped object toward
|
||||
/// the bumper); `by_player` is `true` when the thing pressing into this object
|
||||
/// is the player (not a pushed crate or another object).
|
||||
pub(crate) fn run_bump(&mut self, id: ObjectId, dir: Direction, by_player: bool) -> Vec<BoardAction> {
|
||||
self.run_hook_on_one(Hook::Bump, id, vec![Dynamic::from(dir), Dynamic::from(by_player)], 0.0)
|
||||
}
|
||||
|
||||
/// Calls `enter(dir)` on the object with [`ObjectId`] `id`, if it defines the
|
||||
@@ -261,7 +267,7 @@ impl ScriptHost {
|
||||
/// object, or a pushed crate) relocates onto this non-solid object's cell; `dir`
|
||||
/// is the [`Direction`] the entrant came *from* (best-effort for teleport/shift).
|
||||
pub(crate) fn run_enter(&mut self, id: ObjectId, dir: Direction) -> Vec<BoardAction> {
|
||||
self.run_hook_on_one(Hook::Enter, id, Some(Dynamic::from(dir)), 0.0)
|
||||
self.run_hook_on_one(Hook::Enter, id, vec![Dynamic::from(dir)], 0.0)
|
||||
}
|
||||
|
||||
/// Calls the named function on the object with [`ObjectId`] `target_id`.
|
||||
@@ -359,14 +365,16 @@ fn register_write_api(engine: &mut Engine, board: BoardRef, log_sink: LogSink) {
|
||||
engine.register_get("dx", |d: &mut Direction| d.dx());
|
||||
engine.register_get("dy", |d: &mut Direction| d.dy());
|
||||
|
||||
// move(dir): enqueue a Move followed by a rate-limiting Delay.
|
||||
// move(dir): enqueue a Move followed by an optional rate-limiting Delay.
|
||||
let b = board.clone();
|
||||
engine.register_fn("move", move |ctx: NativeCallContext, dir: Direction, delay: bool| {
|
||||
implement_move(ctx, &b, dir, delay)
|
||||
});
|
||||
|
||||
// If not specified the delay defaults to true:
|
||||
let b = board.clone();
|
||||
engine.register_fn("move", move |ctx: NativeCallContext, dir: Direction| {
|
||||
let src = source_of(&ctx);
|
||||
if let Some(scr) = b.borrow_mut().scripting_mut(src) {
|
||||
scr.queue.act(Action::Move(dir));
|
||||
scr.queue.delay(MOVE_COST);
|
||||
}
|
||||
implement_move(ctx, &b, dir, true)
|
||||
});
|
||||
|
||||
let b = board.clone();
|
||||
@@ -690,3 +698,13 @@ fn source_of(ctx: &NativeCallContext) -> ObjectId {
|
||||
.and_then(|n| ObjectId::try_from(n).ok())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
fn implement_move(ctx: NativeCallContext, board: &BoardRef, dir: Direction, delay: bool) {
|
||||
let src = source_of(&ctx);
|
||||
if let Some(scr) = board.borrow_mut().scripting_mut(src) {
|
||||
scr.queue.act(Action::Move(dir));
|
||||
if delay {
|
||||
scr.queue.delay(MOVE_COST);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,4 @@
|
||||
// Built-in script for the `crate` archetype (see kiln-core/src/builtin.rs).
|
||||
fn bump(me, dir) {
|
||||
push(me.x, me.y, dir.opposite); // Try to clear our target cell
|
||||
let tgt = dir.opposite.from_point(me.x, me.y);
|
||||
// This will silently-nop if tgt is occupied, and it will evaluate that after the push
|
||||
// (queue both right now, evaluate in that order after)
|
||||
teleport(me.id, tgt.x, tgt.y);
|
||||
fn bump(me, dir, _by_player) {
|
||||
move(dir.opposite, false);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
// A gem is a collectible: it is solid, so walking into it fires this `bump()`
|
||||
// hook. We bank the gem and `die()`, which clears the cell — so the move that
|
||||
// bumped us completes and the player ends up standing where the gem was.
|
||||
fn bump(me, _dir) {
|
||||
alter_gems(1);
|
||||
die();
|
||||
fn bump(me, dir, by_player) {
|
||||
if by_player {
|
||||
alter_gems(1);
|
||||
die();
|
||||
} else {
|
||||
move(dir.opposite, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
fn bump(me, dir) {
|
||||
if dir == North || dir == South {
|
||||
return;
|
||||
fn bump(me, dir, _by_player) {
|
||||
if dir == East || dir == West {
|
||||
move(dir.opposite, false);
|
||||
}
|
||||
push(me.x, me.y, dir.opposite); // Try to clear our target cell
|
||||
let tgt = dir.opposite.from_point(me.x, me.y);
|
||||
// This will silently-nop if tgt is occupied, and it will evaluate that after the push
|
||||
// (queue both right now, evaluate in that order after)
|
||||
teleport(me.id, tgt.x, tgt.y);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
//
|
||||
// A heart is a collectible: walking into it fires `bump()`, which restores 1
|
||||
// health and removes the heart, clearing the cell for the bumper to move into.
|
||||
fn bump(me, _dir) {
|
||||
alter_health(1);
|
||||
die();
|
||||
fn bump(me, dir, by_player) {
|
||||
if by_player {
|
||||
alter_health(1);
|
||||
die();
|
||||
} else {
|
||||
move(dir.opposite, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// A key is a collectible: walking into it fires `bump()`. We read our own colour
|
||||
// off the `BUILTIN_key_<colour>` tag, add it to the player's keyring, and `die()`
|
||||
// so the cell clears for the bumper to move into.
|
||||
fn bump(me, _dir) {
|
||||
fn bump(me, _dir, _by_player) {
|
||||
let colors = [
|
||||
"red",
|
||||
"orange",
|
||||
|
||||
@@ -54,7 +54,7 @@ fn tick(me, dt) {
|
||||
me.delay(0.15);
|
||||
}
|
||||
|
||||
fn bump(me, dir) {
|
||||
fn bump(me, dir, _by_player) {
|
||||
let d = facing(me);
|
||||
|
||||
// Only transport things that hit us from the front. `dir` is the side the bump
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
fn bump(me, dir) {
|
||||
if dir == East || dir == West {
|
||||
return;
|
||||
fn bump(me, dir, _by_player) {
|
||||
if dir == North || dir == South {
|
||||
move(dir.opposite, false);
|
||||
}
|
||||
push(me.x, me.y, dir.opposite); // Try to clear our target cell
|
||||
let tgt = dir.opposite.from_point(me.x, me.y);
|
||||
// This will silently-nop if tgt is occupied, and it will evaluate that after the push
|
||||
// (queue both right now, evaluate in that order after)
|
||||
teleport(me.id, tgt.x, tgt.y);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::{log_texts, scripts_from};
|
||||
use crate::board::Board;
|
||||
use crate::board::tests::{object_at, open_board, plain_object_at, sensor_at};
|
||||
use crate::board::tests::{crate_at, object_at, open_board, plain_object_at, sensor_at};
|
||||
use crate::game::{GameState, ScrollLine};
|
||||
use crate::utils::{Direction, ObjectId, Point};
|
||||
use std::collections::HashMap;
|
||||
@@ -297,8 +297,9 @@ fn object_id_for_name_finds_by_name() {
|
||||
}
|
||||
|
||||
// ── bump ────────────────────────────────────────────────────────────────────
|
||||
// `bump(me, dir)` fires when the player presses into a solid object — the only
|
||||
// remaining bump trigger, since object movement no longer dispatches hooks.
|
||||
// `bump(me, dir, by_player)` fires when a solid presses into a scripted object.
|
||||
// `dir` is the side the bump came from; `by_player` is true when the bumper is
|
||||
// the player itself (not a pushed crate or another object).
|
||||
|
||||
#[test]
|
||||
fn player_bump_reports_the_direction_it_came_from() {
|
||||
@@ -308,7 +309,7 @@ fn player_bump_reports_the_direction_it_came_from() {
|
||||
object_at(&mut board, 1, 0, "b", false);
|
||||
let mut game = GameState::with_scripts(
|
||||
board,
|
||||
scripts_from(&[("b", "fn bump(me, dir) { log(`bumped from ${dir}`); }")]),
|
||||
scripts_from(&[("b", "fn bump(me, dir, _by_player) { log(`bumped from ${dir}`); }")]),
|
||||
);
|
||||
game.run_init();
|
||||
game.try_move(Direction::East);
|
||||
@@ -328,7 +329,7 @@ fn bump_direction_supports_comparison_and_offset() {
|
||||
"b",
|
||||
// Player bumps from the West, so `dir == West` and the bumper sits at
|
||||
// (me.x + dir.dx) = 1 + (-1) = 0.
|
||||
"fn bump(me, dir) { if dir == West { log(`bumper at ${me.x + dir.dx}`); } }",
|
||||
"fn bump(me, dir, _by_player) { if dir == West { log(`bumper at ${me.x + dir.dx}`); } }",
|
||||
)]),
|
||||
);
|
||||
game.run_init();
|
||||
@@ -336,6 +337,40 @@ fn bump_direction_supports_comparison_and_offset() {
|
||||
assert!(log_texts(&game).iter().any(|t| t == "bumper at 0"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bump_reports_true_when_bumped_by_the_player() {
|
||||
// Walking straight into a scripted solid: the player is the bumper.
|
||||
let mut board = open_board(3, 1, (0, 0));
|
||||
object_at(&mut board, 1, 0, "b", false);
|
||||
let mut game = GameState::with_scripts(
|
||||
board,
|
||||
scripts_from(&[("b", "fn bump(me, _dir, by_player) { log(`bumped by ${by_player}`); }")]),
|
||||
);
|
||||
game.run_init();
|
||||
game.try_move(Direction::East);
|
||||
assert!(log_texts(&game).iter().any(|t| t == "bumped by true"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bump_reports_false_when_bumped_by_a_pushed_crate() {
|
||||
// The player shoves the crate at (1,0) into a scripted solid at (2,0). The
|
||||
// crate's own bump sees the player, but the object behind it is bumped by the
|
||||
// crate — so `by_player` is false there.
|
||||
let mut board = open_board(4, 1, (0, 0));
|
||||
crate_at(&mut board, 1, 0);
|
||||
object_at(&mut board, 2, 0, "o", false);
|
||||
let mut game = GameState::with_scripts(
|
||||
board,
|
||||
scripts_from(&[("o", "fn bump(me, _dir, by_player) { log(`bumped by ${by_player}`); }")]),
|
||||
);
|
||||
game.run_init();
|
||||
game.try_move(Direction::East);
|
||||
assert!(
|
||||
log_texts(&game).iter().any(|t| t == "bumped by false"),
|
||||
"the object was bumped by the crate, not the player"
|
||||
);
|
||||
}
|
||||
|
||||
// ── scroll ──────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
@@ -348,7 +383,7 @@ fn scroll_opens_on_player_bump() {
|
||||
board,
|
||||
scripts_from(&[(
|
||||
"s",
|
||||
r#"fn bump(me, dir) { scroll(["Hello world", ["eat", "Eat it"]]); }"#,
|
||||
r#"fn bump(me, dir, _by_player) { scroll(["Hello world", ["eat", "Eat it"]]); }"#,
|
||||
)]),
|
||||
);
|
||||
game.run_init();
|
||||
@@ -372,7 +407,7 @@ fn handle_scroll_without_choice_clears_it() {
|
||||
object_at(&mut board, 1, 0, "s", false);
|
||||
let mut game = GameState::with_scripts(
|
||||
board,
|
||||
scripts_from(&[("s", r#"fn bump(me, dir) { scroll(["Hello"]); }"#)]),
|
||||
scripts_from(&[("s", r#"fn bump(me, dir, _by_player) { scroll(["Hello"]); }"#)]),
|
||||
);
|
||||
game.run_init();
|
||||
game.try_move(Direction::East);
|
||||
@@ -394,7 +429,7 @@ fn handle_scroll_with_choice_dispatches_send_to_source() {
|
||||
scripts_from(&[(
|
||||
"s",
|
||||
r#"
|
||||
fn bump(me, dir) { scroll(["Muffin?", ["eat", "Eat it"]]); }
|
||||
fn bump(me, dir, _by_player) { scroll(["Muffin?", ["eat", "Eat it"]]); }
|
||||
fn eat() { log("eaten"); }
|
||||
"#,
|
||||
)]),
|
||||
|
||||
Reference in New Issue
Block a user