tiger_lib/data/
defines.rs

1use std::path::PathBuf;
2
3use crate::block::{BV, Block};
4use crate::everything::Everything;
5use crate::fileset::{FileEntry, FileHandler};
6use crate::game::Game;
7use crate::helpers::{TigerHashMap, dup_error};
8#[cfg(feature = "ck3")]
9use crate::item::Item;
10use crate::parse::ParserMemory;
11use crate::pdxfile::PdxFile;
12#[cfg(feature = "ck3")]
13use crate::report::Severity;
14use crate::report::{ErrorKey, err};
15use crate::token::Token;
16
17#[derive(Clone, Debug, Default)]
18pub struct Defines {
19    defines: TigerHashMap<String, Define>,
20}
21
22impl Defines {
23    pub fn load_item(&mut self, group: Token, name: Token, bv: &BV) {
24        let key = format!("{}|{}", &group, &name);
25        if let Some(other) = self.defines.get(&key) {
26            if other.name.loc.kind >= name.loc.kind && !bv.equivalent(&other.bv) {
27                dup_error(&name, &other.name, "define");
28            }
29        }
30        self.defines.insert(key, Define::new(group, name, bv.clone()));
31    }
32
33    pub fn exists(&self, key: &str) -> bool {
34        self.defines.contains_key(key)
35    }
36
37    // TODO: figure out some way to represent the group as well
38    pub fn iter_keys(&self) -> impl Iterator<Item = &Token> {
39        self.defines.values().map(|item| &item.name)
40    }
41
42    pub fn validate(&self, data: &Everything) {
43        for item in self.defines.values() {
44            item.validate(data);
45        }
46    }
47
48    #[cfg(feature = "jomini")]
49    pub fn get_bv(&self, key: &str) -> Option<&BV> {
50        self.defines.get(key).map(|d| &d.bv)
51    }
52}
53
54impl FileHandler<Block> for Defines {
55    fn subpath(&self) -> PathBuf {
56        PathBuf::from("common/defines")
57    }
58
59    fn load_file(&self, entry: &FileEntry, parser: &ParserMemory) -> Option<Block> {
60        if !entry.filename().to_string_lossy().ends_with(".txt") {
61            return None;
62        }
63
64        PdxFile::read(entry, parser)
65    }
66
67    fn handle_file(&mut self, _entry: &FileEntry, mut block: Block) {
68        // TODO HOI4: Hoi4 has a toplevel group
69        for (group, block) in block.drain_definitions_warn() {
70            for (name, bv) in block.iter_assignments_and_definitions_warn() {
71                self.load_item(group.clone(), name.clone(), bv);
72            }
73        }
74    }
75}
76
77#[derive(Clone, Debug)]
78pub struct Define {
79    #[allow(dead_code)] // TODO
80    group: Token,
81    name: Token,
82    bv: BV,
83}
84
85impl Define {
86    pub fn new(group: Token, name: Token, bv: BV) -> Self {
87        Self { group, name, bv }
88    }
89
90    #[allow(clippy::unused_self)]
91    #[allow(unused_variables)] // because only ck3 uses `data`
92    pub fn validate(&self, data: &Everything) {
93        let defines_map = match Game::game() {
94            #[cfg(feature = "ck3")]
95            Game::Ck3 => &crate::ck3::tables::defines::DEFINES_MAP,
96            #[cfg(feature = "vic3")]
97            Game::Vic3 => &crate::vic3::tables::defines::DEFINES_MAP,
98            #[cfg(feature = "imperator")]
99            Game::Imperator => &crate::imperator::tables::defines::DEFINES_MAP,
100            #[cfg(feature = "eu5")]
101            Game::Eu5 => &crate::eu5::tables::defines::DEFINES_MAP,
102            #[cfg(feature = "hoi4")]
103            Game::Hoi4 => &crate::hoi4::tables::defines::DEFINES_MAP,
104        };
105
106        // TODO: save key instead of reconstructing it here?
107        let key = format!("{}|{}", &self.group, &self.name);
108        if let Some(dt) = defines_map.get(&*key) {
109            dt.validate(&self.bv, data);
110        } else {
111            let msg = format!("unknown define {key}");
112            err(ErrorKey::UnknownField).msg(msg).loc(&self.name).push();
113        }
114
115        #[cfg(feature = "ck3")]
116        if self.group.is("NGameIcons") && self.name.is("PIETY_GROUPS") {
117            if let Some(icon_path) =
118                data.get_defined_string_warn(&self.name, "NGameIcons|PIETY_LEVEL_PATH")
119            {
120                if let Some(groups) = self.bv.expect_block() {
121                    for icon_group in groups.iter_values_warn() {
122                        for nr in &["00", "01", "02", "03", "04", "05"] {
123                            let pathname = format!("{icon_path}/icon_piety_{icon_group}_{nr}.dds");
124                            data.verify_exists_implied_max_sev(
125                                Item::File,
126                                &pathname,
127                                icon_group,
128                                Severity::Warning,
129                            );
130                        }
131                    }
132                }
133            }
134        }
135    }
136}