Floor layer
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
use crate::floor::{FloorSpec, build_floor};
|
||||
use crate::game::{Archetype, Board, FontSpec, Glyph, ObjectDef, Player, PortalDef};
|
||||
use crate::log::LogLine;
|
||||
use color::Rgba8;
|
||||
@@ -25,6 +26,10 @@ pub struct MapFile {
|
||||
/// Optional `[font]` section specifying a bitmap font for this board.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub font: Option<FontHeader>,
|
||||
/// Optional `[floor]` declaration: the visual floor layer (a generator name,
|
||||
/// a single glyph, or a grid with its own palette). See [`FloorSpec`].
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub floor: Option<FloorSpec>,
|
||||
/// Any `[[objects]]` entries. Optional; defaults to empty.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub(crate) objects: Vec<MapFileObjectEntry>,
|
||||
@@ -76,9 +81,9 @@ pub struct FontHeader {
|
||||
/// `tile = " "` for convenience (the char is converted to its Unicode scalar).
|
||||
/// This means existing maps that used `ch = "#"` can be migrated by renaming
|
||||
/// the key to `tile`; the char form keeps working.
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Deserialize, Serialize, Clone, Copy)]
|
||||
#[serde(untagged)]
|
||||
pub(crate) enum TileIndex {
|
||||
pub enum TileIndex {
|
||||
/// A direct tile index (e.g. `tile = 35`).
|
||||
Num(u32),
|
||||
/// A single-character shorthand (e.g. `tile = "#"`); converted to its Unicode scalar.
|
||||
@@ -195,7 +200,7 @@ fn is_zoom_default(z: &u32) -> bool {
|
||||
|
||||
/// Parses an `"#RRGGBB"` hex color string into an [`Rgba8`].
|
||||
/// Returns opaque black on any parse failure.
|
||||
fn parse_color(hex: &str) -> Rgba8 {
|
||||
pub(crate) fn parse_color(hex: &str) -> Rgba8 {
|
||||
let hex = hex.trim_start_matches('#');
|
||||
if hex.len() != 6 {
|
||||
return Rgba8 {
|
||||
@@ -212,7 +217,7 @@ fn parse_color(hex: &str) -> Rgba8 {
|
||||
}
|
||||
|
||||
/// Converts an [`Rgba8`] to an `"#RRGGBB"` hex string (alpha is ignored).
|
||||
fn color_to_hex(color: Rgba8) -> String {
|
||||
pub(crate) fn color_to_hex(color: Rgba8) -> String {
|
||||
format!("#{:02X}{:02X}{:02X}", color.r, color.g, color.b)
|
||||
}
|
||||
|
||||
@@ -409,6 +414,12 @@ impl TryFrom<MapFile> for Board {
|
||||
tile_h: f.tile_h,
|
||||
});
|
||||
|
||||
// Build the cached floor layer from the (optional) `[floor]` declaration.
|
||||
// Best-effort: any problem is recorded on `load_errors`, not fatal. The
|
||||
// raw spec is kept on the board so a save can round-trip it.
|
||||
let floor_spec = mf.floor;
|
||||
let floor = build_floor(&floor_spec, w, h, &mut load_errors);
|
||||
|
||||
// Convert MapFileObjectEntry → ObjectDef, enforcing one solid per cell.
|
||||
// `solid_occupied` is seeded from the grid (solid archetypes); each solid
|
||||
// object then claims its cell, and a second solid on a cell is dropped.
|
||||
@@ -513,6 +524,8 @@ impl TryFrom<MapFile> for Board {
|
||||
width: w,
|
||||
height: h,
|
||||
cells,
|
||||
floor,
|
||||
floor_spec,
|
||||
player: Player {
|
||||
x: px as i32,
|
||||
y: py as i32,
|
||||
@@ -648,6 +661,9 @@ impl From<&Board> for MapFile {
|
||||
palette,
|
||||
grid: GridData { content },
|
||||
font,
|
||||
// Round-trip the original floor declaration verbatim (generators
|
||||
// re-randomize on the next load; literal glyph grids are exact).
|
||||
floor: board.floor_spec.clone(),
|
||||
objects,
|
||||
portals,
|
||||
scripts: board.scripts.clone(),
|
||||
@@ -1026,4 +1042,52 @@ bg = "#000000"
|
||||
assert_eq!(obj2.glyph.fg, obj.glyph.fg);
|
||||
assert_eq!(obj2.glyph.bg, obj.glyph.bg);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn floor_spec_round_trips_through_toml() {
|
||||
// A 2×2 board with a grid floor (a generator and a fixed glyph) must survive
|
||||
// save→load with its `[floor]` declaration intact, and the reloaded board's
|
||||
// computed floor must place the fixed glyph back at its declared cell.
|
||||
let toml = r##"
|
||||
[map]
|
||||
name = "Test"
|
||||
width = 2
|
||||
height = 2
|
||||
player_start = [0, 0]
|
||||
|
||||
[palette]
|
||||
"." = { archetype = "empty", tile = 46, fg = "#000000", bg = "#000000" }
|
||||
|
||||
[grid]
|
||||
content = """
|
||||
..
|
||||
..
|
||||
"""
|
||||
|
||||
[floor]
|
||||
content = """
|
||||
g.
|
||||
.g
|
||||
"""
|
||||
|
||||
[floor.palette]
|
||||
"g" = "grass"
|
||||
"." = { tile = "#", fg = "#010203", bg = "#040506" }
|
||||
"##;
|
||||
let board = load_board(toml);
|
||||
assert!(board.floor_spec.is_some());
|
||||
let fixed = Glyph {
|
||||
tile: '#' as u32,
|
||||
fg: parse_color("#010203"),
|
||||
bg: parse_color("#040506"),
|
||||
};
|
||||
// (1,0) is the fixed `.` floor glyph (the cell archetype is Empty).
|
||||
assert_eq!(board.display_glyph(1, 0), fixed);
|
||||
|
||||
// Save back to TOML and reload; the floor declaration must be preserved.
|
||||
let toml_out = toml::to_string_pretty(&MapFile::from(&board)).unwrap();
|
||||
let board2 = load_board(&toml_out);
|
||||
assert!(board2.floor_spec.is_some());
|
||||
assert_eq!(board2.display_glyph(1, 0), fixed);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user