redoing bump
This commit is contained in:
@@ -252,6 +252,41 @@ impl Board {
|
||||
.is_some_and(|s| s.pushable().allows(dir))
|
||||
}
|
||||
|
||||
/// The object that a move **into** `(x, y)` heading `dir` bumps, if any.
|
||||
///
|
||||
/// Walks the chain of pushable crates from the target cell in `dir` until it
|
||||
/// reaches something that stops it, and reports the object it presses against:
|
||||
/// - a solid object in the target cell (or at the end of a crate chain) is the
|
||||
/// bumped object,
|
||||
/// - open space or the player means nothing is bumped,
|
||||
/// - a non-pushable terrain cell (a wall) blocks the chain with no object to bump.
|
||||
///
|
||||
/// This is what lets *any* solid — the player, another object, or a pushed
|
||||
/// crate — trigger a `bump`: the bumper need not be an object, since we only
|
||||
/// return the *bumped* object's id (the direction it came from is supplied by
|
||||
/// the caller from its move direction).
|
||||
pub fn bump_target(&self, x: usize, y: usize, dir: Direction) -> Option<ObjectId> {
|
||||
let (dx, dy): (i64, i64) = dir.into();
|
||||
let (mut cx, mut cy) = (x, y);
|
||||
loop {
|
||||
match self.solid_at(cx, cy) {
|
||||
None => return None, // open space: nothing bumped
|
||||
Some(s) if s.object_id().is_some() => return s.object_id(), // hit an object
|
||||
Some(s) if s.player() => return None, // the player is never "bumped"
|
||||
// A terrain cell: a pushable crate is walked through; a wall stops us.
|
||||
Some(_) if self.is_pushable(cx, cy, dir) => {
|
||||
let next = (cx as i64 + dx, cy as i64 + dy);
|
||||
if !self.in_bounds(next) {
|
||||
return None; // crate chain runs off the board
|
||||
}
|
||||
cx = next.0 as usize;
|
||||
cy = next.1 as usize;
|
||||
}
|
||||
Some(_) => return None, // non-pushable terrain (wall): no object behind it here
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the chain of pushable solids starting at `(x, y)` can be shoved one
|
||||
/// step in `dir` — i.e. the chain ends at a passable cell rather than the board
|
||||
/// edge or a non-pushable solid.
|
||||
|
||||
+24
-17
@@ -208,7 +208,7 @@ impl GameState {
|
||||
// Logs are collected here rather than pushed inline, since the board borrow
|
||||
// below also borrows `self`.
|
||||
let mut logs: Vec<LogLine> = Vec::new();
|
||||
let mut bumps: Vec<(ObjectId, i64)> = Vec::new();
|
||||
let mut bumps: Vec<(ObjectId, Direction)> = Vec::new();
|
||||
// Net change to player stats from AddGems / AlterHealth actions; applied
|
||||
// to `self` after the board borrow drops.
|
||||
let mut gem_delta: i64 = 0;
|
||||
@@ -225,7 +225,9 @@ impl GameState {
|
||||
match ba.action {
|
||||
Action::Move(dir) => {
|
||||
if let Some(bumped) = step_object(&mut board, ba.source, dir) {
|
||||
bumps.push((bumped, ba.source as i64));
|
||||
// The bump "comes from" the side the mover advanced from,
|
||||
// i.e. the opposite of its travel direction.
|
||||
bumps.push((bumped, dir.opposite()));
|
||||
}
|
||||
}
|
||||
Action::SetTile(tile) => {
|
||||
@@ -382,8 +384,8 @@ impl GameState {
|
||||
self.log.push(LogLine::error(format!("set_key: unknown color {color:?}")));
|
||||
}
|
||||
}
|
||||
for (bumped, bumper) in bumps {
|
||||
self.scripts.run_bump(bumped, bumper);
|
||||
for (bumped, dir) in bumps {
|
||||
self.scripts.run_bump(bumped, dir);
|
||||
}
|
||||
for (target, fn_name, arg) in sends {
|
||||
self.scripts.run_send(target, &fn_name, arg);
|
||||
@@ -462,8 +464,9 @@ impl GameState {
|
||||
///
|
||||
/// The move is ignored if the target cell is out of bounds, or it is neither
|
||||
/// passable nor a pushable solid that can be shoved aside. No-ops silently (the
|
||||
/// caller does not need to check). If the target cell holds a solid object, that
|
||||
/// object's `bump(-1)` hook fires (whether or not the player ends up moving).
|
||||
/// 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).
|
||||
pub fn try_move(&mut self, dir: Direction) {
|
||||
let bumped;
|
||||
let grabbed;
|
||||
@@ -479,12 +482,13 @@ impl GameState {
|
||||
// Walking onto a grab thing (e.g. a gem) is never blocked: the player
|
||||
// moves onto it and its grab() hook fires (the thing despawns itself).
|
||||
grabbed = board.grab_object_at(nx, ny);
|
||||
// A solid object in the way is bumped by the player (id -1) — but a
|
||||
// grab thing fires grab() instead of bump(), so don't also bump it.
|
||||
bumped = board
|
||||
.solid_at(nx, ny)
|
||||
.filter(|_| grabbed.is_none())
|
||||
.and_then(|s| s.object_id());
|
||||
// A solid object in the way is bumped by the player — possibly through a
|
||||
// chain of crates the player is shoving (see `bump_target`) — but a grab
|
||||
// thing fires grab() instead of bump(), so don't also bump it.
|
||||
bumped = grabbed
|
||||
.is_none()
|
||||
.then(|| board.bump_target(nx, ny, dir))
|
||||
.flatten();
|
||||
if grabbed.is_some() || board.is_passable(nx, ny) || board.can_push(nx, ny, dir) {
|
||||
// Don't push a grab thing aside — walk onto it. Otherwise shove any
|
||||
// pushable chain out of the way (no-op when there's nothing to push).
|
||||
@@ -515,7 +519,8 @@ impl GameState {
|
||||
self.resolve();
|
||||
}
|
||||
if let Some(idx) = bumped {
|
||||
self.scripts.run_bump(idx, -1);
|
||||
// The player advanced in `dir`, so the bump arrives from the opposite side.
|
||||
self.scripts.run_bump(idx, dir.opposite());
|
||||
self.drain_errors();
|
||||
}
|
||||
}
|
||||
@@ -526,9 +531,10 @@ impl GameState {
|
||||
///
|
||||
/// The move itself proceeds only if the target is in bounds and either passable or a
|
||||
/// pushable solid the object can shove out of the way (see [`Board::can_push`]). The
|
||||
/// bump is recorded whenever a solid object occupies the target — whether it gets
|
||||
/// pushed aside or blocks the move — since something tried to move into it. Walls and
|
||||
/// crates carry no script, so only solid objects yield a bump.
|
||||
/// bump is recorded for the solid object the move presses into — directly, or at the
|
||||
/// end of a chain of crates being shoved (see [`Board::bump_target`]) — whether it
|
||||
/// gets pushed aside or blocks the move. Walls and crates carry no script, so only
|
||||
/// solid objects yield a bump.
|
||||
fn step_object(board: &mut Board, id: ObjectId, dir: Direction) -> Option<ObjectId> {
|
||||
let (dx, dy): (i64, i64) = dir.into();
|
||||
let (ox, oy) = board.objects.get(&id).map(|o| (o.x, o.y))?;
|
||||
@@ -538,7 +544,8 @@ fn step_object(board: &mut Board, id: ObjectId, dir: Direction) -> Option<Object
|
||||
}
|
||||
let (nx, ny) = (target.0 as usize, target.1 as usize);
|
||||
// Capture the bumped object before any push relocates it (its id is stable).
|
||||
let bumped = board.solid_at(nx, ny).and_then(|s| s.object_id());
|
||||
// Walks through a pushed crate chain to the object it presses against.
|
||||
let bumped = board.bump_target(nx, ny, dir);
|
||||
if board.is_passable(nx, ny) || board.can_push(nx, ny, dir) {
|
||||
board.push(nx, ny, dir); // shoves a crate/object out of the way; no-op otherwise
|
||||
let obj = board.objects.get_mut(&id).expect("id checked above");
|
||||
|
||||
+25
-6
@@ -6,8 +6,8 @@
|
||||
//!
|
||||
//! - `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, state, id)` — run when another mover steps into this object's cell, with the
|
||||
//! bumper's object index (`-1` for the player); see [`ScriptHost::run_bump`].
|
||||
//! - `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`].
|
||||
//! - `grab(me, state)` — run when the player walks onto a `grab` object; see [`ScriptHost::run_grab`].
|
||||
//! Typically adds a stat + `die()`s.
|
||||
//!
|
||||
@@ -257,10 +257,12 @@ impl ScriptHost {
|
||||
}
|
||||
}
|
||||
|
||||
/// Calls `bump(id)` on the object with [`ObjectId`] `object_id`, if it defines
|
||||
/// the hook. After the hook, drains the object's queue with `dt = 0`.
|
||||
pub fn run_bump(&mut self, id: ObjectId, bumper: i64) {
|
||||
self.run_hook_on_one(Hook::Bump, id, Some(Dynamic::from(bumper)), 0.0);
|
||||
/// Calls `bump(dir)` on the object with [`ObjectId`] `id`, if it defines the
|
||||
/// hook. `dir` is the [`Direction`] the bump came *from* (it points from the
|
||||
/// bumped object toward the bumper). After the hook, drains the object's queue
|
||||
/// with `dt = 0`.
|
||||
pub fn run_bump(&mut self, id: ObjectId, dir: Direction) {
|
||||
self.run_hook_on_one(Hook::Bump, id, Some(Dynamic::from(dir)), 0.0);
|
||||
}
|
||||
|
||||
/// Calls `grab()` on the object with [`ObjectId`] `object_id`, if it defines the
|
||||
@@ -344,6 +346,23 @@ impl ScriptHost {
|
||||
|
||||
fn register_write_api(engine: &mut Engine, board: BoardRef) {
|
||||
engine.register_type_with_name::<Direction>("Direction");
|
||||
// Rhai does not auto-derive comparison for custom types, so register `==`/`!=`
|
||||
// to let scripts test the `bump` direction (e.g. `if dir == West { … }`).
|
||||
engine.register_fn("==", |a: Direction, b: Direction| a == b);
|
||||
engine.register_fn("!=", |a: Direction, b: Direction| a != b);
|
||||
// Let a Direction interpolate/print as its name (e.g. in `log(`from ${dir}`)`).
|
||||
let dir_name = |d: Direction| match d {
|
||||
Direction::North => "North",
|
||||
Direction::South => "South",
|
||||
Direction::East => "East",
|
||||
Direction::West => "West",
|
||||
};
|
||||
engine.register_fn("to_string", dir_name);
|
||||
engine.register_fn("to_debug", dir_name);
|
||||
// Expose the unit-step components so scripts can turn a direction into a cell
|
||||
// offset (e.g. the bumper's cell is `(me.x + dir.dx, me.y + dir.dy)`).
|
||||
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.
|
||||
let b = board.clone();
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
//
|
||||
// The direction is not baked into this source: every transporter shares one
|
||||
// compiled copy and reads its `BUILTIN_transporter_<dir>` tag. The world is
|
||||
// reached through the global `Board`/`Player` constants; `teleport(id, x, y)`
|
||||
// moves an arbitrary entity (`-1` is the player).
|
||||
// reached through the global `Board`/`Player` constants. The bumper is moved by
|
||||
// coordinate via `shift([[from], [to]])`, which relocates whatever solid sits at
|
||||
// the entrance — player, object, or a pushed crate — onto the empty destination.
|
||||
|
||||
// This transporter's facing unit vector, from its direction tag.
|
||||
fn facing(me) {
|
||||
@@ -27,6 +28,16 @@ fn opposite_tag(me) {
|
||||
else { "BUILTIN_transporter_east" }
|
||||
}
|
||||
|
||||
// The direction a front bump arrives from — the side opposite our facing (a
|
||||
// north-facing transporter is entered from the south, etc.). `bump` reacts only
|
||||
// when the reported direction matches this.
|
||||
fn entrance_dir(me) {
|
||||
if me.has_tag("BUILTIN_transporter_north") { South }
|
||||
else if me.has_tag("BUILTIN_transporter_south") { North }
|
||||
else if me.has_tag("BUILTIN_transporter_east") { West }
|
||||
else { East }
|
||||
}
|
||||
|
||||
// The 4-frame animation loop for this direction (CP437 tile codes).
|
||||
fn frames(me) {
|
||||
if me.has_tag("BUILTIN_transporter_north") { [94, 45, 94, 126] } // ^ - ^ ~
|
||||
@@ -47,38 +58,41 @@ fn tick(me, dt) {
|
||||
me.delay(0.15);
|
||||
}
|
||||
|
||||
fn bump(me, id) {
|
||||
// Move whatever solid sits at (sx, sy) onto the empty cell (tx, ty) immediately.
|
||||
// A two-cell `shift` relocates the source solid — player, object, or crate — and
|
||||
// leaves its old cell empty (the destination is checked empty by the caller). Kept
|
||||
// as a helper so the nested-array literal stays at a shallow expression depth.
|
||||
fn transport(sx, sy, tx, ty) {
|
||||
shift([[sx, sy], [tx, ty]]); now();
|
||||
}
|
||||
|
||||
fn bump(me, dir) {
|
||||
// Only transport things that hit us from the front — the entrance side. `dir`
|
||||
// is the direction the bump came from; a bump from any other side does nothing.
|
||||
if dir != entrance_dir(me) { return; }
|
||||
|
||||
let d = facing(me);
|
||||
let dx = d[0];
|
||||
let dy = d[1];
|
||||
|
||||
// Only transport things that hit us from the front — the entrance cell at
|
||||
// (me - d). A bump from any other side does nothing.
|
||||
let bx;
|
||||
let by;
|
||||
if id == -1 {
|
||||
bx = Player.x;
|
||||
by = Player.y;
|
||||
} else {
|
||||
let o = Board.get(id);
|
||||
if o == () { return; }
|
||||
bx = o.x;
|
||||
by = o.y;
|
||||
}
|
||||
if bx != me.x - dx || by != me.y - dy { return; }
|
||||
// Whatever solid is pressed against our entrance cell (me - d) gets moved:
|
||||
// the player, another object, or a pushed crate — all handled uniformly by
|
||||
// shifting the solid at that coordinate onto an empty destination.
|
||||
let ex = me.x - dx;
|
||||
let ey = me.y - dy;
|
||||
|
||||
// 1. The cell just past us (the opposite side): if nothing solid is there,
|
||||
// drop the bumper onto it.
|
||||
let fx = me.x + dx;
|
||||
let fy = me.y + dy;
|
||||
if Board.passable(fx, fy) {
|
||||
teleport(id, fx, fy); now();
|
||||
transport(ex, ey, fx, fy);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Otherwise scan along our axis for the nearest opposite-facing transporter
|
||||
// whose entrance (the cell just before it) is free, and drop the bumper
|
||||
// there. A blocked pair is skipped; give up at the board edge.
|
||||
// whose far side (the cell just past it) is free, and drop the bumper there.
|
||||
// A blocked pair is skipped; give up at the board edge.
|
||||
let opp = Board.tagged(opposite_tag(me));
|
||||
let cx = fx;
|
||||
let cy = fy;
|
||||
@@ -94,10 +108,12 @@ fn bump(me, id) {
|
||||
if o.x == cx && o.y == cy { here = true; }
|
||||
}
|
||||
if here {
|
||||
let ex = cx + dx;
|
||||
let ey = cy + dy;
|
||||
if Board.passable(ex, ey) {
|
||||
teleport(id, ex, ey); now();
|
||||
// Emerge on the paired transporter's far side — the cell just past it
|
||||
// along our scan (cx + d), out its back.
|
||||
let tx = cx + dx;
|
||||
let ty = cy + dy;
|
||||
if Board.passable(tx, ty) {
|
||||
transport(ex, ey, tx, ty);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,11 +18,11 @@ fn collision_priority_resolves_in_array_order_and_bumps() {
|
||||
scripts_from(&[
|
||||
(
|
||||
"e",
|
||||
"fn init(m) { move(East); } fn bump(m,id) { log(`o0 by ${id}`); }",
|
||||
"fn init(m) { move(East); } fn bump(m,dir) { log(`o0 from ${dir}`); }",
|
||||
),
|
||||
(
|
||||
"w",
|
||||
"fn init(m) { move(West); } fn bump(m,id) { log(`o1 by ${id}`); }",
|
||||
"fn init(m) { move(West); } fn bump(m,dir) { log(`o1 from ${dir}`); }",
|
||||
),
|
||||
]),
|
||||
);
|
||||
@@ -36,6 +36,7 @@ fn collision_priority_resolves_in_array_order_and_bumps() {
|
||||
assert_eq!((b.objects[&2].x, b.objects[&2].y), (2, 0)); // obj1 blocked
|
||||
}
|
||||
let logs = log_texts(&game);
|
||||
assert!(logs.iter().any(|t| t == "o0 by 2")); // obj0 bumped by obj1
|
||||
assert!(!logs.iter().any(|t| t.starts_with("o1 by"))); // obj1 not bumped
|
||||
// obj1 moved West into obj0, so the bump arrives from the East side of obj0.
|
||||
assert!(logs.iter().any(|t| t == "o0 from East")); // obj0 bumped by obj1
|
||||
assert!(!logs.iter().any(|t| t.starts_with("o1 from"))); // obj1 not bumped
|
||||
}
|
||||
|
||||
@@ -56,9 +56,9 @@ fn bumping_a_transporter_drops_you_on_its_far_side() {
|
||||
|
||||
#[test]
|
||||
fn a_blocked_far_side_transports_out_of_the_paired_transporter() {
|
||||
// Player, east transporter, a wall blocking its far side, the free entrance
|
||||
// cell, then the paired west transporter. The player should pop out at the
|
||||
// west transporter's entrance (the cell just before it), across the wall.
|
||||
// Player, east transporter, a wall blocking its far side, a gap, the paired
|
||||
// west transporter, then a free cell. The player should pop out on the paired
|
||||
// transporter's far side (the cell just past it), across the wall.
|
||||
let board = load_board(&map(
|
||||
6,
|
||||
1,
|
||||
@@ -81,11 +81,49 @@ fn a_blocked_far_side_transports_out_of_the_paired_transporter() {
|
||||
let b = game.board();
|
||||
assert_eq!(
|
||||
(b.player.x, b.player.y),
|
||||
(3, 0),
|
||||
"transported to the paired transporter's entrance",
|
||||
(5, 0),
|
||||
"transported past the paired transporter",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_pushed_crate_is_transported_through() {
|
||||
// Player, a crate, an east transporter, then a free cell. Pushing the crate
|
||||
// east into the transporter bumps it (through the crate chain); the transporter
|
||||
// moves the crate — a plain terrain solid, no object id — out its far side.
|
||||
let board = load_board(&map(
|
||||
4,
|
||||
1,
|
||||
&[layer(
|
||||
"@oT ",
|
||||
&[
|
||||
("@", "kind = \"player\""),
|
||||
("o", "kind = \"crate\""),
|
||||
("T", "kind = \"transporter_east\""),
|
||||
],
|
||||
)],
|
||||
));
|
||||
let mut game = GameState::new(board);
|
||||
game.run_init();
|
||||
|
||||
// Push east into the crate: the crate can't move (the transporter blocks it),
|
||||
// but the transporter is bumped from the West and teleports the crate to (3,0).
|
||||
game.try_move(Direction::East);
|
||||
game.tick(Duration::from_secs_f64(0.1));
|
||||
{
|
||||
let b = game.board();
|
||||
assert!(b.solid_at(3, 0).is_some(), "crate transported to the far side");
|
||||
assert!(b.is_passable(1, 0), "crate's old cell is now empty");
|
||||
assert_eq!((b.player.x, b.player.y), (0, 0), "player didn't move yet");
|
||||
}
|
||||
|
||||
// With the crate gone, a second push walks the player onto the vacated cell.
|
||||
game.try_move(Direction::East);
|
||||
game.tick(Duration::from_secs_f64(0.1));
|
||||
let b = game.board();
|
||||
assert_eq!((b.player.x, b.player.y), (1, 0), "player follows into the gap");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transporter_round_trips_to_its_keyword() {
|
||||
let board = load_board(&map(
|
||||
|
||||
@@ -245,20 +245,40 @@ fn object_id_for_name_finds_by_name() {
|
||||
|
||||
// Ensure try_move from the player side also triggers scripted bump
|
||||
#[test]
|
||||
fn player_bump_fires_with_negative_one() {
|
||||
// The player walks into a solid scripted object; the object is bumped with -1
|
||||
// and the player does not move onto it.
|
||||
fn player_bump_reports_the_direction_it_came_from() {
|
||||
// The player walks East into a solid scripted object; the object is bumped from
|
||||
// the West side (opposite the player's travel) and the player does not move onto it.
|
||||
let board = open_board(3, 1, (0, 0), vec![scripted_object(1, 0, "b")]);
|
||||
let mut game = GameState::with_scripts(
|
||||
board,
|
||||
scripts_from(&[("b", "fn bump(m,id) { log(`bumped by ${id}`); }")]),
|
||||
scripts_from(&[("b", "fn bump(m,dir) { log(`bumped from ${dir}`); }")]),
|
||||
);
|
||||
game.run_init();
|
||||
game.try_move(Direction::East);
|
||||
assert_eq!((game.board().player.x, game.board().player.y), (0, 0)); // blocked
|
||||
// bump's log is emitted into the object's queue; a tick flushes it.
|
||||
game.tick(Duration::from_millis(16));
|
||||
assert!(log_texts(&game).iter().any(|t| t == "bumped by -1"));
|
||||
assert!(log_texts(&game).iter().any(|t| t == "bumped from West"));
|
||||
}
|
||||
|
||||
// A bump handler can compare the direction and read its `dx`/`dy` offset to
|
||||
// locate the bumper's cell.
|
||||
#[test]
|
||||
fn bump_direction_supports_comparison_and_offset() {
|
||||
let board = open_board(3, 1, (0, 0), vec![scripted_object(1, 0, "b")]);
|
||||
let mut game = GameState::with_scripts(
|
||||
board,
|
||||
scripts_from(&[(
|
||||
"b",
|
||||
// Player bumps from the West, so `dir == West` and the bumper sits at
|
||||
// (me.x + dir.dx) = 1 + (-1) = 0.
|
||||
"fn bump(m,dir) { if dir == West { log(`bumper at ${m.x + dir.dx}`); } }",
|
||||
)]),
|
||||
);
|
||||
game.run_init();
|
||||
game.try_move(Direction::East);
|
||||
game.tick(Duration::from_millis(16));
|
||||
assert!(log_texts(&game).iter().any(|t| t == "bumper at 0"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -271,7 +291,7 @@ fn scroll_opens_on_player_bump() {
|
||||
board,
|
||||
scripts_from(&[(
|
||||
"s",
|
||||
r#"fn bump(m,id) { scroll(["Hello world", ["eat", "Eat it"]]); }"#,
|
||||
r#"fn bump(m,dir) { scroll(["Hello world", ["eat", "Eat it"]]); }"#,
|
||||
)]),
|
||||
);
|
||||
game.run_init();
|
||||
@@ -295,7 +315,7 @@ fn handle_scroll_without_choice_clears_it() {
|
||||
let board = open_board(3, 1, (0, 0), vec![scripted_object(1, 0, "s")]);
|
||||
let mut game = GameState::with_scripts(
|
||||
board,
|
||||
scripts_from(&[("s", r#"fn bump(m,id) { scroll(["Hello"]); }"#)]),
|
||||
scripts_from(&[("s", r#"fn bump(m,dir) { scroll(["Hello"]); }"#)]),
|
||||
);
|
||||
game.run_init();
|
||||
game.try_move(Direction::East);
|
||||
@@ -317,7 +337,7 @@ fn handle_scroll_with_choice_dispatches_send_to_source() {
|
||||
scripts_from(&[(
|
||||
"s",
|
||||
r#"
|
||||
fn bump(m,id) { scroll(["Muffin?", ["eat", "Eat it"]]); }
|
||||
fn bump(m,dir) { scroll(["Muffin?", ["eat", "Eat it"]]); }
|
||||
fn eat() { log("eaten"); }
|
||||
"#,
|
||||
)]),
|
||||
|
||||
@@ -367,6 +367,20 @@ impl Direction {
|
||||
Direction::North => -1,
|
||||
}
|
||||
}
|
||||
|
||||
/// The direction pointing the opposite way.
|
||||
///
|
||||
/// Used to turn a mover's travel direction into the "came-from" direction
|
||||
/// reported to a bumped object's `bump` hook (a bumper heading East arrives
|
||||
/// from the West side of the thing it hits).
|
||||
pub fn opposite(self) -> Direction {
|
||||
match self {
|
||||
Direction::North => Direction::South,
|
||||
Direction::South => Direction::North,
|
||||
Direction::East => Direction::West,
|
||||
Direction::West => Direction::East,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A value that can be stored in a board's script registry across board transitions.
|
||||
|
||||
Reference in New Issue
Block a user