some auto-refactoring
This commit is contained in:
+72
-117
@@ -3,11 +3,11 @@ use crate::board::Board;
|
||||
use crate::log::LogLine;
|
||||
use color::Rgba8;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::convert::TryFrom;
|
||||
use std::path::Path;
|
||||
use crate::archetype::Archetype;
|
||||
use crate::font::FontSpec;
|
||||
use crate::glyph::Glyph;
|
||||
use crate::object_def::ObjectDef;
|
||||
use crate::utils::{ObjectId, Player, PortalDef};
|
||||
@@ -28,9 +28,6 @@ pub struct MapFile {
|
||||
pub palette: HashMap<String, PaletteEntry>,
|
||||
/// The `[grid]` section: the multi-line string that defines cell layout.
|
||||
pub grid: GridData,
|
||||
/// 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")]
|
||||
@@ -58,22 +55,6 @@ pub struct MapHeader {
|
||||
/// Name of the board-level script in the `[scripts]` table, if any.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub board_script_name: Option<String>,
|
||||
/// Integer tile scale factor. 1 = natural size. Omitted from files when 1 (the default).
|
||||
#[serde(default = "default_zoom", skip_serializing_if = "is_zoom_default")]
|
||||
pub zoom: u32,
|
||||
}
|
||||
|
||||
/// The `[font]` section of a map file, specifying a bitmap font for this board.
|
||||
///
|
||||
/// When absent, the app's default embedded CP437 font is used.
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct FontHeader {
|
||||
/// Path to the PNG font image, relative to the working directory.
|
||||
pub path: String,
|
||||
/// Width of each tile in the font image, in pixels.
|
||||
pub tile_w: u32,
|
||||
/// Height of each tile in the font image, in pixels.
|
||||
pub tile_h: u32,
|
||||
}
|
||||
|
||||
/// A tile index in a palette entry: either a plain integer or a character literal.
|
||||
@@ -200,11 +181,59 @@ pub(crate) struct MapFileObjectEntry {
|
||||
fn default_true() -> bool {
|
||||
true
|
||||
}
|
||||
fn default_zoom() -> u32 {
|
||||
1
|
||||
|
||||
/// Constructs a [`Glyph`] from the three raw TOML-sourced fields.
|
||||
fn make_glyph(tile: u32, fg: &str, bg: &str) -> Glyph {
|
||||
Glyph { tile, fg: parse_color(fg), bg: parse_color(bg) }
|
||||
}
|
||||
fn is_zoom_default(z: &u32) -> bool {
|
||||
*z == 1
|
||||
|
||||
/// Constructs a serializable [`PaletteEntry`] from core board types.
|
||||
fn make_palette_entry(glyph: Glyph, arch: Archetype) -> PaletteEntry {
|
||||
PaletteEntry {
|
||||
archetype: arch.name().to_string(),
|
||||
tile: TileIndex::Num(glyph.tile),
|
||||
fg: color_to_hex(glyph.fg),
|
||||
bg: color_to_hex(glyph.bg),
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves the player's starting cell from `player_start`, falling back to `(0, 0)` on any error.
|
||||
fn resolve_player_start(
|
||||
start: &PlayerStart,
|
||||
w: usize,
|
||||
h: usize,
|
||||
player_grid_pos: Option<(usize, usize)>,
|
||||
player_grid_count: usize,
|
||||
errors: &mut Vec<LogLine>,
|
||||
) -> (usize, usize) {
|
||||
match start {
|
||||
PlayerStart::Coord([x, y])
|
||||
if *x >= 0 && *y >= 0 && (*x as usize) < w && (*y as usize) < h =>
|
||||
{
|
||||
(*x as usize, *y as usize)
|
||||
}
|
||||
PlayerStart::Coord([x, y]) => {
|
||||
errors.push(LogLine::error(format!(
|
||||
"player_start ({x}, {y}) is out of bounds; placing player at (0, 0)"
|
||||
)));
|
||||
(0, 0)
|
||||
}
|
||||
PlayerStart::Char(c) => match (player_grid_pos, player_grid_count) {
|
||||
(Some(pos), 1) => pos,
|
||||
(Some(pos), n) => {
|
||||
errors.push(LogLine::error(format!(
|
||||
"player char '{c}' appears {n} times in the grid; using the first"
|
||||
)));
|
||||
pos
|
||||
}
|
||||
_ => {
|
||||
errors.push(LogLine::error(format!(
|
||||
"player char '{c}' not found in the grid; placing player at (0, 0)"
|
||||
)));
|
||||
(0, 0)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses an `"#RRGGBB"` hex color string into an [`Rgba8`].
|
||||
@@ -270,11 +299,7 @@ impl TryFrom<MapFile> for Board {
|
||||
let tile = entry.tile.into_u32();
|
||||
match Archetype::try_from(entry.archetype.as_str()) {
|
||||
Ok(archetype) => {
|
||||
let glyph = Glyph {
|
||||
tile,
|
||||
fg: parse_color(&entry.fg),
|
||||
bg: parse_color(&entry.bg),
|
||||
};
|
||||
let glyph = make_glyph(tile, &entry.fg, &entry.bg);
|
||||
palette.insert(key_char, (glyph, archetype));
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -351,7 +376,6 @@ impl TryFrom<MapFile> for Board {
|
||||
continue;
|
||||
}
|
||||
// It must be unique across objects — keep the first claimant, drop later ones.
|
||||
use std::collections::hash_map::Entry;
|
||||
match palette_char_owner.entry(ch) {
|
||||
Entry::Occupied(_) => {
|
||||
load_errors.push(LogLine::error(format!(
|
||||
@@ -402,7 +426,7 @@ impl TryFrom<MapFile> for Board {
|
||||
if skip[owner] {
|
||||
continue;
|
||||
}
|
||||
if obj_palette_positions.get(&ch).map_or(true, Vec::is_empty) {
|
||||
if obj_palette_positions.get(&ch).is_none_or(Vec::is_empty) {
|
||||
load_errors.push(LogLine::error(format!(
|
||||
"object palette char '{ch}' not found in the grid; skipping object"
|
||||
)));
|
||||
@@ -410,12 +434,6 @@ impl TryFrom<MapFile> for Board {
|
||||
}
|
||||
}
|
||||
|
||||
let font = mf.font.map(|f| FontSpec {
|
||||
path: f.path,
|
||||
tile_w: f.tile_w,
|
||||
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.
|
||||
@@ -430,34 +448,14 @@ impl TryFrom<MapFile> for Board {
|
||||
// Resolve the player's cell (nonfatal; fall back to (0, 0) on any problem),
|
||||
// then let the player *win* its cell: clear solid terrain under it and claim
|
||||
// the cell so a solid object placed here is dropped by the loop below.
|
||||
let (px, py) = match mf.map.player_start {
|
||||
PlayerStart::Coord([x, y])
|
||||
if x >= 0 && y >= 0 && (x as usize) < w && (y as usize) < h =>
|
||||
{
|
||||
(x as usize, y as usize)
|
||||
}
|
||||
PlayerStart::Coord([x, y]) => {
|
||||
load_errors.push(LogLine::error(format!(
|
||||
"player_start ({x}, {y}) is out of bounds; placing player at (0, 0)"
|
||||
)));
|
||||
(0, 0)
|
||||
}
|
||||
PlayerStart::Char(c) => match (player_grid_pos, player_grid_count) {
|
||||
(Some(pos), 1) => pos,
|
||||
(Some(pos), n) => {
|
||||
load_errors.push(LogLine::error(format!(
|
||||
"player char '{c}' appears {n} times in the grid; using the first"
|
||||
)));
|
||||
pos
|
||||
}
|
||||
_ => {
|
||||
load_errors.push(LogLine::error(format!(
|
||||
"player char '{c}' not found in the grid; placing player at (0, 0)"
|
||||
)));
|
||||
(0, 0)
|
||||
}
|
||||
},
|
||||
};
|
||||
let (px, py) = resolve_player_start(
|
||||
&mf.map.player_start,
|
||||
w,
|
||||
h,
|
||||
player_grid_pos,
|
||||
player_grid_count,
|
||||
&mut load_errors,
|
||||
);
|
||||
let pidx = py * w + px;
|
||||
if solid_occupied[pidx] {
|
||||
cells[pidx] = canonical_empty;
|
||||
@@ -504,7 +502,6 @@ impl TryFrom<MapFile> for Board {
|
||||
// Validate name uniqueness: first claimant keeps the name, later ones
|
||||
// have their name cleared and a nonfatal error is recorded.
|
||||
e.name.as_ref().and_then(|n| {
|
||||
use std::collections::hash_map::Entry;
|
||||
match seen_names.entry(n.clone()) {
|
||||
Entry::Vacant(v) => { v.insert(i); Some(n.clone()) }
|
||||
Entry::Occupied(o) => {
|
||||
@@ -524,11 +521,7 @@ impl TryFrom<MapFile> for Board {
|
||||
id: 0, // stamped by Board::add_object
|
||||
x,
|
||||
y,
|
||||
glyph: Glyph {
|
||||
tile: e.tile.into_u32(),
|
||||
fg: parse_color(&e.fg),
|
||||
bg: parse_color(&e.bg),
|
||||
},
|
||||
glyph: make_glyph(e.tile.into_u32(), &e.fg, &e.bg),
|
||||
solid: e.solid,
|
||||
opaque: e.opaque,
|
||||
pushable: e.pushable,
|
||||
@@ -572,8 +565,6 @@ impl TryFrom<MapFile> for Board {
|
||||
objects,
|
||||
next_object_id,
|
||||
portals: mf.portals,
|
||||
font,
|
||||
zoom: mf.map.zoom,
|
||||
board_script_name: mf.map.board_script_name,
|
||||
load_errors,
|
||||
})
|
||||
@@ -607,33 +598,14 @@ impl From<&Board> for MapFile {
|
||||
for x in 0..board.width {
|
||||
let (glyph, arch) = board.get(x, y);
|
||||
let key = (*glyph, *arch);
|
||||
if let std::collections::hash_map::Entry::Vacant(e) = cell_to_key.entry(key) {
|
||||
if key == canonical_empty {
|
||||
e.insert(' ');
|
||||
palette.insert(
|
||||
" ".to_string(),
|
||||
PaletteEntry {
|
||||
archetype: arch.name().to_string(),
|
||||
tile: TileIndex::Num(glyph.tile),
|
||||
fg: color_to_hex(glyph.fg),
|
||||
bg: color_to_hex(glyph.bg),
|
||||
},
|
||||
);
|
||||
if let Entry::Vacant(e) = cell_to_key.entry(key) {
|
||||
let ch = if key == canonical_empty {
|
||||
' '
|
||||
} else {
|
||||
let ch = *pool_iter
|
||||
.next()
|
||||
.expect("board has more than 92 unique non-canonical cell types");
|
||||
e.insert(ch);
|
||||
palette.insert(
|
||||
ch.to_string(),
|
||||
PaletteEntry {
|
||||
archetype: arch.name().to_string(),
|
||||
tile: TileIndex::Num(glyph.tile),
|
||||
fg: color_to_hex(glyph.fg),
|
||||
bg: color_to_hex(glyph.bg),
|
||||
},
|
||||
);
|
||||
}
|
||||
*pool_iter.next().expect("board has more than 92 unique non-canonical cell types")
|
||||
};
|
||||
e.insert(ch);
|
||||
palette.insert(ch.to_string(), make_palette_entry(*glyph, *arch));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -651,12 +623,6 @@ impl From<&Board> for MapFile {
|
||||
// Trailing newline ensures the last row is complete; str::lines handles it correctly.
|
||||
let content = rows.join("\n") + "\n";
|
||||
|
||||
let font = board.font.as_ref().map(|f| FontHeader {
|
||||
path: f.path.clone(),
|
||||
tile_w: f.tile_w,
|
||||
tile_h: f.tile_h,
|
||||
});
|
||||
|
||||
// Convert ObjectDef → MapFileObjectEntry, encoding the glyph as TOML-compatible fields.
|
||||
let objects: Vec<MapFileObjectEntry> = board
|
||||
.objects
|
||||
@@ -680,16 +646,7 @@ impl From<&Board> for MapFile {
|
||||
})
|
||||
.collect();
|
||||
|
||||
let portals = board
|
||||
.portals
|
||||
.iter()
|
||||
.map(|p| PortalDef {
|
||||
x: p.x,
|
||||
y: p.y,
|
||||
target_map: p.target_map.clone(),
|
||||
target_entry: p.target_entry.clone(),
|
||||
})
|
||||
.collect();
|
||||
let portals = board.portals.clone();
|
||||
|
||||
MapFile {
|
||||
map: MapHeader {
|
||||
@@ -698,11 +655,9 @@ impl From<&Board> for MapFile {
|
||||
height: board.height,
|
||||
player_start: PlayerStart::Coord([board.player.x, board.player.y]),
|
||||
board_script_name: board.board_script_name.clone(),
|
||||
zoom: board.zoom,
|
||||
},
|
||||
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(),
|
||||
|
||||
Reference in New Issue
Block a user