object layer
This commit is contained in:
+120
-5
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user