2026-06-16 14:34:42 -05:00
|
|
|
//! Pushers are now scripted objects (the `pusher_*` archetypes expand into objects
|
|
|
|
|
//! carrying the embedded `pusher.rhai` plus a `BUILTIN_pusher_<dir>` tag).
|
|
|
|
|
|
|
|
|
|
use super::{layer, load_board, map};
|
|
|
|
|
use crate::archetype::Archetype;
|
|
|
|
|
use crate::game::GameState;
|
|
|
|
|
use crate::map_file::MapFile;
|
|
|
|
|
use crate::object_def::ObjectDef;
|
|
|
|
|
use std::time::Duration;
|
|
|
|
|
|
|
|
|
|
/// Finds the pusher object on a board (by its built-in tag).
|
|
|
|
|
fn pusher<'a>(board: &'a crate::board::Board, id: &mut u32) -> &'a ObjectDef {
|
|
|
|
|
let (&oid, obj) = board
|
|
|
|
|
.objects
|
|
|
|
|
.iter()
|
|
|
|
|
.find(|(_, o)| o.tags.contains("BUILTIN_pusher_east"))
|
|
|
|
|
.expect("pusher object");
|
|
|
|
|
*id = oid;
|
|
|
|
|
obj
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn pusher_loads_as_a_tagged_scripted_solid_object() {
|
|
|
|
|
let board = load_board(&map(
|
|
|
|
|
3,
|
|
|
|
|
1,
|
|
|
|
|
&[layer(
|
|
|
|
|
"P @",
|
|
|
|
|
&[("P", "kind = \"pusher_east\""), ("@", "kind = \"player\"")],
|
|
|
|
|
)],
|
|
|
|
|
));
|
|
|
|
|
let mut id = 0;
|
|
|
|
|
let p = pusher(&board, &mut id);
|
|
|
|
|
assert_eq!((p.x, p.y), (0, 0));
|
|
|
|
|
assert!(
|
|
|
|
|
p.builtin_script.is_some(),
|
|
|
|
|
"carries the embedded pusher script"
|
|
|
|
|
);
|
2026-06-21 18:44:42 -05:00
|
|
|
// Expansion keys all pushers under one synthetic script name so they share a
|
|
|
|
|
// compiled AST; the source still comes from `builtin_script`.
|
|
|
|
|
assert_eq!(p.script_name.as_deref(), Some("BUILTIN_pusher"));
|
2026-06-16 14:34:42 -05:00
|
|
|
assert!(!board.is_passable(0, 0), "pusher is solid");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn pusher_advances_and_shoves_a_crate() {
|
|
|
|
|
// Pusher at (0,0), crate at (1,0), the player parked at the east edge.
|
|
|
|
|
let board = load_board(&map(
|
|
|
|
|
5,
|
|
|
|
|
1,
|
|
|
|
|
&[layer(
|
|
|
|
|
"Po @",
|
|
|
|
|
&[
|
|
|
|
|
("P", "kind = \"pusher_east\""),
|
|
|
|
|
("o", "kind = \"crate\""),
|
|
|
|
|
("@", "kind = \"player\""),
|
|
|
|
|
],
|
|
|
|
|
)],
|
|
|
|
|
));
|
|
|
|
|
let mut pid = 0;
|
|
|
|
|
pusher(&board, &mut pid);
|
|
|
|
|
|
|
|
|
|
let mut game = GameState::new(board);
|
|
|
|
|
game.run_init();
|
|
|
|
|
for _ in 0..12 {
|
|
|
|
|
game.tick(Duration::from_secs_f64(0.3));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let b = game.board();
|
|
|
|
|
assert!(b.objects[&pid].x > 0, "pusher advanced east");
|
|
|
|
|
assert_eq!(
|
|
|
|
|
b.get(0, 1, 0).1,
|
|
|
|
|
Archetype::Empty,
|
|
|
|
|
"crate left its start cell"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
(2..b.width).any(|x| b.get(0, x, 0).1 == Archetype::Crate),
|
|
|
|
|
"crate was shoved east"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn pusher_blocked_by_wall_stays_put() {
|
|
|
|
|
let board = load_board(&map(
|
|
|
|
|
4,
|
|
|
|
|
1,
|
|
|
|
|
&[layer(
|
|
|
|
|
"P# @",
|
|
|
|
|
&[
|
|
|
|
|
("P", "kind = \"pusher_east\""),
|
|
|
|
|
("#", "kind = \"wall\""),
|
|
|
|
|
("@", "kind = \"player\""),
|
|
|
|
|
],
|
|
|
|
|
)],
|
|
|
|
|
));
|
|
|
|
|
let mut pid = 0;
|
|
|
|
|
pusher(&board, &mut pid);
|
|
|
|
|
|
|
|
|
|
let mut game = GameState::new(board);
|
|
|
|
|
game.run_init();
|
|
|
|
|
for _ in 0..12 {
|
|
|
|
|
game.tick(Duration::from_secs_f64(0.3));
|
|
|
|
|
}
|
|
|
|
|
assert_eq!(
|
|
|
|
|
game.board().objects[&pid].x,
|
|
|
|
|
0,
|
|
|
|
|
"a wall-blocked pusher does not move"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn pusher_round_trips_to_its_keyword() {
|
|
|
|
|
let board = load_board(&map(
|
|
|
|
|
3,
|
|
|
|
|
1,
|
|
|
|
|
&[layer(
|
|
|
|
|
"P @",
|
|
|
|
|
&[("P", "kind = \"pusher_east\""), ("@", "kind = \"player\"")],
|
|
|
|
|
)],
|
|
|
|
|
));
|
|
|
|
|
// Save collapses the expanded object back into the `pusher_east` keyword.
|
|
|
|
|
let toml_out = toml::to_string_pretty(&MapFile::from(&board)).unwrap();
|
|
|
|
|
assert!(
|
|
|
|
|
toml_out.contains("pusher_east"),
|
|
|
|
|
"pusher should save as its archetype keyword, got:\n{toml_out}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Reload: it is a working pusher object again.
|
|
|
|
|
let board2 = load_board(&toml_out);
|
|
|
|
|
let mut id = 0;
|
|
|
|
|
let p = pusher(&board2, &mut id);
|
|
|
|
|
assert_eq!((p.x, p.y), (0, 0));
|
|
|
|
|
assert!(p.builtin_script.is_some());
|
|
|
|
|
}
|