Skip to main content

tiger_lib/
everything.rs

1//! Stores everything known about the game and mod being validated.
2//!
3//! References to [`Everything`] are passed down through nearly all of the validation logic, so
4//! that individual functions can access all the defined game items.
5
6use std::borrow::Cow;
7use std::fmt::Debug;
8use std::path::{Path, PathBuf};
9#[cfg(any(feature = "ck3", feature = "vic3"))]
10use std::sync::RwLock;
11
12use anyhow::Result;
13use rayon::{Scope, scope};
14use strum::IntoEnumIterator;
15use thiserror::Error;
16
17#[cfg(any(feature = "ck3", feature = "vic3"))]
18use crate::block::BV;
19use crate::block::Block;
20#[cfg(feature = "ck3")]
21use crate::ck3::data::{
22    characters::Characters,
23    climate::Climate,
24    gameconcepts::GameConcepts,
25    interaction_cats::CharacterInteractionCategories,
26    maa::MenAtArmsTypes,
27    prov_history::ProvinceHistories,
28    prov_terrain::{ProvinceProperties, ProvinceTerrains},
29    provinces::Ck3Provinces,
30    title_history::TitleHistories,
31    titles::Titles,
32    traits::Traits,
33    wars::Wars,
34};
35#[cfg(feature = "ck3")]
36use crate::ck3::tables::misc::*;
37use crate::config_load::{check_for_legacy_ignore, load_filter};
38use crate::context::ScopeContext;
39#[cfg(any(feature = "ck3", feature = "vic3"))]
40use crate::data::data_binding::DataBindings;
41use crate::data::{
42    assets::Assets,
43    defines::Defines,
44    gui::Gui,
45    localization::Localization,
46    on_actions::OnActions,
47    scripted_effects::{Effect, Effects},
48    scripted_triggers::{Trigger, Triggers},
49};
50#[cfg(feature = "jomini")]
51use crate::data::{
52    coa::Coas, events::Events, music::Musics, script_values::ScriptValues,
53    scripted_lists::ScriptedLists, scripted_modifiers::ScriptedModifiers,
54};
55use crate::db::{Db, DbKind};
56use crate::dds::DdsFiles;
57#[cfg(feature = "eu5")]
58use crate::eu5::data::provinces::Eu5Provinces;
59#[cfg(feature = "eu5")]
60use crate::eu5::tables::misc::*;
61use crate::fileset::{FileEntry, FileKind, FileStage, Fileset};
62use crate::game::Game;
63#[cfg(any(feature = "ck3", feature = "vic3"))]
64use crate::helpers::TigerHashSet;
65#[cfg(feature = "hoi4")]
66use crate::hoi4::data::{
67    events::Hoi4Events, gfx::Gfx, music::Hoi4Musics, provinces::Hoi4Provinces,
68};
69#[cfg(feature = "hoi4")]
70use crate::hoi4::tables::misc::*;
71#[cfg(feature = "imperator")]
72use crate::imperator::data::{decisions::Decisions, provinces::ImperatorProvinces};
73#[cfg(feature = "imperator")]
74use crate::imperator::tables::misc::*;
75use crate::item::{Item, ItemExt, ItemLoader};
76use crate::lowercase::Lowercase;
77use crate::macros::MACRO_MAP;
78use crate::parse::ParserMemory;
79#[cfg(feature = "vic3")]
80use crate::parse::json::parse_json_file;
81use crate::pdxfile::PdxFile;
82#[cfg(any(feature = "ck3", feature = "vic3"))]
83use crate::report::err;
84#[cfg(feature = "jomini")]
85use crate::report::warn;
86use crate::report::{ErrorKey, OutputStyle, Severity, report, set_output_style};
87use crate::rivers::Rivers;
88#[cfg(feature = "jomini")]
89use crate::scopes::Scopes;
90use crate::token::{Loc, Token};
91#[cfg(feature = "jomini")]
92use crate::variable_scopes::VariableScopes;
93use crate::variables::Variables;
94#[cfg(feature = "vic3")]
95use crate::vic3::data::{
96    buy_packages::BuyPackage, history::History, provinces::Vic3Provinces,
97    strategic_regions::StrategicRegion, terrain::TerrainMask,
98};
99#[cfg(feature = "vic3")]
100use crate::vic3::tables::misc::*;
101
102#[derive(Debug, Error)]
103#[allow(clippy::enum_variant_names)]
104pub enum FilesError {
105    #[error("Could not read game files at {path}")]
106    VanillaUnreadable { path: PathBuf, source: walkdir::Error },
107    #[error("Could not read mod files at {path}")]
108    ModUnreadable { path: PathBuf, source: walkdir::Error },
109    #[error("Could not read config file at {path}")]
110    ConfigUnreadable { path: PathBuf },
111}
112
113/// A record of everything known about the game and mod being validated.
114///
115/// References to [`Everything`] are passed down through nearly all of the validation logic, so
116/// that individual functions can access all the defined game items.
117///
118/// The validator has two main phases: parsing and validation.
119/// * During parsing, the script files are read, parsed, and loaded into the various databases.
120///   `Everything` is mutable during this period.
121/// * During validation, `Everything` is immutable and cross-checking between item types can be done safely.
122#[derive(Debug)]
123pub struct Everything {
124    /// Config from file
125    config: Block,
126
127    /// The global parser state, carrying information between files.
128    /// Currently only used by the pdxfile parser, to handle the `reader_export` directory,
129    /// which is specially processed before all other files.
130    pub parser: ParserMemory,
131
132    /// A cache of define values (from common/defines) that are missing and that have already been
133    /// warned about as missing. This is to avoid duplicate warnings.
134    #[cfg(any(feature = "ck3", feature = "vic3"))]
135    warned_defines: RwLock<TigerHashSet<String>>,
136
137    /// Tracks all the files (vanilla and mods) that are relevant to the current validation.
138    pub(crate) fileset: Fileset,
139
140    /// Tracks specifically the .dds files, and their formats and sizes.
141    pub(crate) dds: DdsFiles,
142
143    /// A general database of item types. Most items go here. The ones that need special handling
144    /// go in the separate databases listed below.
145    pub(crate) database: Db,
146
147    pub(crate) localization: Localization,
148
149    #[cfg(feature = "jomini")]
150    pub(crate) scripted_lists: ScriptedLists,
151
152    pub(crate) defines: Defines,
153
154    #[cfg(feature = "jomini")]
155    pub(crate) events: Events,
156    #[cfg(feature = "hoi4")]
157    pub(crate) events_hoi4: Hoi4Events,
158    #[cfg(feature = "imperator")]
159    pub(crate) decisions_imperator: Decisions,
160
161    #[cfg(feature = "jomini")]
162    pub(crate) scripted_modifiers: ScriptedModifiers,
163    pub(crate) on_actions: OnActions,
164
165    #[cfg(feature = "ck3")]
166    pub(crate) interaction_cats: CharacterInteractionCategories,
167
168    #[cfg(feature = "ck3")]
169    pub(crate) provinces_ck3: Ck3Provinces,
170    #[cfg(feature = "vic3")]
171    pub(crate) provinces_vic3: Vic3Provinces,
172    #[cfg(feature = "imperator")]
173    pub(crate) provinces_imperator: ImperatorProvinces,
174    #[cfg(feature = "eu5")]
175    pub(crate) provinces_eu5: Eu5Provinces,
176    #[cfg(feature = "hoi4")]
177    pub(crate) provinces_hoi4: Hoi4Provinces,
178
179    #[cfg(feature = "ck3")]
180    pub(crate) province_histories: ProvinceHistories,
181    #[cfg(feature = "ck3")]
182    pub(crate) province_properties: ProvinceProperties,
183    #[cfg(feature = "ck3")]
184    pub(crate) province_terrains: ProvinceTerrains,
185
186    #[cfg(feature = "ck3")]
187    pub(crate) gameconcepts: GameConcepts,
188
189    #[cfg(feature = "ck3")]
190    pub(crate) titles: Titles,
191
192    #[cfg(feature = "ck3")]
193    pub(crate) characters: Characters,
194
195    #[cfg(feature = "jomini")]
196    pub(crate) script_values: ScriptValues,
197
198    pub(crate) triggers: Triggers,
199    pub(crate) effects: Effects,
200
201    #[cfg(feature = "ck3")]
202    pub(crate) traits: Traits,
203
204    #[cfg(feature = "ck3")]
205    pub(crate) title_history: TitleHistories,
206
207    #[cfg(feature = "ck3")]
208    pub(crate) menatarmstypes: MenAtArmsTypes,
209
210    pub(crate) gui: Gui,
211    #[cfg(any(feature = "ck3", feature = "vic3"))]
212    pub(crate) data_bindings: DataBindings,
213
214    #[cfg(feature = "hoi4")]
215    pub(crate) gfx: Gfx,
216    pub(crate) assets: Assets,
217    #[cfg(feature = "hoi4")]
218    pub(crate) music_hoi4: Hoi4Musics,
219    #[cfg(feature = "jomini")]
220    pub(crate) music: Musics,
221
222    #[cfg(feature = "jomini")]
223    pub(crate) coas: Coas,
224
225    #[cfg(feature = "vic3")]
226    pub(crate) history: History,
227
228    #[cfg(feature = "ck3")]
229    pub(crate) wars: Wars,
230
231    pub(crate) variables: Variables,
232
233    #[cfg(feature = "jomini")]
234    pub(crate) global_scopes: VariableScopes,
235    #[cfg(feature = "jomini")]
236    pub(crate) global_list_scopes: VariableScopes,
237    #[cfg(feature = "jomini")]
238    pub(crate) variable_scopes: VariableScopes,
239    #[cfg(feature = "jomini")]
240    pub(crate) variable_list_scopes: VariableScopes,
241}
242
243macro_rules! load_all_generic {
244    ($s: ident, $t: ident) => {
245        $s.spawn(|_| $t.fileset.handle(&mut $t.dds, &$t.parser));
246        $s.spawn(|_| $t.fileset.handle(&mut $t.defines, &$t.parser));
247        $s.spawn(|_| $t.fileset.handle(&mut $t.triggers, &$t.parser));
248        $s.spawn(|_| $t.fileset.handle(&mut $t.effects, &$t.parser));
249        $s.spawn(|_| $t.fileset.handle(&mut $t.assets, &$t.parser));
250        $s.spawn(|_| $t.fileset.handle(&mut $t.gui, &$t.parser));
251        $s.spawn(|_| $t.fileset.handle(&mut $t.on_actions, &$t.parser));
252    };
253}
254
255#[cfg(feature = "ck3")]
256macro_rules! load_all_ck3 {
257    ($s: ident, $t: ident) => {
258        $s.spawn(|_| $t.fileset.handle(&mut $t.events, &$t.parser));
259        $s.spawn(|_| $t.fileset.handle(&mut $t.interaction_cats, &$t.parser));
260        $s.spawn(|_| $t.fileset.handle(&mut $t.province_histories, &$t.parser));
261        $s.spawn(|_| $t.fileset.handle(&mut $t.province_properties, &$t.parser));
262        $s.spawn(|_| $t.fileset.handle(&mut $t.province_terrains, &$t.parser));
263        $s.spawn(|_| $t.fileset.handle(&mut $t.gameconcepts, &$t.parser));
264        $s.spawn(|_| $t.fileset.handle(&mut $t.titles, &$t.parser));
265        $s.spawn(|_| $t.fileset.handle(&mut $t.characters, &$t.parser));
266        $s.spawn(|_| $t.fileset.handle(&mut $t.traits, &$t.parser));
267        $s.spawn(|_| $t.fileset.handle(&mut $t.title_history, &$t.parser));
268        $s.spawn(|_| $t.fileset.handle(&mut $t.menatarmstypes, &$t.parser));
269        $s.spawn(|_| $t.fileset.handle(&mut $t.music, &$t.parser));
270        $s.spawn(|_| $t.fileset.handle(&mut $t.data_bindings, &$t.parser));
271        $s.spawn(|_| $t.fileset.handle(&mut $t.provinces_ck3, &$t.parser));
272        $s.spawn(|_| $t.fileset.handle(&mut $t.scripted_lists, &$t.parser));
273        $s.spawn(|_| $t.fileset.handle(&mut $t.wars, &$t.parser));
274        $s.spawn(|_| $t.fileset.handle(&mut $t.coas, &$t.parser));
275        $s.spawn(|_| $t.fileset.handle(&mut $t.scripted_modifiers, &$t.parser));
276        $s.spawn(|_| $t.fileset.handle(&mut $t.script_values, &$t.parser));
277        $s.spawn(|_| crate::ck3::data::buildings::Building::finalize(&mut $t.database));
278    };
279}
280
281#[cfg(feature = "vic3")]
282macro_rules! load_all_vic3 {
283    ($s: ident, $t: ident) => {
284        $s.spawn(|_| $t.fileset.handle(&mut $t.events, &$t.parser));
285        $s.spawn(|_| $t.fileset.handle(&mut $t.history, &$t.parser));
286        $s.spawn(|_| $t.fileset.handle(&mut $t.provinces_vic3, &$t.parser));
287        $s.spawn(|_| $t.fileset.handle(&mut $t.data_bindings, &$t.parser));
288        $s.spawn(|_| $t.fileset.handle(&mut $t.coas, &$t.parser));
289        $s.spawn(|_| $t.fileset.handle(&mut $t.scripted_lists, &$t.parser));
290        $s.spawn(|_| $t.fileset.handle(&mut $t.scripted_modifiers, &$t.parser));
291        $s.spawn(|_| $t.fileset.handle(&mut $t.script_values, &$t.parser));
292        $s.spawn(|_| $t.fileset.handle(&mut $t.music, &$t.parser));
293        $s.spawn(|_| {
294            Everything::load_json(
295                &$t.fileset,
296                &mut $t.database,
297                Item::TerrainMask,
298                TerrainMask::add_json,
299            );
300        });
301    };
302}
303
304#[cfg(feature = "imperator")]
305macro_rules! load_all_imperator {
306    ($s: ident, $t: ident) => {
307        $s.spawn(|_| $t.fileset.handle(&mut $t.events, &$t.parser));
308        $s.spawn(|_| $t.fileset.handle(&mut $t.decisions_imperator, &$t.parser));
309        $s.spawn(|_| $t.fileset.handle(&mut $t.provinces_imperator, &$t.parser));
310        $s.spawn(|_| $t.fileset.handle(&mut $t.coas, &$t.parser));
311        $s.spawn(|_| $t.fileset.handle(&mut $t.scripted_lists, &$t.parser));
312        $s.spawn(|_| $t.fileset.handle(&mut $t.scripted_modifiers, &$t.parser));
313        $s.spawn(|_| $t.fileset.handle(&mut $t.script_values, &$t.parser));
314        $s.spawn(|_| $t.fileset.handle(&mut $t.music, &$t.parser));
315    };
316}
317
318#[cfg(feature = "eu5")]
319macro_rules! load_all_eu5 {
320    ($s: ident, $t: ident) => {
321        $s.spawn(|_| $t.fileset.handle(&mut $t.events, &$t.parser));
322        $s.spawn(|_| $t.fileset.handle(&mut $t.coas, &$t.parser));
323        $s.spawn(|_| $t.fileset.handle(&mut $t.provinces_eu5, &$t.parser));
324        $s.spawn(|_| $t.fileset.handle(&mut $t.scripted_lists, &$t.parser));
325        $s.spawn(|_| $t.fileset.handle(&mut $t.scripted_modifiers, &$t.parser));
326        $s.spawn(|_| $t.fileset.handle(&mut $t.script_values, &$t.parser));
327        $s.spawn(|_| $t.fileset.handle(&mut $t.music, &$t.parser));
328    };
329}
330
331#[cfg(feature = "hoi4")]
332macro_rules! load_all_hoi4 {
333    ($s: ident, $t: ident) => {
334        $s.spawn(|_| $t.fileset.handle(&mut $t.events_hoi4, &$t.parser));
335        $s.spawn(|_| $t.fileset.handle(&mut $t.gfx, &$t.parser));
336        $s.spawn(|_| $t.fileset.handle(&mut $t.provinces_hoi4, &$t.parser));
337        $s.spawn(|_| $t.fileset.handle(&mut $t.music_hoi4, &$t.parser));
338    };
339}
340
341macro_rules! scan_all_generic {
342    ($s: ident) => {
343        $s.triggers.scan_variables(&mut $s.variables);
344        $s.effects.scan_variables(&mut $s.variables);
345        $s.on_actions.scan_variables(&mut $s.variables);
346    };
347}
348
349#[cfg(feature = "ck3")]
350macro_rules! scan_all_ck3 {
351    ($s: ident) => {
352        $s.events.scan_variables(&mut $s.variables);
353        $s.interaction_cats.scan_variables(&mut $s.variables);
354        $s.province_histories.scan_variables(&mut $s.variables);
355        $s.titles.scan_variables(&mut $s.variables);
356        $s.characters.scan_variables(&mut $s.variables);
357        $s.traits.scan_variables(&mut $s.variables);
358        $s.title_history.scan_variables(&mut $s.variables);
359        $s.menatarmstypes.scan_variables(&mut $s.variables);
360        $s.music.scan_variables(&mut $s.variables);
361        $s.scripted_lists.scan_variables(&mut $s.variables);
362        $s.coas.scan_variables(&mut $s.variables);
363        $s.scripted_modifiers.scan_variables(&mut $s.variables);
364        $s.script_values.scan_variables(&mut $s.variables);
365    };
366}
367
368#[cfg(feature = "vic3")]
369macro_rules! scan_all_vic3 {
370    ($s: ident) => {
371        $s.events.scan_variables(&mut $s.variables);
372        $s.history.scan_variables(&mut $s.variables);
373        $s.coas.scan_variables(&mut $s.variables);
374        $s.scripted_lists.scan_variables(&mut $s.variables);
375        $s.scripted_modifiers.scan_variables(&mut $s.variables);
376        $s.script_values.scan_variables(&mut $s.variables);
377        $s.music.scan_variables(&mut $s.variables);
378    };
379}
380
381#[cfg(feature = "imperator")]
382macro_rules! scan_all_imperator {
383    ($s: ident) => {
384        $s.events.scan_variables(&mut $s.variables);
385        $s.decisions_imperator.scan_variables(&mut $s.variables);
386        $s.coas.scan_variables(&mut $s.variables);
387        $s.scripted_lists.scan_variables(&mut $s.variables);
388        $s.scripted_modifiers.scan_variables(&mut $s.variables);
389        $s.script_values.scan_variables(&mut $s.variables);
390        $s.music.scan_variables(&mut $s.variables);
391    };
392}
393
394#[cfg(feature = "eu5")]
395macro_rules! scan_all_eu5 {
396    ($s: ident) => {
397        $s.events.scan_variables(&mut $s.variables);
398        $s.coas.scan_variables(&mut $s.variables);
399        $s.scripted_lists.scan_variables(&mut $s.variables);
400        $s.scripted_modifiers.scan_variables(&mut $s.variables);
401        $s.script_values.scan_variables(&mut $s.variables);
402        $s.music.scan_variables(&mut $s.variables);
403    };
404}
405#[cfg(feature = "hoi4")]
406macro_rules! scan_all_hoi4 {
407    ($s: ident) => {
408        $s.events_hoi4.scan_variables(&mut $s.variables);
409        $s.music_hoi4.scan_variables(&mut $s.variables);
410    };
411}
412
413impl Everything {
414    /// Create a new `Everything` instance, ready for validating a mod.
415    ///
416    /// `vanilla_dir` is the path to the base game files. If it's `None`, then no vanilla files
417    /// will be loaded. This will seriously affect validation, but it's ok if you just want to load
418    /// and examine the mod files.
419    ///
420    /// `mod_root` is the path to the mod files. The config file will also be looked for there.
421    ///
422    /// `replace_paths` is from the similarly named field in the `.mod` file.
423    pub fn new(
424        config_filepath: Option<&Path>,
425        vanilla_dir: Option<&Path>,
426        workshop_dir: Option<&Path>,
427        paradox_dir: Option<&Path>,
428        mod_root: &Path,
429        replace_paths: Vec<PathBuf>,
430    ) -> Result<Self> {
431        let mut fileset = Fileset::new(vanilla_dir, mod_root.to_path_buf(), replace_paths);
432
433        let config_file_name = match Game::game() {
434            #[cfg(feature = "ck3")]
435            Game::Ck3 => "ck3-tiger.conf",
436            #[cfg(feature = "vic3")]
437            Game::Vic3 => "vic3-tiger.conf",
438            #[cfg(feature = "imperator")]
439            Game::Imperator => "imperator-tiger.conf",
440            #[cfg(feature = "eu5")]
441            Game::Eu5 => "eu5-tiger.conf",
442            #[cfg(feature = "hoi4")]
443            Game::Hoi4 => "hoi4-tiger.conf",
444        };
445
446        let config_file = match config_filepath {
447            Some(path) => path.to_path_buf(),
448            None => mod_root.join(config_file_name),
449        };
450
451        let config = if config_file.is_file() {
452            Self::read_config(config_file_name, &config_file)
453                .ok_or(FilesError::ConfigUnreadable { path: config_file })?
454        } else {
455            Block::new(Loc::for_file(
456                config_file.clone(),
457                FileStage::NoStage,
458                FileKind::Mod,
459                config_file.clone(),
460            ))
461        };
462
463        fileset.config(config.clone(), workshop_dir, paradox_dir)?;
464
465        fileset.scan_all()?;
466        fileset.finalize();
467
468        #[cfg(feature = "jomini")]
469        let global_scopes = VariableScopes::new("global_var:");
470        #[cfg(feature = "jomini")]
471        let global_list_scopes = VariableScopes::new("global list ");
472        #[cfg(feature = "jomini")]
473        let variable_scopes = VariableScopes::new("var:");
474        #[cfg(feature = "jomini")]
475        let variable_list_scopes = VariableScopes::new("variable list ");
476
477        #[cfg(feature = "ck3")]
478        if Game::is_ck3() {
479            variable_list_scopes
480                .config_override("lover_object_of_importance", Scopes::Character | Scopes::Flag);
481            variable_list_scopes
482                .config_override("lover_object_of_importance_2", Scopes::Character | Scopes::Flag);
483            variable_scopes.config_override("random_location", Scopes::Province | Scopes::Value);
484            variable_scopes
485                .config_override("task_contract_object", Scopes::Character | Scopes::Artifact);
486        }
487
488        #[cfg(feature = "jomini")]
489        if Game::is_jomini()
490            && let Some(block) = config.get_field_block("scope_override")
491        {
492            for (key, token) in block.iter_assignments() {
493                let mut scopes = Scopes::empty();
494                if token.lowercase_is("all") {
495                    scopes = Scopes::all();
496                } else {
497                    for part in token.split('|') {
498                        if let Some(scope) = Scopes::from_snake_case(part.as_str()) {
499                            scopes |= scope;
500                        } else {
501                            let msg = format!("unknown scope type `{part}`");
502                            warn(ErrorKey::Config).msg(msg).loc(part).push();
503                        }
504                    }
505                }
506                if let Some(name) = key.strip_prefix("var:") {
507                    variable_scopes.config_override(name.as_str(), scopes);
508                } else if let Some(name) = key.strip_prefix("var_list:") {
509                    variable_list_scopes.config_override(name.as_str(), scopes);
510                } else if let Some(name) = key.strip_prefix("global_var:") {
511                    global_scopes.config_override(name.as_str(), scopes);
512                } else if let Some(name) = key.strip_prefix("global_list:") {
513                    global_list_scopes.config_override(name.as_str(), scopes);
514                }
515            }
516        }
517
518        Ok(Everything {
519            parser: ParserMemory::default(),
520            fileset,
521            dds: DdsFiles::default(),
522            config,
523            #[cfg(any(feature = "ck3", feature = "vic3"))]
524            warned_defines: RwLock::new(TigerHashSet::default()),
525            database: Db::default(),
526            localization: Localization::default(),
527            #[cfg(feature = "jomini")]
528            scripted_lists: ScriptedLists::default(),
529            defines: Defines::default(),
530            #[cfg(feature = "jomini")]
531            events: Events::default(),
532            #[cfg(feature = "hoi4")]
533            events_hoi4: Hoi4Events::default(),
534            #[cfg(feature = "imperator")]
535            decisions_imperator: Decisions::default(),
536            #[cfg(feature = "jomini")]
537            scripted_modifiers: ScriptedModifiers::default(),
538            on_actions: OnActions::default(),
539            #[cfg(feature = "ck3")]
540            interaction_cats: CharacterInteractionCategories::default(),
541            #[cfg(feature = "ck3")]
542            provinces_ck3: Ck3Provinces::default(),
543            #[cfg(feature = "vic3")]
544            provinces_vic3: Vic3Provinces::default(),
545            #[cfg(feature = "imperator")]
546            provinces_imperator: ImperatorProvinces::default(),
547            #[cfg(feature = "eu5")]
548            provinces_eu5: Eu5Provinces::default(),
549            #[cfg(feature = "hoi4")]
550            provinces_hoi4: Hoi4Provinces::default(),
551            #[cfg(feature = "ck3")]
552            province_histories: ProvinceHistories::default(),
553            #[cfg(feature = "ck3")]
554            province_properties: ProvinceProperties::default(),
555            #[cfg(feature = "ck3")]
556            province_terrains: ProvinceTerrains::default(),
557            #[cfg(feature = "ck3")]
558            gameconcepts: GameConcepts::default(),
559            #[cfg(feature = "ck3")]
560            titles: Titles::default(),
561            #[cfg(feature = "ck3")]
562            characters: Characters::default(),
563            #[cfg(feature = "jomini")]
564            script_values: ScriptValues::default(),
565            triggers: Triggers::default(),
566            effects: Effects::default(),
567            #[cfg(feature = "ck3")]
568            traits: Traits::default(),
569            #[cfg(feature = "ck3")]
570            title_history: TitleHistories::default(),
571            #[cfg(feature = "ck3")]
572            menatarmstypes: MenAtArmsTypes::default(),
573            gui: Gui::default(),
574            #[cfg(any(feature = "ck3", feature = "vic3"))]
575            data_bindings: DataBindings::default(),
576            #[cfg(feature = "hoi4")]
577            gfx: Gfx::default(),
578            assets: Assets::default(),
579            #[cfg(feature = "hoi4")]
580            music_hoi4: Hoi4Musics::default(),
581            #[cfg(feature = "jomini")]
582            music: Musics::default(),
583            #[cfg(feature = "jomini")]
584            coas: Coas::default(),
585            #[cfg(feature = "vic3")]
586            history: History::default(),
587            #[cfg(feature = "ck3")]
588            wars: Wars::default(),
589            variables: Variables::new(),
590            #[cfg(feature = "jomini")]
591            global_scopes,
592            #[cfg(feature = "jomini")]
593            global_list_scopes,
594            #[cfg(feature = "jomini")]
595            variable_scopes,
596            #[cfg(feature = "jomini")]
597            variable_list_scopes,
598        })
599    }
600
601    fn read_config(name: &str, path: &Path) -> Option<Block> {
602        let entry =
603            FileEntry::new(PathBuf::from(name), FileStage::NoStage, FileKind::Mod, path.to_owned());
604        PdxFile::read_optional_bom(&entry, &ParserMemory::default())
605    }
606
607    pub fn load_config_filtering_rules(&self) {
608        check_for_legacy_ignore(&self.config);
609        load_filter(&self.config);
610    }
611
612    /// Load the `OutputStyle` settings from the config.
613    /// Note that the settings from the config can still be overridden
614    /// by supplying the --no-color flag.
615    fn load_output_styles(&self, default_color: bool) -> OutputStyle {
616        // Treat a missing output_style block and an empty output_style block exactly the same.
617        let block = match self.config.get_field_block("output_style") {
618            Some(block) => Cow::Borrowed(block),
619            None => Cow::Owned(Block::new(self.config.loc)),
620        };
621        if !block.get_field_bool("enable").unwrap_or(default_color) {
622            return OutputStyle::no_color();
623        }
624        let mut style = OutputStyle::default();
625        for severity in Severity::iter() {
626            if let Some(error_block) =
627                block.get_field_block(format!("{severity}").to_ascii_lowercase().as_str())
628                && let Some(color) = error_block.get_field_value("color")
629            {
630                style.set(severity, color.as_str());
631            }
632        }
633        style
634    }
635
636    pub fn load_output_settings(&self, default_colors: bool) {
637        set_output_style(self.load_output_styles(default_colors));
638    }
639
640    #[cfg(feature = "vic3")]
641    fn load_json<F>(fileset: &Fileset, db: &mut Db, itype: Item, add_json: F)
642    where
643        F: Fn(&mut Db, Block) + Sync + Send,
644    {
645        for block in fileset.filter_map_under(&PathBuf::from(itype.path()), |entry| {
646            if entry.filename().to_string_lossy().ends_with(".json") {
647                parse_json_file(entry)
648            } else {
649                None
650            }
651        }) {
652            add_json(db, block);
653        }
654    }
655
656    #[cfg(feature = "ck3")]
657    fn load_reader_export(&mut self) {
658        let path = PathBuf::from("reader_export");
659        for entry in self.fileset.get_files_under(&path) {
660            if entry.filename().to_string_lossy().ends_with(".txt") {
661                PdxFile::reader_export(entry, &mut self.parser.pdxfile);
662            }
663        }
664    }
665
666    fn load_pdx_files(&mut self, loader: &ItemLoader) {
667        let path = PathBuf::from(loader.itype().path());
668        let recursive = loader.recursive();
669        let expect_count = path.components().count() + 1;
670        for mut block in self.fileset.filter_map_under(&path, |entry| {
671            // It's <= expect_count because some loader paths are files not directories
672            if (recursive || entry.path().components().count() <= expect_count)
673                && entry.filename().to_string_lossy().ends_with(loader.extension())
674            {
675                PdxFile::read_encoded(entry, loader.encoding(), &self.parser)
676            } else {
677                None
678            }
679        }) {
680            if loader.whole_file() {
681                let fname = block.loc.filename();
682                // unwrap is safe here because of the ends_with check above.
683                let key = fname.strip_suffix(loader.extension()).unwrap();
684                let key = Token::new(key, block.loc);
685                (loader.adder())(&mut self.database, key, block);
686            } else {
687                for (key, block) in block.drain_definitions_warn() {
688                    (loader.adder())(&mut self.database, key, block);
689                }
690            }
691        }
692    }
693
694    fn load_all_normal_pdx_files(&mut self) {
695        for loader in inventory::iter::<ItemLoader> {
696            if loader.for_game(Game::game()) {
697                self.load_pdx_files(loader);
698            }
699        }
700    }
701
702    pub fn load_all(&mut self) {
703        #[cfg(feature = "ck3")]
704        self.load_reader_export();
705        self.load_all_normal_pdx_files();
706
707        std::thread::scope(|s| {
708            s.spawn(|| self.fileset.handle(&mut self.localization, &self.parser));
709
710            scope(|s| {
711                load_all_generic!(s, self);
712                match Game::game() {
713                    #[cfg(feature = "ck3")]
714                    Game::Ck3 => {
715                        load_all_ck3!(s, self);
716                    }
717                    #[cfg(feature = "vic3")]
718                    Game::Vic3 => {
719                        load_all_vic3!(s, self);
720                    }
721                    #[cfg(feature = "imperator")]
722                    Game::Imperator => {
723                        load_all_imperator!(s, self);
724                    }
725                    #[cfg(feature = "eu5")]
726                    Game::Eu5 => {
727                        load_all_eu5!(s, self);
728                    }
729                    #[cfg(feature = "hoi4")]
730                    Game::Hoi4 => {
731                        load_all_hoi4!(s, self);
732                    }
733                }
734            });
735
736            self.database.add_subitems();
737            scan_all_generic!(self);
738            match Game::game() {
739                #[cfg(feature = "ck3")]
740                Game::Ck3 => {
741                    scan_all_ck3!(self);
742                }
743                #[cfg(feature = "vic3")]
744                Game::Vic3 => {
745                    scan_all_vic3!(self);
746                }
747                #[cfg(feature = "imperator")]
748                Game::Imperator => {
749                    scan_all_imperator!(self);
750                }
751                #[cfg(feature = "eu5")]
752                Game::Eu5 => {
753                    scan_all_eu5!(self);
754                }
755                #[cfg(feature = "hoi4")]
756                Game::Hoi4 => {
757                    scan_all_hoi4!(self);
758                }
759            }
760            self.database.scan_variables(&mut self.variables);
761        });
762    }
763
764    fn validate_all_generic<'a>(&'a self, s: &Scope<'a>) {
765        s.spawn(|_| self.fileset.validate(self));
766        s.spawn(|_| self.defines.validate(self));
767        s.spawn(|_| self.triggers.validate(self));
768        s.spawn(|_| self.effects.validate(self));
769        s.spawn(|_| self.assets.validate(self));
770        s.spawn(|_| self.gui.validate(self));
771        s.spawn(|_| self.on_actions.validate(self));
772        s.spawn(|_| self.dds.validate());
773    }
774
775    #[cfg(feature = "ck3")]
776    fn validate_all_ck3<'a>(&'a self, s: &Scope<'a>) {
777        s.spawn(|_| self.events.validate(self));
778        s.spawn(|_| self.interaction_cats.validate(self));
779        s.spawn(|_| self.province_histories.validate(self));
780        s.spawn(|_| self.province_properties.validate(self));
781        s.spawn(|_| self.province_terrains.validate(self));
782        s.spawn(|_| self.gameconcepts.validate(self));
783        s.spawn(|_| self.titles.validate(self));
784        s.spawn(|_| self.characters.validate(self));
785        s.spawn(|_| self.traits.validate(self));
786        s.spawn(|_| self.title_history.validate(self));
787        s.spawn(|_| self.menatarmstypes.validate(self));
788        s.spawn(|_| self.data_bindings.validate(self));
789        s.spawn(|_| self.provinces_ck3.validate(self));
790        s.spawn(|_| self.wars.validate(self));
791        s.spawn(|_| self.coas.validate(self));
792        s.spawn(|_| self.scripted_lists.validate(self));
793        s.spawn(|_| self.scripted_modifiers.validate(self));
794        s.spawn(|_| self.script_values.validate(self));
795        s.spawn(|_| self.music.validate(self));
796        s.spawn(|_| Climate::validate_all(&self.database, self));
797    }
798
799    #[cfg(feature = "vic3")]
800    fn validate_all_vic3<'a>(&'a self, s: &Scope<'a>) {
801        if std::env::var("TIGER_CHECK_MODIFS").is_ok() {
802            for line in std::io::stdin().lines().map_while(Result::ok) {
803                if !line.starts_with(' ')
804                    && let Some(name) = line.strip_suffix(":")
805                {
806                    eprintln!("checking modif {name}");
807                    let loc: Loc = FileEntry::new(
808                        "stdin".into(),
809                        FileStage::NoStage,
810                        FileKind::Vanilla,
811                        "stdin".into(),
812                    )
813                    .into();
814                    let name_token = Token::new(name, loc);
815                    crate::vic3::tables::modifs::lookup_engine_modif(
816                        &name_token,
817                        &Lowercase::new(name),
818                        self,
819                        Some(Severity::Error),
820                    );
821                }
822            }
823        }
824        s.spawn(|_| self.events.validate(self));
825        s.spawn(|_| self.history.validate(self));
826        s.spawn(|_| self.provinces_vic3.validate(self));
827        s.spawn(|_| self.data_bindings.validate(self));
828        s.spawn(|_| self.coas.validate(self));
829        s.spawn(|_| self.scripted_lists.validate(self));
830        s.spawn(|_| self.scripted_modifiers.validate(self));
831        s.spawn(|_| self.script_values.validate(self));
832        s.spawn(|_| self.music.validate(self));
833        s.spawn(|_| StrategicRegion::crosscheck(self));
834        s.spawn(|_| BuyPackage::crosscheck(self));
835    }
836
837    #[cfg(feature = "imperator")]
838    fn validate_all_imperator<'a>(&'a self, s: &Scope<'a>) {
839        s.spawn(|_| self.events.validate(self));
840        s.spawn(|_| self.decisions_imperator.validate(self));
841        s.spawn(|_| self.provinces_imperator.validate(self));
842        s.spawn(|_| self.coas.validate(self));
843        s.spawn(|_| self.scripted_lists.validate(self));
844        s.spawn(|_| self.scripted_modifiers.validate(self));
845        s.spawn(|_| self.script_values.validate(self));
846        s.spawn(|_| self.music.validate(self));
847    }
848
849    #[cfg(feature = "eu5")]
850    fn validate_all_eu5<'a>(&'a self, s: &Scope<'a>) {
851        s.spawn(|_| self.events.validate(self));
852        s.spawn(|_| self.coas.validate(self));
853        s.spawn(|_| self.provinces_eu5.validate(self));
854        s.spawn(|_| self.scripted_lists.validate(self));
855        s.spawn(|_| self.scripted_modifiers.validate(self));
856        s.spawn(|_| self.script_values.validate(self));
857        s.spawn(|_| self.music.validate(self));
858    }
859
860    #[cfg(feature = "hoi4")]
861    fn validate_all_hoi4<'a>(&'a self, s: &Scope<'a>) {
862        s.spawn(|_| self.events_hoi4.validate(self));
863        s.spawn(|_| self.provinces_hoi4.validate(self));
864        s.spawn(|_| self.gfx.validate(self));
865        s.spawn(|_| self.music_hoi4.validate(self));
866    }
867
868    pub fn validate_all(&self) {
869        scope(|s| {
870            self.validate_all_generic(s);
871            match Game::game() {
872                #[cfg(feature = "ck3")]
873                Game::Ck3 => self.validate_all_ck3(s),
874                #[cfg(feature = "vic3")]
875                Game::Vic3 => self.validate_all_vic3(s),
876                #[cfg(feature = "imperator")]
877                Game::Imperator => self.validate_all_imperator(s),
878                #[cfg(feature = "eu5")]
879                Game::Eu5 => self.validate_all_eu5(s),
880                #[cfg(feature = "hoi4")]
881                Game::Hoi4 => self.validate_all_hoi4(s),
882            }
883            s.spawn(|_| self.database.validate(self));
884        });
885        self.localization.validate_pass2(self);
886    }
887
888    pub fn check_rivers(&mut self) {
889        let mut rivers = Rivers::default();
890        self.fileset.handle(&mut rivers, &self.parser);
891        rivers.validate(self);
892    }
893
894    #[cfg(feature = "ck3")]
895    pub fn check_pod(&mut self) {
896        self.province_histories.check_pod_faiths(self, &self.titles);
897        self.characters.check_pod_flags(self);
898        self.localization.check_pod_loca(self);
899    }
900
901    pub fn check_unused(&mut self) {
902        self.localization.check_unused(self);
903        self.fileset.check_unused_dds(self);
904    }
905
906    #[allow(dead_code)]
907    pub(crate) fn item_has_property(&self, itype: Item, key: &str, property: &str) -> bool {
908        self.database.has_property(itype, key, property, self)
909    }
910
911    #[cfg(feature = "ck3")] // vic3 happens not to use
912    pub(crate) fn item_lc_has_property(
913        &self,
914        itype: Item,
915        key: &Lowercase,
916        property: &str,
917    ) -> bool {
918        self.database.lc_has_property(itype, key, property, self)
919    }
920
921    #[cfg(feature = "ck3")]
922    fn item_exists_ck3(&self, itype: Item, key: &str) -> bool {
923        match itype {
924            Item::ActivityState => ACTIVITY_STATES.contains(&key),
925            Item::ArtifactHistory => ARTIFACT_HISTORY.contains(&key),
926            Item::ArtifactRarity => ARTIFACT_RARITIES.contains(&&*key.to_ascii_lowercase()),
927            Item::Character => self.characters.exists(key),
928            Item::CharacterInteractionCategory => self.interaction_cats.exists(key),
929            Item::Coa => self.coas.exists(key),
930            Item::CoaTemplate => self.coas.template_exists(key),
931            Item::Currency => CURRENCIES_CK3.contains(&key),
932            Item::DangerType => DANGER_TYPES.contains(&key),
933            Item::DlcFeature => DLC_FEATURES_CK3.contains(&key),
934            Item::Event => self.events.exists(key),
935            Item::EventNamespace => self.events.namespace_exists(key),
936            Item::GameConcept => self.gameconcepts.exists(key),
937            Item::GeneAttribute => self.assets.attribute_exists(key),
938            Item::GeneticConstraint => self.traits.constraint_exists(key),
939            Item::MenAtArms => self.menatarmstypes.exists(key),
940            Item::MenAtArmsBase => self.menatarmstypes.base_exists(key),
941            Item::Music => self.music.exists(key),
942            Item::PrisonType => PRISON_TYPES.contains(&key),
943            Item::Province => self.provinces_ck3.exists(key),
944            Item::RewardItem => REWARD_ITEMS.contains(&key),
945            Item::ScriptedList => self.scripted_lists.exists(key),
946            Item::ScriptedModifier => self.scripted_modifiers.exists(key),
947            Item::ScriptValue => self.script_values.exists(key),
948            Item::Sexuality => SEXUALITIES.contains(&key),
949            Item::Skill => SKILLS.contains(&key),
950            Item::Sound => self.valid_sound(key),
951            Item::Title => self.titles.exists(key),
952            Item::TitleHistory => self.title_history.exists(key),
953            Item::Trait => self.traits.exists(key),
954            Item::TraitFlag => self.traits.flag_exists(key),
955            Item::TraitTrack => self.traits.track_exists(key),
956            Item::TraitCategory => TRAIT_CATEGORIES.contains(&key),
957            _ => self.database.exists(itype, key),
958        }
959    }
960
961    #[cfg(feature = "vic3")]
962    fn item_exists_vic3(&self, itype: Item, key: &str) -> bool {
963        match itype {
964            Item::Approval => APPROVALS.contains(&key),
965            Item::Attitude => ATTITUDES.contains(&&*key.to_lowercase()),
966            Item::CharacterArchetype => CHARACTER_ARCHETYPES.contains(&key),
967            Item::Coa => self.coas.exists(key),
968            Item::CoaTemplate => self.coas.template_exists(key),
969            Item::CountryTier => COUNTRY_TIERS.contains(&key),
970            Item::DlcFeature => DLC_FEATURES_VIC3.contains(&key),
971            Item::Event => self.events.exists(key),
972            Item::EventCategory => EVENT_CATEGORIES.contains(&key),
973            Item::EventNamespace => self.events.namespace_exists(key),
974            Item::GeneAttribute => self.assets.attribute_exists(key),
975            Item::InfamyThreshold => INFAMY_THRESHOLDS.contains(&key),
976            Item::Level => LEVELS.contains(&key),
977            Item::Music => self.music.exists(key),
978            Item::RelationsThreshold => RELATIONS.contains(&key),
979            Item::ScriptedList => self.scripted_lists.exists(key),
980            Item::ScriptedModifier => self.scripted_modifiers.exists(key),
981            Item::ScriptValue => self.script_values.exists(key),
982            Item::SecretGoal => SECRET_GOALS.contains(&key),
983            Item::Sound => self.valid_sound(key),
984            Item::Strata => STRATA.contains(&key),
985            Item::TerrainKey => TERRAIN_KEYS.contains(&key),
986            Item::TransferOfPower => TRANSFER_OF_POWER.contains(&key),
987            _ => self.database.exists(itype, key),
988        }
989    }
990
991    #[cfg(feature = "imperator")]
992    fn item_exists_imperator(&self, itype: Item, key: &str) -> bool {
993        match itype {
994            Item::Coa => self.coas.exists(key),
995            Item::CoaTemplate => self.coas.template_exists(key),
996            Item::DlcName => DLC_NAME_IMPERATOR.contains(&key),
997            Item::Decision => self.decisions_imperator.exists(key),
998            Item::Event => self.events.exists(key),
999            Item::EventNamespace => self.events.namespace_exists(key),
1000            Item::GeneAttribute => self.assets.attribute_exists(key),
1001            Item::Music => self.music.exists(key),
1002            Item::Province => self.provinces_imperator.exists(key),
1003            Item::ScriptedList => self.scripted_lists.exists(key),
1004            Item::ScriptedModifier => self.scripted_modifiers.exists(key),
1005            Item::ScriptValue => self.script_values.exists(key),
1006            Item::Sound => self.valid_sound(key),
1007            _ => self.database.exists(itype, key),
1008        }
1009    }
1010
1011    #[cfg(feature = "eu5")]
1012    fn item_exists_eu5(&self, itype: Item, key: &str) -> bool {
1013        match itype {
1014            Item::Coa => self.coas.exists(key),
1015            Item::CoaTemplate => self.coas.template_exists(key),
1016            Item::DlcFeature => DLC_FEATURES_EU5.contains(&key),
1017            Item::Event => self.events.exists(key),
1018            Item::EventNamespace => self.events.namespace_exists(key),
1019            Item::GeneAttribute => self.assets.attribute_exists(key),
1020            Item::Music => self.music.exists(key),
1021            Item::ScriptedList => self.scripted_lists.exists(key),
1022            Item::ScriptedModifier => self.scripted_modifiers.exists(key),
1023            Item::ScriptValue => self.script_values.exists(key),
1024            Item::Sound => self.valid_sound(key),
1025            Item::Currency => CURRENCIES.contains(&key),
1026            Item::CharacterTraitCategory => CHARACTER_TRAIT_CATEGORY.contains(&key),
1027            _ => self.database.exists(itype, key),
1028        }
1029    }
1030
1031    #[cfg(feature = "hoi4")]
1032    fn item_exists_hoi4(&self, itype: Item, key: &str) -> bool {
1033        match itype {
1034            Item::AiStrategyType => AI_STRATEGY_TYPES.contains(&key),
1035            Item::Event => self.events_hoi4.exists(key),
1036            Item::EventNamespace => self.events_hoi4.namespace_exists(key),
1037            Item::Music => self.music_hoi4.exists(key),
1038            Item::MusicAsset => self.assets.music_exists(key),
1039            Item::Pdxmesh => self.gfx.mesh_exists(key),
1040            Item::Province => self.provinces_hoi4.exists(key),
1041            Item::Sprite => self.gfx.sprite_exists(key),
1042            _ => self.database.exists(itype, key),
1043        }
1044    }
1045
1046    pub(crate) fn item_exists(&self, itype: Item, key: &str) -> bool {
1047        match itype {
1048            Item::Asset => self.assets.asset_exists(key),
1049            Item::BlendShape => self.assets.blend_shape_exists(key),
1050            Item::Define => self.defines.exists(key),
1051            Item::Entity => self.assets.entity_exists(key),
1052            Item::Entry => self.fileset.entry_exists(key),
1053            Item::File => self.fileset.exists(key),
1054            Item::GuiLayer => self.gui.layer_exists(key),
1055            Item::GuiTemplate => self.gui.template_exists(key),
1056            Item::GuiType => self.gui.type_exists(&Lowercase::new(key)),
1057            Item::Localization => self.localization.exists(key),
1058            Item::OnAction => self.on_actions.exists(key),
1059            #[cfg(feature = "jomini")]
1060            Item::Pdxmesh => self.assets.mesh_exists(key),
1061            Item::ScriptedEffect => self.effects.exists(key),
1062            Item::ScriptedTrigger => self.triggers.exists(key),
1063            Item::TextFormat => self.gui.textformat_exists(key),
1064            Item::TextIcon => self.gui.texticon_exists(key),
1065            Item::TextureFile => self.assets.texture_exists(key),
1066            Item::WidgetName => self.gui.name_exists(key),
1067            Item::Directory | Item::Shortcut => true, // TODO
1068            _ => match Game::game() {
1069                #[cfg(feature = "ck3")]
1070                Game::Ck3 => self.item_exists_ck3(itype, key),
1071                #[cfg(feature = "vic3")]
1072                Game::Vic3 => self.item_exists_vic3(itype, key),
1073                #[cfg(feature = "imperator")]
1074                Game::Imperator => self.item_exists_imperator(itype, key),
1075                #[cfg(feature = "eu5")]
1076                Game::Eu5 => self.item_exists_eu5(itype, key),
1077                #[cfg(feature = "hoi4")]
1078                Game::Hoi4 => self.item_exists_hoi4(itype, key),
1079            },
1080        }
1081    }
1082
1083    /// Return true iff the item `key` is found with a case insensitive match.
1084    /// This function is **incomplete**. It only contains the item types for which case insensitive
1085    /// matches are needed; this is currently the ones used in `src/ck3/tables/modif.rs`.
1086    #[cfg(feature = "ck3")]
1087    fn item_exists_lc_ck3(&self, itype: Item, key: &Lowercase) -> bool {
1088        match itype {
1089            Item::MenAtArmsBase => self.menatarmstypes.base_exists_lc(key),
1090            Item::Trait => self.traits.exists_lc(key),
1091            Item::TraitTrack => self.traits.track_exists_lc(key),
1092            _ => self.database.exists_lc(itype, key),
1093        }
1094    }
1095
1096    /// Return true iff the item `key` is found with a case insensitive match.
1097    /// This function is **incomplete**. It only contains the item types for which case insensitive
1098    /// matches are needed; this is currently the ones used in `src/vic3/tables/modif.rs`.
1099    #[cfg(feature = "vic3")]
1100    fn item_exists_lc_vic3(&self, itype: Item, key: &Lowercase) -> bool {
1101        match itype {
1102            Item::TerrainKey => TERRAIN_KEYS.contains(&key.as_str()),
1103            _ => self.database.exists_lc(itype, key),
1104        }
1105    }
1106
1107    /// Return true iff the item `key` is found with a case insensitive match.
1108    /// This function is **incomplete**. It only contains the item types for which case insensitive
1109    /// matches are needed; this is currently the ones used in `src/imperator/tables/modif.rs`.
1110    #[cfg(feature = "imperator")]
1111    fn item_exists_lc_imperator(&self, itype: Item, key: &Lowercase) -> bool {
1112        #[allow(clippy::match_single_binding)]
1113        match itype {
1114            _ => self.database.exists_lc(itype, key),
1115        }
1116    }
1117
1118    /// Return true iff the item `key` is found with a case insensitive match.
1119    /// This function is **incomplete**. It only contains the item types for which case insensitive
1120    /// matches are needed.
1121    #[cfg(feature = "eu5")]
1122    fn item_exists_lc_eu5(&self, itype: Item, key: &Lowercase) -> bool {
1123        self.database.exists_lc(itype, key)
1124    }
1125
1126    /// Return true iff the item `key` is found with a case insensitive match.
1127    /// This function is **incomplete**. It only contains the item types for which case insensitive
1128    /// matches are needed.
1129    #[cfg(feature = "hoi4")]
1130    fn item_exists_lc_hoi4(&self, itype: Item, key: &Lowercase) -> bool {
1131        #[allow(clippy::match_single_binding)]
1132        match itype {
1133            Item::EventNamespace => self.events_hoi4.namespace_exists_lc(key),
1134            _ => self.database.exists_lc(itype, key),
1135        }
1136    }
1137
1138    /// Return true iff the item `key` is found with a case insensitive match.
1139    /// This function is **incomplete**. It only contains the item types for which case insensitive
1140    /// matches are needed; this is currently the ones used in modif lookups.
1141    pub(crate) fn item_exists_lc(&self, itype: Item, key: &Lowercase) -> bool {
1142        #[allow(clippy::match_single_binding)]
1143        match itype {
1144            _ => match Game::game() {
1145                #[cfg(feature = "ck3")]
1146                Game::Ck3 => self.item_exists_lc_ck3(itype, key),
1147                #[cfg(feature = "vic3")]
1148                Game::Vic3 => self.item_exists_lc_vic3(itype, key),
1149                #[cfg(feature = "imperator")]
1150                Game::Imperator => self.item_exists_lc_imperator(itype, key),
1151                #[cfg(feature = "eu5")]
1152                Game::Eu5 => self.item_exists_lc_eu5(itype, key),
1153                #[cfg(feature = "hoi4")]
1154                Game::Hoi4 => self.item_exists_lc_hoi4(itype, key),
1155            },
1156        }
1157    }
1158
1159    pub(crate) fn mark_used(&self, itype: Item, key: &str) {
1160        match itype {
1161            Item::File => self.fileset.mark_used(key),
1162            Item::Localization => {
1163                self.localization.mark_used_return_exists(key);
1164            }
1165            _ => (),
1166        }
1167    }
1168
1169    pub(crate) fn verify_exists(&self, itype: Item, token: &Token) {
1170        self.verify_exists_implied(itype, token.as_str(), token);
1171    }
1172
1173    pub(crate) fn verify_exists_max_sev(&self, itype: Item, token: &Token, max_sev: Severity) {
1174        self.verify_exists_implied_max_sev(itype, token.as_str(), token, max_sev);
1175    }
1176
1177    pub(crate) fn verify_exists_implied_max_sev(
1178        &self,
1179        itype: Item,
1180        key: &str,
1181        token: &Token,
1182        max_sev: Severity,
1183    ) {
1184        match itype {
1185            Item::Entry => self.fileset.verify_entry_exists(key, token, max_sev),
1186            Item::File => self.fileset.verify_exists_implied(key, token, max_sev),
1187            Item::Localization => self.localization.verify_exists_implied(key, token, max_sev),
1188            Item::Music => match Game::game() {
1189                #[cfg(feature = "ck3")]
1190                Game::Ck3 => self.music.verify_exists_implied(key, token, max_sev),
1191                #[cfg(feature = "vic3")]
1192                Game::Vic3 => self.music.verify_exists_implied(key, token, max_sev),
1193                #[cfg(feature = "imperator")]
1194                Game::Imperator => self.music.verify_exists_implied(key, token, max_sev),
1195                #[cfg(feature = "eu5")]
1196                Game::Eu5 => self.music.verify_exists_implied(key, token, max_sev),
1197                #[cfg(feature = "hoi4")]
1198                Game::Hoi4 => self.music_hoi4.verify_exists_implied(key, token, max_sev),
1199            },
1200            Item::Province => match Game::game() {
1201                #[cfg(feature = "ck3")]
1202                Game::Ck3 => self.provinces_ck3.verify_exists_implied(key, token, max_sev),
1203                #[cfg(feature = "vic3")]
1204                Game::Vic3 => self.provinces_vic3.verify_exists_implied(key, token, max_sev),
1205                #[cfg(feature = "imperator")]
1206                Game::Imperator => {
1207                    self.provinces_imperator.verify_exists_implied(key, token, max_sev);
1208                }
1209                #[cfg(feature = "eu5")]
1210                Game::Eu5 => {
1211                    self.provinces_eu5.verify_exists_implied(key, token, max_sev);
1212                }
1213                #[cfg(feature = "hoi4")]
1214                #[cfg(feature = "hoi4")]
1215                Game::Hoi4 => {
1216                    self.provinces_hoi4.verify_exists_implied(key, token, max_sev);
1217                }
1218            },
1219            Item::TextureFile => {
1220                if let Some(entry) = self.assets.get_texture(key) {
1221                    // TODO: avoid allocating a string here
1222                    self.fileset.mark_used(&entry.path().to_string_lossy());
1223                } else {
1224                    let msg = format!("no texture file {key} anywhere under {}", itype.path());
1225                    report(ErrorKey::MissingFile, itype.severity().at_most(max_sev))
1226                        .conf(itype.confidence())
1227                        .msg(msg)
1228                        .loc(token)
1229                        .push();
1230                }
1231            }
1232            _ => {
1233                if !self.item_exists(itype, key) {
1234                    let path = itype.path();
1235                    let msg = if path.is_empty() {
1236                        format!("unknown {itype} {key}")
1237                    } else {
1238                        format!("{itype} {key} not defined in {path}")
1239                    };
1240                    report(ErrorKey::MissingItem, itype.severity().at_most(max_sev))
1241                        .conf(itype.confidence())
1242                        .msg(msg)
1243                        .loc(token)
1244                        .push();
1245                }
1246            }
1247        }
1248    }
1249
1250    #[allow(dead_code)]
1251    pub(crate) fn verify_exists_implied_max_sev_lc(
1252        &self,
1253        itype: Item,
1254        key: &Lowercase,
1255        token: &Token,
1256        max_sev: Severity,
1257    ) {
1258        if !self.item_exists_lc(itype, key) {
1259            let path = itype.path();
1260            let msg = if path.is_empty() {
1261                format!("unknown {itype} {key}")
1262            } else {
1263                format!("{itype} {key} not defined in {path}")
1264            };
1265            report(ErrorKey::MissingItem, itype.severity().at_most(max_sev))
1266                .conf(itype.confidence())
1267                .msg(msg)
1268                .loc(token)
1269                .push();
1270        }
1271    }
1272
1273    pub(crate) fn verify_exists_implied(&self, itype: Item, key: &str, token: &Token) {
1274        self.verify_exists_implied_max_sev(itype, key, token, Severity::Error);
1275    }
1276
1277    #[cfg(feature = "ck3")]
1278    pub(crate) fn verify_icon(&self, define: &str, token: &Token, suffix: &str) {
1279        if let Some(icon_path) = self.get_defined_string_warn(token, define) {
1280            let pathname = format!("{icon_path}/{token}{suffix}");
1281            // It's `Severity::Warning` because a missing icon is only a UI issue.
1282            self.verify_exists_implied_max_sev(Item::File, &pathname, token, Severity::Warning);
1283        }
1284    }
1285
1286    #[cfg(feature = "ck3")]
1287    pub(crate) fn mark_used_icon(&self, define: &str, token: &Token, suffix: &str) {
1288        if let Some(icon_path) = self.get_defined_string_warn(token, define) {
1289            let pathname = format!("{icon_path}/{token}{suffix}");
1290            self.fileset.mark_used(&pathname);
1291        }
1292    }
1293
1294    #[allow(dead_code)]
1295    pub(crate) fn validate_use(&self, itype: Item, key: &Token, block: &Block) {
1296        self.database.validate_use(itype, key, block, self);
1297    }
1298
1299    #[allow(dead_code)]
1300    pub(crate) fn validate_call(
1301        &self,
1302        itype: Item,
1303        key: &Token,
1304        block: &Block,
1305        sc: &mut ScopeContext,
1306    ) {
1307        self.database.validate_call(itype, key, block, self, sc);
1308    }
1309
1310    /// Validate the use of a localization within a specific `ScopeContext`.
1311    /// This allows validation of the named scopes used within the localization's datafunctions.
1312    pub(crate) fn validate_localization_sc(&self, key: &str, sc: &mut ScopeContext) {
1313        self.localization.validate_use(key, self, sc);
1314    }
1315
1316    #[allow(dead_code)]
1317    pub(crate) fn get_item<T: DbKind>(
1318        &self,
1319        itype: Item,
1320        key: &str,
1321    ) -> Option<(&Token, &Block, &T)> {
1322        self.database.get_item(itype, key)
1323    }
1324
1325    pub(crate) fn get_key_block(&self, itype: Item, key: &str) -> Option<(&Token, &Block)> {
1326        self.database.get_key_block(itype, key)
1327    }
1328
1329    pub(crate) fn get_trigger(&self, key: &Token) -> Option<&Trigger> {
1330        #[cfg(any(feature = "ck3", feature = "eu5"))]
1331        if Game::is_ck3() || Game::is_eu5() {
1332            if let Some(trigger) = self.triggers.get(key.as_str()) {
1333                return Some(trigger);
1334            }
1335            if let Some(trigger) = self.events.get_trigger(key) {
1336                return Some(trigger);
1337            }
1338            return None;
1339        }
1340        self.triggers.get(key.as_str())
1341    }
1342
1343    pub(crate) fn get_effect(&self, key: &Token) -> Option<&Effect> {
1344        #[cfg(any(feature = "ck3", feature = "eu5"))]
1345        if Game::is_ck3() || Game::is_eu5() {
1346            if let Some(effect) = self.effects.get(key.as_str()) {
1347                return Some(effect);
1348            }
1349            if let Some(effect) = self.events.get_effect(key) {
1350                return Some(effect);
1351            }
1352            return None;
1353        }
1354        self.effects.get(key.as_str())
1355    }
1356
1357    #[cfg(feature = "ck3")]
1358    pub(crate) fn get_defined_string(&self, key: &str) -> Option<&Token> {
1359        self.defines.get_bv(key).and_then(BV::get_value)
1360    }
1361
1362    #[cfg(any(feature = "ck3", feature = "vic3"))]
1363    pub(crate) fn get_defined_array(&self, key: &str) -> Option<&Block> {
1364        self.defines.get_bv(key).and_then(BV::get_block)
1365    }
1366
1367    #[allow(clippy::missing_panics_doc)] // only panics on poisoned mutex
1368    #[cfg(feature = "ck3")]
1369    pub(crate) fn get_defined_string_warn(&self, token: &Token, key: &str) -> Option<&Token> {
1370        let result = self.get_defined_string(key);
1371        if result.is_none() {
1372            let mut cache = self.warned_defines.write().unwrap();
1373            if !cache.contains(key) {
1374                let msg = format!("{key} not defined in common/defines/");
1375                err(ErrorKey::MissingItem).msg(msg).loc(token).push();
1376                cache.insert(key.to_string());
1377            }
1378        }
1379        result
1380    }
1381
1382    #[allow(clippy::missing_panics_doc)] // only panics on poisoned mutex
1383    #[cfg(any(feature = "ck3", feature = "vic3"))]
1384    pub(crate) fn get_defined_array_warn(&self, token: &Token, key: &str) -> Option<&Block> {
1385        let result = self.get_defined_array(key);
1386        if result.is_none() {
1387            let mut cache = self.warned_defines.write().unwrap();
1388            if !cache.contains(key) {
1389                let msg = format!("{key} not defined in common/defines/");
1390                err(ErrorKey::MissingItem).msg(msg).loc(token).push();
1391                cache.insert(key.to_string());
1392            }
1393        }
1394        result
1395    }
1396
1397    #[cfg(feature = "ck3")]
1398    pub fn iter_keys_ck3<'a>(&'a self, itype: Item) -> Box<dyn Iterator<Item = &'a Token> + 'a> {
1399        match itype {
1400            Item::Coa => Box::new(self.coas.iter_keys()),
1401            Item::CoaTemplate => Box::new(self.coas.iter_template_keys()),
1402            Item::Character => Box::new(self.characters.iter_keys()),
1403            Item::CharacterInteractionCategory => Box::new(self.interaction_cats.iter_keys()),
1404            Item::Event => Box::new(self.events.iter_keys()),
1405            Item::EventNamespace => Box::new(self.events.iter_namespace_keys()),
1406            Item::GameConcept => Box::new(self.gameconcepts.iter_keys()),
1407            Item::GeneAttribute => Box::new(self.assets.iter_attribute_keys()),
1408            Item::GeneticConstraint => Box::new(self.traits.iter_constraint_keys()),
1409            Item::MenAtArms => Box::new(self.menatarmstypes.iter_keys()),
1410            Item::MenAtArmsBase => Box::new(self.menatarmstypes.iter_base_keys()),
1411            Item::Music => Box::new(self.music.iter_keys()),
1412            Item::Province => Box::new(self.provinces_ck3.iter_keys()),
1413            Item::ScriptedList => Box::new(self.scripted_lists.iter_keys()),
1414            Item::ScriptedModifier => Box::new(self.scripted_modifiers.iter_keys()),
1415            Item::ScriptValue => Box::new(self.script_values.iter_keys()),
1416            Item::Title => Box::new(self.titles.iter_keys()),
1417            Item::TitleHistory => Box::new(self.title_history.iter_keys()),
1418            Item::Trait => Box::new(self.traits.iter_keys()),
1419            Item::TraitFlag => Box::new(self.traits.iter_flag_keys()),
1420            Item::TraitTrack => Box::new(self.traits.iter_track_keys()),
1421            _ => Box::new(self.database.iter_keys(itype)),
1422        }
1423    }
1424
1425    #[cfg(feature = "vic3")]
1426    fn iter_keys_vic3<'a>(&'a self, itype: Item) -> Box<dyn Iterator<Item = &'a Token> + 'a> {
1427        match itype {
1428            Item::Coa => Box::new(self.coas.iter_keys()),
1429            Item::CoaTemplate => Box::new(self.coas.iter_template_keys()),
1430            Item::Event => Box::new(self.events.iter_keys()),
1431            Item::EventNamespace => Box::new(self.events.iter_namespace_keys()),
1432            Item::Music => Box::new(self.music.iter_keys()),
1433            Item::GeneAttribute => Box::new(self.assets.iter_attribute_keys()),
1434            Item::ScriptedList => Box::new(self.scripted_lists.iter_keys()),
1435            Item::ScriptedModifier => Box::new(self.scripted_modifiers.iter_keys()),
1436            Item::ScriptValue => Box::new(self.script_values.iter_keys()),
1437            _ => Box::new(self.database.iter_keys(itype)),
1438        }
1439    }
1440
1441    #[cfg(feature = "imperator")]
1442    fn iter_keys_imperator<'a>(&'a self, itype: Item) -> Box<dyn Iterator<Item = &'a Token> + 'a> {
1443        match itype {
1444            Item::Coa => Box::new(self.coas.iter_keys()),
1445            Item::CoaTemplate => Box::new(self.coas.iter_template_keys()),
1446            Item::Decision => Box::new(self.decisions_imperator.iter_keys()),
1447            Item::Event => Box::new(self.events.iter_keys()),
1448            Item::EventNamespace => Box::new(self.events.iter_namespace_keys()),
1449            Item::GeneAttribute => Box::new(self.assets.iter_attribute_keys()),
1450            Item::Music => Box::new(self.music.iter_keys()),
1451            Item::Province => Box::new(self.provinces_imperator.iter_keys()),
1452            Item::ScriptedList => Box::new(self.scripted_lists.iter_keys()),
1453            Item::ScriptedModifier => Box::new(self.scripted_modifiers.iter_keys()),
1454            Item::ScriptValue => Box::new(self.script_values.iter_keys()),
1455            _ => Box::new(self.database.iter_keys(itype)),
1456        }
1457    }
1458
1459    #[cfg(feature = "eu5")]
1460    fn iter_keys_eu5<'a>(&'a self, itype: Item) -> Box<dyn Iterator<Item = &'a Token> + 'a> {
1461        match itype {
1462            Item::Coa => Box::new(self.coas.iter_keys()),
1463            Item::CoaTemplate => Box::new(self.coas.iter_template_keys()),
1464            Item::Event => Box::new(self.events.iter_keys()),
1465            Item::EventNamespace => Box::new(self.events.iter_namespace_keys()),
1466            Item::Music => Box::new(self.music.iter_keys()),
1467            Item::GeneAttribute => Box::new(self.assets.iter_attribute_keys()),
1468            Item::ScriptedList => Box::new(self.scripted_lists.iter_keys()),
1469            Item::ScriptedModifier => Box::new(self.scripted_modifiers.iter_keys()),
1470            Item::ScriptValue => Box::new(self.script_values.iter_keys()),
1471            _ => Box::new(self.database.iter_keys(itype)),
1472        }
1473    }
1474
1475    #[cfg(feature = "hoi4")]
1476    fn iter_keys_hoi4<'a>(&'a self, itype: Item) -> Box<dyn Iterator<Item = &'a Token> + 'a> {
1477        match itype {
1478            Item::Event => Box::new(self.events_hoi4.iter_keys()),
1479            Item::EventNamespace => Box::new(self.events_hoi4.iter_namespace_keys()),
1480            Item::Music => Box::new(self.music_hoi4.iter_keys()),
1481            Item::MusicAsset => Box::new(self.assets.iter_music_keys()),
1482            Item::Pdxmesh => Box::new(self.gfx.iter_mesh_keys()),
1483            Item::Province => Box::new(self.provinces_hoi4.iter_keys()),
1484            Item::Sprite => Box::new(self.gfx.iter_sprite_keys()),
1485            _ => Box::new(self.database.iter_keys(itype)),
1486        }
1487    }
1488
1489    pub fn iter_keys<'a>(&'a self, itype: Item) -> Box<dyn Iterator<Item = &'a Token> + 'a> {
1490        match itype {
1491            Item::Asset => Box::new(self.assets.iter_asset_keys()),
1492            Item::BlendShape => Box::new(self.assets.iter_blend_shape_keys()),
1493            Item::Define => Box::new(self.defines.iter_keys()),
1494            Item::Entity => Box::new(self.assets.iter_entity_keys()),
1495            Item::File => Box::new(self.fileset.iter_keys()),
1496            Item::GuiLayer => Box::new(self.gui.iter_layer_keys()),
1497            Item::GuiTemplate => Box::new(self.gui.iter_template_keys()),
1498            Item::GuiType => Box::new(self.gui.iter_type_keys()),
1499            Item::Localization => Box::new(self.localization.iter_keys()),
1500            Item::OnAction => Box::new(self.on_actions.iter_keys()),
1501            #[cfg(feature = "jomini")]
1502            Item::Pdxmesh => Box::new(self.assets.iter_mesh_keys()),
1503            Item::ScriptedEffect => Box::new(self.effects.iter_keys()),
1504            Item::ScriptedTrigger => Box::new(self.triggers.iter_keys()),
1505            Item::TextFormat => Box::new(self.gui.iter_textformat_keys()),
1506            Item::TextIcon => Box::new(self.gui.iter_texticon_keys()),
1507            Item::TextureFile => Box::new(self.assets.iter_texture_keys()),
1508            Item::WidgetName => Box::new(self.gui.iter_names()),
1509            _ => match Game::game() {
1510                #[cfg(feature = "ck3")]
1511                Game::Ck3 => self.iter_keys_ck3(itype),
1512                #[cfg(feature = "vic3")]
1513                Game::Vic3 => self.iter_keys_vic3(itype),
1514                #[cfg(feature = "imperator")]
1515                Game::Imperator => self.iter_keys_imperator(itype),
1516                #[cfg(feature = "eu5")]
1517                Game::Eu5 => self.iter_keys_eu5(itype),
1518                #[cfg(feature = "hoi4")]
1519                Game::Hoi4 => self.iter_keys_hoi4(itype),
1520            },
1521        }
1522    }
1523
1524    #[cfg(feature = "jomini")]
1525    fn valid_sound(&self, name: &str) -> bool {
1526        // TODO: verify that file:/ values work
1527        if let Some(filename) = name.strip_prefix("file:/") {
1528            self.fileset.exists(filename)
1529        } else {
1530            let sounds_set = match Game::game() {
1531                #[cfg(feature = "ck3")]
1532                Game::Ck3 => &crate::ck3::tables::sounds::SOUNDS_SET,
1533                #[cfg(feature = "vic3")]
1534                Game::Vic3 => &crate::vic3::tables::sounds::SOUNDS_SET,
1535                #[cfg(feature = "imperator")]
1536                Game::Imperator => &crate::imperator::tables::sounds::SOUNDS_SET,
1537                #[cfg(feature = "eu5")]
1538                Game::Eu5 => &crate::eu5::tables::sounds::SOUNDS_SET,
1539                #[cfg(feature = "hoi4")]
1540                Game::Hoi4 => unimplemented!(),
1541            };
1542            sounds_set.contains(&Lowercase::new(name))
1543        }
1544    }
1545
1546    /// Return true iff a script value of the given name is defined.
1547    #[allow(clippy::unused_self)]
1548    #[allow(unused_variables)] // hoi4 does not use `name`
1549    pub(crate) fn script_value_exists(&self, name: &str) -> bool {
1550        if Game::is_jomini() {
1551            #[cfg(feature = "jomini")]
1552            return self.script_values.exists(name);
1553        }
1554        false
1555    }
1556
1557    pub(crate) fn event_check_scope(&self, id: &Token, sc: &mut ScopeContext) {
1558        if Game::is_hoi4() {
1559            #[cfg(feature = "hoi4")]
1560            self.events_hoi4.check_scope(id, sc, self);
1561        } else {
1562            #[cfg(feature = "jomini")]
1563            self.events.check_scope(id, sc, self);
1564        }
1565    }
1566
1567    pub(crate) fn event_validate_call(&self, id: &Token, sc: &mut ScopeContext) {
1568        if Game::is_hoi4() {
1569            #[cfg(feature = "hoi4")]
1570            self.events_hoi4.validate_call(id, self, sc);
1571        } else {
1572            #[cfg(feature = "jomini")]
1573            self.events.validate_call(id, self, sc);
1574        }
1575    }
1576}
1577
1578impl Drop for Everything {
1579    fn drop(&mut self) {
1580        // For the sake of the benchmark code, restore MACRO_MAP to a clean slate
1581        MACRO_MAP.clear();
1582    }
1583}
1584
1585#[cfg(feature = "internal_benches")]
1586#[divan::bench_group(sample_count = 10)]
1587mod benchmark {
1588    use super::*;
1589    use crate::benches;
1590    use divan::{self, Bencher};
1591
1592    #[cfg(feature = "ck3")]
1593    #[divan::bench(args = benches::ck3::bench_mods())]
1594    fn load_provinces_ck3(bencher: Bencher, (vanilla_dir, modpath): (&str, &PathBuf)) {
1595        bencher
1596            .with_inputs(|| {
1597                Everything::new(None, Some(Path::new(vanilla_dir)), None, None, modpath, vec![])
1598                    .unwrap()
1599            })
1600            .bench_local_refs(|everything| {
1601                everything.fileset.handle(&mut everything.provinces_ck3, &everything.parser);
1602            });
1603    }
1604
1605    #[cfg(feature = "vic3")]
1606    #[divan::bench(args = benches::vic3::bench_mods())]
1607    fn load_provinces_vic3(bencher: Bencher, (vanilla_dir, modpath): (&str, &PathBuf)) {
1608        bencher
1609            .with_inputs(|| {
1610                Everything::new(None, Some(Path::new(vanilla_dir)), None, None, modpath, vec![])
1611                    .unwrap()
1612            })
1613            .bench_local_refs(|everything| {
1614                everything.fileset.handle(&mut everything.provinces_vic3, &everything.parser);
1615            });
1616    }
1617
1618    #[divan::bench(args = benches::bench_mods())]
1619    fn load_localization(bencher: Bencher, (vanilla_dir, modpath): (&str, &PathBuf)) {
1620        bencher
1621            .with_inputs(|| {
1622                Everything::new(None, Some(Path::new(vanilla_dir)), None, None, modpath, vec![])
1623                    .unwrap()
1624            })
1625            .bench_local_refs(|everything| {
1626                everything.fileset.handle(&mut everything.localization, &everything.parser);
1627            });
1628    }
1629}