Skip to main content

flatland_presentation/
validate.rs

1//! Validate `sprite_modes` maps against gfx sprite sheets.
2
3use crate::{SpriteCatalog, SpriteModeMap};
4
5/// Validate that every value in `sprite_modes` exists on the sheet for `tile_id`.
6pub fn validate_sprite_mode_maps(
7    owner_label: &str,
8    tile_id: Option<&str>,
9    sprite_modes: &SpriteModeMap,
10    catalog: &SpriteCatalog,
11) -> Vec<String> {
12    let mut errors = Vec::new();
13    let Some(tile_id) = tile_id.filter(|s| !s.is_empty()) else {
14        if !sprite_modes.is_empty() {
15            errors.push(format!(
16                "{owner_label}: sprite_modes set but tile_id is missing"
17            ));
18        }
19        return errors;
20    };
21    if sprite_modes.is_empty() {
22        return errors;
23    }
24    let sheet_modes = catalog.mode_ids(tile_id);
25    if sheet_modes.is_empty() {
26        errors.push(format!(
27            "{owner_label}: tile_id '{tile_id}' not found in sprite catalog or has no modes"
28        ));
29        return errors;
30    }
31    for (key, mode) in sprite_modes {
32        if !mode_exists_on_sheet(mode, &sheet_modes) {
33            errors.push(format!(
34                "{owner_label}: sprite_modes.{key} = '{mode}' not on sheet '{tile_id}' (have: {})",
35                sheet_modes.join(", ")
36            ));
37        }
38    }
39    errors
40}
41
42/// True when `mode` is on the sheet, or directional variants like `walk_north` exist for base `walk`.
43pub fn mode_exists_on_sheet(mode: &str, sheet_modes: &[String]) -> bool {
44    if sheet_modes.iter().any(|m| m == mode) {
45        return true;
46    }
47    let prefix = format!("{mode}_");
48    sheet_modes.iter().any(|m| m.starts_with(&prefix))
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54    use crate::catalog::{load_sprite_catalog, SpriteSheetModes};
55    use std::collections::HashMap;
56
57    #[test]
58    fn catches_unknown_mode() {
59        let dir = crate::catalog::default_sprites_dir().expect("sprites dir");
60        let catalog = load_sprite_catalog(&dir).expect("load");
61        let mut map = SpriteModeMap::new();
62        map.insert("available".into(), "bogus_mode_xyz".into());
63        let errs = validate_sprite_mode_maps("item.test", Some("resource.oak_log"), &map, &catalog);
64        assert_eq!(errs.len(), 1);
65        assert!(errs[0].contains("bogus_mode_xyz"));
66    }
67
68    #[test]
69    fn accepts_directional_prefix_for_base_mode() {
70        let sheet_modes = vec!["idle_north".into(), "walk_south".into(), "combat".into()];
71        assert!(mode_exists_on_sheet("idle", &sheet_modes));
72        assert!(mode_exists_on_sheet("walk", &sheet_modes));
73        assert!(mode_exists_on_sheet("combat", &sheet_modes));
74        assert!(!mode_exists_on_sheet("run", &sheet_modes));
75    }
76}