builtin scripts
This commit is contained in:
@@ -3,6 +3,7 @@ mod grid_errors;
|
||||
mod object_placement;
|
||||
mod player_placement;
|
||||
mod portal_placement;
|
||||
mod pushers;
|
||||
mod round_trip;
|
||||
|
||||
use crate::board::Board;
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
//! 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"
|
||||
);
|
||||
assert!(p.script_name.is_none());
|
||||
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());
|
||||
}
|
||||
Reference in New Issue
Block a user