tiger_lib/vic3/data/
provinces.rs

1use std::path::PathBuf;
2
3use image::{DynamicImage, Rgb};
4use itertools::Itertools;
5
6use crate::everything::Everything;
7use crate::fileset::{FileEntry, FileHandler};
8use crate::helpers::TigerHashSet;
9use crate::item::Item;
10use crate::parse::ParserMemory;
11use crate::report::{ErrorKey, Severity, err, report};
12use crate::token::Token;
13
14#[derive(Clone, Debug, Default)]
15pub struct Vic3Provinces {
16    /// Colors in the provinces.png
17    colors: TigerHashSet<Rgb<u8>>,
18
19    /// Kept and used for error reporting.
20    provinces_png: Option<FileEntry>,
21}
22
23impl Vic3Provinces {
24    pub fn verify_exists_implied(&self, key: &str, item: &Token, max_sev: Severity) {
25        if !self.exists(key) {
26            // TODO: determine the severity of a missing province. Does it cause crashes?
27            let msg = "province not found on map";
28            let sev = Item::Province.severity().at_most(max_sev);
29            report(ErrorKey::MissingItem, sev).msg(msg).loc(item).push();
30        }
31    }
32
33    pub fn exists(&self, key: &str) -> bool {
34        // If we failed to load the provinces.png, then don't complain about individual provinces not being found.
35        if self.provinces_png.is_none() {
36            return true;
37        }
38        if key.len() != 7 {
39            return false; // not a valid province id
40        }
41        if let Some(hexid) = key.strip_prefix('x') {
42            if let Ok(r) = u8::from_str_radix(&hexid[0..2], 16) {
43                if let Ok(g) = u8::from_str_radix(&hexid[2..4], 16) {
44                    if let Ok(b) = u8::from_str_radix(&hexid[4..6], 16) {
45                        return self.colors.contains(&Rgb([r, g, b]));
46                    }
47                }
48            }
49        }
50        false
51    }
52
53    #[allow(clippy::unused_self)]
54    pub fn validate(&self, _data: &Everything) {}
55}
56
57impl FileHandler<DynamicImage> for Vic3Provinces {
58    fn subpath(&self) -> PathBuf {
59        PathBuf::from("map_data/provinces.png")
60    }
61
62    fn load_file(&self, entry: &FileEntry, _parser: &ParserMemory) -> Option<DynamicImage> {
63        if entry.path().components().count() == 2 {
64            let img = match image::open(entry.fullpath()) {
65                Ok(img) => img,
66                Err(e) => {
67                    let msg = format!("could not read `{}`: {e:#}", entry.path().display());
68                    // TODO: does this crash?
69                    err(ErrorKey::ReadError).msg(msg).loc(entry).push();
70                    return None;
71                }
72            };
73            if let DynamicImage::ImageRgb8(_) = img {
74                return Some(img);
75            }
76            let msg = format!(
77                "`{}` has wrong color format `{:?}`, should be Rgb8",
78                entry.path().display(),
79                img.color()
80            );
81            // TODO: does this crash?
82            err(ErrorKey::ImageFormat).msg(msg).loc(entry).push();
83        }
84        None
85    }
86
87    fn handle_file(&mut self, entry: &FileEntry, img: DynamicImage) {
88        self.provinces_png = Some(entry.clone());
89        if let DynamicImage::ImageRgb8(img) = img {
90            for pixel in img.pixels().dedup() {
91                self.colors.insert(*pixel);
92            }
93        }
94    }
95}