The roadmap capstone: the dungeon is now populated.
- New entity layer: src/entity.rs (Entity { kind, at }, EntityKind {Entrance,
Exit, Treasure, Monster}); Map gains an `entities: Vec<Entity>` overlay field
(serde round-tripped). Entities aren't terrain, so no new Tile and the frozen
viz is untouched.
- EntityPlacer/EntityConfig: places one Entrance (random room), one Exit (the
room farthest from it — a traversal goal), per-room Treasure (treasure_chance)
and Monsters (monster_chance, 1..=max, never in the entrance room). All on
plain Floor only, never two per cell, fully deterministic.
- Handoff via the Blackboard (the designed side channel for non-tile/region/edge
data) under entity::BLACKBOARD_KEY, harvested by Pipeline into Map.entities —
so GenContext's 25 literals stay untouched.
- Renderer draws iconic markers on the final stage: green beacon (entrance),
cyan portal (exit), gold diamond (treasure), crimson eyed-blob (monster), each
with a soft glow. New entities toggle + treasure/monster sliders + readout.
Core: 135 tests green (6 new: one entrance/one exit on distinct floor, entrance
room monster-free, determinism, empty-map, recipe populated-check). clippy
clean. Pipeline is now 9 passes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
57 lines
2.3 KiB
Rust
57 lines
2.3 KiB
Rust
//! Entities: the things that *populate* a map, distinct from its terrain.
|
|
//!
|
|
//! Where [`Tile`](crate::map::Tile) says *what the ground is* and
|
|
//! [`Region`](crate::region::Region) says *what areas exist*, an [`Entity`] is a
|
|
//! discrete thing sitting **on** a walkable cell — a stairway, a chest, a
|
|
//! monster. Entities never change the terrain (they are an overlay layer on the
|
|
//! [`Map`](crate::map::Map)), so a renderer draws them on top of the tiles and a
|
|
//! game treats them as occupants of a cell.
|
|
//!
|
|
//! Like [`Tile`](crate::map::Tile) and [`RegionKind`](crate::region::RegionKind),
|
|
//! the kind enum starts minimal and grows additively; passes and renderers match
|
|
//! only on the kinds they care about. All types derive serde so entities travel
|
|
//! inside a `Map` through JSON.
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::geometry::Point;
|
|
|
|
/// The [`Blackboard`](crate::blackboard::Blackboard) key under which the entity
|
|
/// placer stashes its `Vec<Entity>` for [`Pipeline`](crate::pass::Pipeline) to
|
|
/// harvest into the finished [`Map`](crate::map::Map). Shared so the producer
|
|
/// (the placer pass) and the consumer (the engine) never drift on the name.
|
|
pub const BLACKBOARD_KEY: &str = "entities";
|
|
|
|
/// What an [`Entity`] is. Additive: new kinds (traps, NPCs, altars, …) slot in
|
|
/// without disturbing existing matches.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
pub enum EntityKind {
|
|
/// Where the dungeon is entered from (the player's start / stairs up).
|
|
Entrance,
|
|
/// The dungeon's goal — the room farthest from the entrance (stairs down).
|
|
Exit,
|
|
/// A pickup: treasure, loot, an item.
|
|
Treasure,
|
|
/// A creature occupying the cell.
|
|
Monster,
|
|
}
|
|
|
|
/// A single entity: its [`EntityKind`] and the cell it occupies.
|
|
///
|
|
/// `at` is always a walkable [`Floor`](crate::map::Tile::Floor) cell in a
|
|
/// well-formed map; the placer only ever drops entities onto open floor and never
|
|
/// stacks two on one cell.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
pub struct Entity {
|
|
/// What this entity is.
|
|
pub kind: EntityKind,
|
|
/// The cell it occupies.
|
|
pub at: Point,
|
|
}
|
|
|
|
impl Entity {
|
|
/// Creates an entity of `kind` at cell `at`.
|
|
pub const fn new(kind: EntityKind, at: Point) -> Self {
|
|
Entity { kind, at }
|
|
}
|
|
}
|