Skip to main content

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            && other.name.loc.kind >= name.loc.kind
27            && !bv.equivalent(&other.bv)
28        {
29            dup_error(&name, &other.name, "define");
30        }
31        self.defines.insert(key, Define::new(group, name, bv.clone()));
32    }
33
34    pub fn exists(&self, key: &str) -> bool {
35        self.defines.contains_key(key)
36    }
37
38    // TODO: figure out some way to represent the group as well
39    pub fn iter_keys(&self) -> impl Iterator<Item = &Token> {
40        self.defines.values().map(|item| &item.name)
41    }
42
43    pub fn validate(&self, data: &Everything) {
44        for item in self.defines.values() {
45            item.validate(data);
46        }
47    }
48
49    #[cfg(feature = "jomini")]
50    pub fn get_bv(&self, key: &str) -> Option<&BV> {
51        self.defines.get(key).map(|d| &d.bv)
52    }
53}
54
55impl FileHandler<Block> for Defines {
56    fn subpath(&self) -> PathBuf {
57        PathBuf::from("common/defines")
58    }
59
60    fn load_file(&self, entry: &FileEntry, parser: &ParserMemory) -> Option<Block> {
61        if !entry.filename().to_string_lossy().ends_with(".txt") {
62            return None;
63        }
64
65        PdxFile::read(entry, parser)
66    }
67
68    fn handle_file(&mut self, _entry: &FileEntry, mut block: Block) {
69        // TODO HOI4: Hoi4 has a toplevel group
70        for (group, block) in block.drain_definitions_warn() {
71            for (name, bv) in block.iter_assignments_and_definitions_warn() {
72                self.load_item(group.clone(), name.clone(), bv);
73            }
74        }
75    }
76}
77
78#[derive(Clone, Debug)]
79pub struct Define {
80    #[allow(dead_code)] // TODO
81    group: Token,
82    name: Token,
83    bv: BV,
84}
85
86impl Define {
87    pub fn new(group: Token, name: Token, bv: BV) -> Self {
88        Self { group, name, bv }
89    }
90
91    #[allow(clippy::unused_self)]
92    #[allow(unused_variables)] // because only ck3 uses `data`
93    pub fn validate(&self, data: &Everything) {
94        let defines_map = match Game::game() {
95            #[cfg(feature = "ck3")]
96            Game::Ck3 => &crate::ck3::tables::defines::DEFINES_MAP,
97            #[cfg(feature = "vic3")]
98            Game::Vic3 => &crate::vic3::tables::defines::DEFINES_MAP,
99            #[cfg(feature = "imperator")]
100            Game::Imperator => &crate::imperator::tables::defines::DEFINES_MAP,
101            #[cfg(feature = "eu5")]
102            Game::Eu5 => &crate::eu5::tables::defines::DEFINES_MAP,
103            #[cfg(feature = "hoi4")]
104            Game::Hoi4 => &crate::hoi4::tables::defines::DEFINES_MAP,
105        };
106
107        // TODO: save key instead of reconstructing it here?
108        let key = format!("{}|{}", &self.group, &self.name);
109        if let Some(dt) = defines_map.get(&*key) {
110            dt.validate(&self.bv, data);
111        } else {
112            let msg = format!("unknown define {key}");
113            err(ErrorKey::UnknownField).msg(msg).loc(&self.name).push();
114        }
115
116        if std::env::var("TIGER_DUMP_DEFINES").is_ok() {
117            let define_type = match &self.bv {
118                BV::Value(token) => {
119                    if token.is_number() {
120                        "DefineType::Number"
121                    } else if token.is("yes") || token.is("no") {
122                        "DefineType::Boolean"
123                    } else {
124                        "DefineType::String"
125                    }
126                }
127                BV::Block(block) => {
128                    if block.num_items() == 0 {
129                        "DefineType::UnknownList"
130                    } else {
131                        let first = block.iter_items().next().unwrap();
132                        if first.get_value().is_some_and(Token::is_number) {
133                            if block.num_items() == 4 {
134                                "DefineType::Color"
135                            } else {
136                                "DefineType::NumberList"
137                            }
138                        } else if first.get_value().is_some_and(|t| !t.is_number()) {
139                            "DefineType::StringList"
140                        } else {
141                            "DefineType::UnknownList"
142                        }
143                    }
144                }
145            };
146            if let Some(define_type) = defines_map.get(&*key) {
147                eprintln!("    (\"{}|{}\", DefineType::{define_type:?}),", &self.group, &self.name);
148            } else {
149                eprintln!("    (\"{}|{}\", {define_type}),", &self.group, &self.name);
150            }
151        }
152
153        #[cfg(feature = "ck3")]
154        if self.group.is("NGameIcons")
155            && self.name.is("PIETY_GROUPS")
156            && let Some(icon_path) =
157                data.get_defined_string_warn(&self.name, "NGameIcons|PIETY_LEVEL_PATH")
158            && let Some(groups) = self.bv.expect_block()
159        {
160            for icon_group in groups.iter_values_warn() {
161                for nr in &["00", "01", "02", "03", "04", "05"] {
162                    let pathname = format!("{icon_path}/icon_piety_{icon_group}_{nr}.dds");
163                    data.verify_exists_implied_max_sev(
164                        Item::File,
165                        &pathname,
166                        icon_group,
167                        Severity::Warning,
168                    );
169                }
170            }
171        }
172    }
173}