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