use serde::{Deserialize, Serialize}; use crate::archetype::Archetype; use crate::script::Direction; /// Which directions a solid may be pushed in. /// /// Only meaningful for `solid` cells (a non-solid cell is passable, so nothing is /// ever pushed into it). `Crate` is [`Pushable::Any`]; the directional crates /// constrain pushes to one axis. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Pushable { /// Cannot be pushed. No, /// Pushable in any direction. Any, /// Pushable east/west only. Horizontal, /// Pushable north/south only. Vertical, } impl Pushable { /// Whether a push in `dir` is allowed. pub fn allows(self, dir: Direction) -> bool { match self { Pushable::No => false, Pushable::Any => true, Pushable::Horizontal => matches!(dir, Direction::East | Direction::West), Pushable::Vertical => matches!(dir, Direction::North | Direction::South), } } } /// The behavioral properties of a board cell at runtime. /// /// `Behavior` is a plain data struct returned by [`Archetype::behavior`]. It /// contains the properties the engine needs to simulate a cell — currently /// solidity, opacity, and pushability. Future properties (shootable, etc.) can /// be added here without changing call sites. /// /// For scripted objects, solidity and opacity are stored directly on /// [`ObjectDef`] and will eventually be overridable by Rhai scripts at runtime. #[derive(Copy, Clone, Debug)] pub struct Behavior { /// Whether this cell blocks / participates in movement. A solid cell stops a /// mover (and is the only kind of cell that can later be pushed or receive a /// collision event). This is the inverse of the old `passable` flag. pub solid: bool, /// Whether this cell blocks line of sight (reserved for future rendering). pub opaque: bool, /// Which directions a mover can shove this cell in (only meaningful when `solid`). pub pushable: Pushable, } /// A stable, unique identifier for a board object. /// /// Ids are handed out by [`Board::add_object`] from the per-board /// [`Board::next_object_id`] counter (starting at 1) and never reused, so a /// reference to an object stays valid even as other objects are added or removed. /// This replaces the old "index into `Board::objects`" scheme, which shifted when /// objects were reordered/destroyed. pub type ObjectId = u32; /// The single solid occupant of a board cell, returned by [`Board::solid_at`]. /// /// At most one solid — the player, a grid [`Archetype`], *or* an [`ObjectDef`] — may /// occupy a cell (the invariant enforced at load time), so this represents the one /// thing a mover would collide with there. pub enum Solid { /// The player occupies the cell. The player is solid (it blocks movers) and /// pushable in any direction (see [`Board::is_pushable`]). Player, /// The cell's grid archetype is itself solid (e.g. [`Archetype::Wall`]). Cell(Archetype), /// A solid [`ObjectId`] occupies the cell. Object(ObjectId), } /// A portal that teleports the player to a named entry point on another board. /// /// Portals are loaded from `[[portals]]` entries in `.toml` map files and /// stored on [`Board`]. When the player steps onto a portal's cell, they /// should be moved to `target_entry` on the board named by `target_map`. /// /// **Not yet runtime-wired.** Portal navigation (multi-board loading and /// switching) is a future feature. #[derive(Deserialize, Serialize)] pub struct PortalDef { /// Column of this portal on the board (0-indexed). pub x: usize, /// Row of this portal on the board (0-indexed). pub y: usize, /// File name (without extension) of the target board, e.g. `"cave"`. pub target_map: String, /// Named entry point on the target board where the player arrives. pub target_entry: String, } /// The player's current position on the board. /// /// The player is currently a special entity rendered on top of the board /// rather than being stored as a board cell. This is expected to change: /// the player will eventually become a scripted object that responds to /// input events, at which point this struct may be removed or made optional. /// See the "player may become an object" notes in `CLAUDE.md` for the design /// tensions this implies. #[derive(Copy, Clone)] pub struct Player { /// Column position (0-indexed, increasing rightward). pub x: i32, /// Row position (0-indexed, increasing downward). pub y: i32, }