object layer

This commit is contained in:
2026-05-30 19:25:10 -05:00
parent 96ec8e8e92
commit d19737c113
5 changed files with 207 additions and 53 deletions
+120 -5
View File
@@ -26,7 +26,7 @@ pub struct MapFile {
pub font: Option<FontHeader>,
/// Any `[[objects]]` entries. Optional; defaults to empty.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub objects: Vec<ObjectDef>,
pub objects: Vec<MapFileObjectEntry>,
/// Any `[[portals]]` entries. Optional; defaults to empty.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub portals: Vec<PortalDef>,
@@ -121,6 +121,28 @@ pub struct GridData {
pub content: String,
}
/// One `[[objects]]` entry in a map file.
///
/// Used only for TOML serialization/deserialization. Converted to/from
/// [`ObjectDef`] (which holds a [`Glyph`] directly) in the [`TryFrom`] /
/// [`From`] impls below.
#[derive(Deserialize, Serialize)]
pub(crate) struct MapFileObjectEntry {
/// Column of this object on the board (0-indexed).
x: usize,
/// Row of this object on the board (0-indexed).
y: usize,
/// Tile index into the board's bitmap font. Accepts integer or single-char string.
tile: TileIndex,
/// Foreground color as an `"#RRGGBB"` hex string.
fg: String,
/// Background color as an `"#RRGGBB"` hex string.
bg: String,
/// Name of the Rhai script in the `[scripts]` table, if any.
#[serde(default, skip_serializing_if = "Option::is_none")]
script_name: Option<String>,
}
/// Parses an `"#RRGGBB"` hex color string into a [`Color32`].
/// Returns black on any parse failure.
fn parse_color(hex: &str) -> Color32 {
@@ -222,6 +244,22 @@ impl TryFrom<MapFile> for Board {
tile_h: f.tile_h,
});
// Convert MapFileObjectEntry → ObjectDef, resolving tile index and color strings.
let objects = mf
.objects
.into_iter()
.map(|e| ObjectDef {
x: e.x,
y: e.y,
glyph: Glyph {
tile: e.tile.into_u32(),
fg: parse_color(&e.fg),
bg: parse_color(&e.bg),
},
script_name: e.script_name,
})
.collect();
Ok(Board {
name: mf.map.name,
width: w,
@@ -231,7 +269,7 @@ impl TryFrom<MapFile> for Board {
x: mf.map.player_start[0],
y: mf.map.player_start[1],
},
objects: mf.objects,
objects,
portals: mf.portals,
font,
scripts: mf.scripts,
@@ -317,13 +355,16 @@ impl From<&Board> for MapFile {
tile_h: f.tile_h,
});
// Reconstruct objects as plain structs (ObjectDef/PortalDef don't implement Clone).
let objects = board
// Convert ObjectDef → MapFileObjectEntry, encoding the glyph as TOML-compatible fields.
let objects: Vec<MapFileObjectEntry> = board
.objects
.iter()
.map(|o| ObjectDef {
.map(|o| MapFileObjectEntry {
x: o.x,
y: o.y,
tile: TileIndex::Num(o.glyph.tile),
fg: color_to_hex(o.glyph.fg),
bg: color_to_hex(o.glyph.bg),
script_name: o.script_name.clone(),
})
.collect();
@@ -429,4 +470,78 @@ content = """
assert!(msg.contains("3 characters"), "expected col count in error, got: {msg}");
assert!(msg.contains("width = 4"), "expected declared width in error, got: {msg}");
}
#[test]
fn object_archetype_in_palette_produces_error_block() {
// "object" is no longer a valid palette archetype; it should produce ErrorBlock.
let toml = r##"
[map]
name = "Test"
width = 1
height = 1
player_start = [0, 0]
[palette]
"O" = { archetype = "object", tile = 64, fg = "#00FFFF", bg = "#000000" }
[grid]
content = """
O
"""
"##;
let mf: MapFile = toml::from_str(toml).unwrap();
let board = Board::try_from(mf).unwrap();
let (_, arch) = board.get(0, 0);
assert_eq!(*arch, crate::game::Archetype::ErrorBlock);
}
#[test]
fn object_glyph_round_trips_through_toml() {
// Objects must survive a save→load cycle with their glyph intact.
use eframe::egui::Color32;
let toml = r##"
[map]
name = "Test"
width = 3
height = 3
player_start = [0, 0]
[palette]
"." = { archetype = "empty", tile = 46, fg = "#000000", bg = "#000000" }
[grid]
content = """
...
...
...
"""
[[objects]]
x = 1
y = 1
tile = 64
fg = "#00FFFF"
bg = "#000000"
"##;
let mf: MapFile = toml::from_str(toml).unwrap();
let board = Board::try_from(mf).unwrap();
assert_eq!(board.objects.len(), 1);
let obj = &board.objects[0];
assert_eq!(obj.x, 1);
assert_eq!(obj.y, 1);
assert_eq!(obj.glyph.tile, 64);
assert_eq!(obj.glyph.fg, Color32::from_rgb(0x00, 0xFF, 0xFF));
assert_eq!(obj.glyph.bg, Color32::BLACK);
// Save back to TOML and reload; glyph must survive.
let map_file = MapFile::from(&board);
let toml_out = toml::to_string_pretty(&map_file).unwrap();
let mf2: MapFile = toml::from_str(&toml_out).unwrap();
let board2 = Board::try_from(mf2).unwrap();
let obj2 = &board2.objects[0];
assert_eq!(obj2.glyph.tile, obj.glyph.tile);
assert_eq!(obj2.glyph.fg, obj.glyph.fg);
assert_eq!(obj2.glyph.bg, obj.glyph.bg);
}
}