//! Pushers are now scripted objects (the `pusher_*` archetypes expand into objects //! carrying the embedded `pusher.rhai` plus a `BUILTIN_pusher_` tag). use super::{grid, 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, &grid( "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" ); // Each alias gets its own compile-cache key (e.g. "BUILTIN_pusher_east") so the // script can read direction from Me.has_tag("BUILTIN_pusher_east"). assert_eq!(p.script_name.as_deref(), Some("BUILTIN_pusher_east")); 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, &grid( "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(1, 0).1, Archetype::Empty, "crate left its start cell" ); assert!( (2..b.width).any(|x| b.get(x, 0).1 == Archetype::Crate), "crate was shoved east" ); } #[test] fn pusher_blocked_by_wall_stays_put() { let board = load_board(&map( 4, 1, &grid( "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, &grid( "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()); }