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
17use crate::block::Block;
18#[cfg(any(feature = "ck3", feature = "vic3"))]
19use crate::block::BV;
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    coa::Coas,
45    defines::Defines,
46    events::Events,
47    gui::Gui,
48    localization::Localization,
49    music::Musics,
50    on_actions::OnActions,
51    script_values::ScriptValues,
52    scripted_effects::{Effect, Effects},
53    scripted_lists::ScriptedLists,
54    scripted_modifiers::ScriptedModifiers,
55    scripted_triggers::{Trigger, Triggers},
56};
57use crate::db::{Db, DbKind};
58use crate::dds::DdsFiles;
59use crate::fileset::{FileEntry, FileKind, Fileset};
60use crate::game::Game;
61#[cfg(any(feature = "ck3", feature = "vic3"))]
62use crate::helpers::TigerHashSet;
63#[cfg(feature = "imperator")]
64use crate::imperator::data::{decisions::Decisions, provinces::ImperatorProvinces};
65#[cfg(feature = "imperator")]
66use crate::imperator::tables::misc::*;
67use crate::item::{Item, ItemLoader};
68use crate::lowercase::Lowercase;
69use crate::macros::MACRO_MAP;
70#[cfg(feature = "vic3")]
71use crate::parse::json::parse_json_file;
72use crate::parse::ParserMemory;
73use crate::pdxfile::PdxFile;
74#[cfg(any(feature = "ck3", feature = "vic3"))]
75use crate::report::err;
76use crate::report::{report, set_output_style, ErrorKey, OutputStyle, Severity};
77use crate::rivers::Rivers;
78use crate::token::{Loc, Token};
79#[cfg(feature = "vic3")]
80use crate::vic3::data::{
81    buy_packages::BuyPackage, history::History, provinces::Vic3Provinces,
82    strategic_regions::StrategicRegion, terrain::TerrainMask,
83};
84#[cfg(feature = "vic3")]
85use crate::vic3::tables::misc::*;
86
87#[derive(Debug, Error)]
88#[allow(clippy::enum_variant_names)]
89pub enum FilesError {
90    #[error("Could not read game files at {path}")]
91    VanillaUnreadable { path: PathBuf, source: walkdir::Error },
92    #[error("Could not read mod files at {path}")]
93    ModUnreadable { path: PathBuf, source: walkdir::Error },
94    #[error("Could not read config file at {path}")]
95    ConfigUnreadable { path: PathBuf },
96}
97
98/// A record of everything known about the game and mod being validated.
99///
100/// References to [`Everything`] are passed down through nearly all of the validation logic, so
101/// that individual functions can access all the defined game items.
102///
103/// The validator has two main phases: parsing and validation.
104/// * During parsing, the script files are read, parsed, and loaded into the various databases.
105///   `Everything` is mutable during this period.
106/// * During validation, `Everything` is immutable and cross-checking between item types can be done safely.
107#[derive(Debug)]
108pub struct Everything {
109    /// Config from file
110    config: Block,
111
112    /// The global parser state, carrying information between files.
113    /// Currently only used by the pdxfile parser, to handle the `reader_export` directory,
114    /// which is specially processed before all other files.
115    pub parser: ParserMemory,
116
117    /// A cache of define values (from common/defines) that are missing and that have already been
118    /// warned about as missing. This is to avoid duplicate warnings.
119    #[cfg(any(feature = "ck3", feature = "vic3"))]
120    warned_defines: RwLock<TigerHashSet<String>>,
121
122    /// Tracks all the files (vanilla and mods) that are relevant to the current validation.
123    pub(crate) fileset: Fileset,
124
125    /// Tracks specifically the .dds files, and their formats and sizes.
126    pub(crate) dds: DdsFiles,
127
128    /// A general database of item types. Most items go here. The ones that need special handling
129    /// go in the separate databases listed below.
130    pub(crate) database: Db,
131
132    pub(crate) localization: Localization,
133
134    pub(crate) scripted_lists: ScriptedLists,
135
136    pub(crate) defines: Defines,
137
138    pub(crate) events: Events,
139    #[cfg(feature = "imperator")]
140    pub(crate) decisions_imperator: Decisions,
141
142    pub(crate) scripted_modifiers: ScriptedModifiers,
143    pub(crate) on_actions: OnActions,
144
145    #[cfg(feature = "ck3")]
146    pub(crate) interaction_cats: CharacterInteractionCategories,
147
148    #[cfg(feature = "ck3")]
149    pub(crate) provinces_ck3: Ck3Provinces,
150    #[cfg(feature = "vic3")]
151    pub(crate) provinces_vic3: Vic3Provinces,
152    #[cfg(feature = "imperator")]
153    pub(crate) provinces_imperator: ImperatorProvinces,
154
155    #[cfg(feature = "ck3")]
156    pub(crate) province_histories: ProvinceHistories,
157    #[cfg(feature = "ck3")]
158    pub(crate) province_properties: ProvinceProperties,
159    #[cfg(feature = "ck3")]
160    pub(crate) province_terrains: ProvinceTerrains,
161
162    #[cfg(feature = "ck3")]
163    pub(crate) gameconcepts: GameConcepts,
164
165    #[cfg(feature = "ck3")]
166    pub(crate) titles: Titles,
167
168    #[cfg(feature = "ck3")]
169    pub(crate) characters: Characters,
170
171    pub(crate) script_values: ScriptValues,
172
173    pub(crate) triggers: Triggers,
174    pub(crate) effects: Effects,
175
176    #[cfg(feature = "ck3")]
177    pub(crate) traits: Traits,
178
179    #[cfg(feature = "ck3")]
180    pub(crate) title_history: TitleHistories,
181
182    #[cfg(feature = "ck3")]
183    pub(crate) doctrines: Doctrines,
184
185    #[cfg(feature = "ck3")]
186    pub(crate) menatarmstypes: MenAtArmsTypes,
187
188    pub(crate) gui: Gui,
189    #[cfg(any(feature = "ck3", feature = "vic3"))]
190    pub(crate) data_bindings: DataBindings,
191
192    pub(crate) assets: Assets,
193    pub(crate) music: Musics,
194
195    pub(crate) coas: Coas,
196
197    #[cfg(feature = "vic3")]
198    pub(crate) history: History,
199
200    #[cfg(feature = "ck3")]
201    pub(crate) wars: Wars,
202}
203
204impl Everything {
205    /// Create a new `Everything` instance, ready for validating a mod.
206    ///
207    /// `vanilla_dir` is the path to the base game files. If it's `None`, then no vanilla files
208    /// will be loaded. This will seriously affect validation, but it's ok if you just want to load
209    /// and examine the mod files.
210    ///
211    /// `mod_root` is the path to the mod files. The config file will also be looked for there.
212    ///
213    /// `replace_paths` is from the similarly named field in the `.mod` file.
214    pub fn new(
215        config_filepath: Option<&Path>,
216        vanilla_dir: Option<&Path>,
217        mod_root: &Path,
218        replace_paths: Vec<PathBuf>,
219    ) -> Result<Self> {
220        let mut fileset = Fileset::new(vanilla_dir, mod_root.to_path_buf(), replace_paths);
221
222        let config_file_name = match Game::game() {
223            #[cfg(feature = "ck3")]
224            Game::Ck3 => "ck3-tiger.conf",
225            #[cfg(feature = "vic3")]
226            Game::Vic3 => "vic3-tiger.conf",
227            #[cfg(feature = "imperator")]
228            Game::Imperator => "imperator-tiger.conf",
229        };
230
231        let config_file = match config_filepath {
232            Some(path) => path.to_path_buf(),
233            None => mod_root.join(config_file_name),
234        };
235
236        let config = if config_file.is_file() {
237            Self::read_config(config_file_name, &config_file)
238                .ok_or(FilesError::ConfigUnreadable { path: config_file })?
239        } else {
240            Block::new(Loc::for_file(config_file.clone(), FileKind::Mod, config_file.clone()))
241        };
242
243        fileset.config(config.clone())?;
244
245        fileset.scan_all()?;
246        fileset.finalize();
247
248        Ok(Everything {
249            parser: ParserMemory::default(),
250            fileset,
251            dds: DdsFiles::default(),
252            config,
253            #[cfg(any(feature = "ck3", feature = "vic3"))]
254            warned_defines: RwLock::new(TigerHashSet::default()),
255            database: Db::default(),
256            localization: Localization::default(),
257            scripted_lists: ScriptedLists::default(),
258            defines: Defines::default(),
259            events: Events::default(),
260            #[cfg(feature = "imperator")]
261            decisions_imperator: Decisions::default(),
262            scripted_modifiers: ScriptedModifiers::default(),
263            on_actions: OnActions::default(),
264            #[cfg(feature = "ck3")]
265            interaction_cats: CharacterInteractionCategories::default(),
266            #[cfg(feature = "ck3")]
267            provinces_ck3: Ck3Provinces::default(),
268            #[cfg(feature = "vic3")]
269            provinces_vic3: Vic3Provinces::default(),
270            #[cfg(feature = "imperator")]
271            provinces_imperator: ImperatorProvinces::default(),
272            #[cfg(feature = "ck3")]
273            province_histories: ProvinceHistories::default(),
274            #[cfg(feature = "ck3")]
275            province_properties: ProvinceProperties::default(),
276            #[cfg(feature = "ck3")]
277            province_terrains: ProvinceTerrains::default(),
278            #[cfg(feature = "ck3")]
279            gameconcepts: GameConcepts::default(),
280            #[cfg(feature = "ck3")]
281            titles: Titles::default(),
282            #[cfg(feature = "ck3")]
283            characters: Characters::default(),
284            script_values: ScriptValues::default(),
285            triggers: Triggers::default(),
286            effects: Effects::default(),
287            #[cfg(feature = "ck3")]
288            traits: Traits::default(),
289            #[cfg(feature = "ck3")]
290            title_history: TitleHistories::default(),
291            #[cfg(feature = "ck3")]
292            doctrines: Doctrines::default(),
293            #[cfg(feature = "ck3")]
294            menatarmstypes: MenAtArmsTypes::default(),
295            gui: Gui::default(),
296            #[cfg(any(feature = "ck3", feature = "vic3"))]
297            data_bindings: DataBindings::default(),
298            assets: Assets::default(),
299            music: Musics::default(),
300            coas: Coas::default(),
301            #[cfg(feature = "vic3")]
302            history: History::default(),
303            #[cfg(feature = "ck3")]
304            wars: Wars::default(),
305        })
306    }
307
308    fn read_config(name: &str, path: &Path) -> Option<Block> {
309        let entry = FileEntry::new(PathBuf::from(name), FileKind::Mod, path.to_path_buf());
310        PdxFile::read_optional_bom(&entry, &ParserMemory::default())
311    }
312
313    pub fn load_config_filtering_rules(&self) {
314        check_for_legacy_ignore(&self.config);
315        load_filter(&self.config);
316    }
317
318    /// Load the `OutputStyle` settings from the config.
319    /// Note that the settings from the config can still be overridden
320    /// by supplying the --no-color flag.
321    fn load_output_styles(&self, default_color: bool) -> OutputStyle {
322        // Treat a missing output_style block and an empty output_style block exactly the same.
323        let block = match self.config.get_field_block("output_style") {
324            Some(block) => Cow::Borrowed(block),
325            None => Cow::Owned(Block::new(self.config.loc)),
326        };
327        if !block.get_field_bool("enable").unwrap_or(default_color) {
328            return OutputStyle::no_color();
329        }
330        let mut style = OutputStyle::default();
331        for severity in Severity::iter() {
332            if let Some(error_block) =
333                block.get_field_block(format!("{severity}").to_ascii_lowercase().as_str())
334            {
335                if let Some(color) = error_block.get_field_value("color") {
336                    style.set(severity, color.as_str());
337                }
338            }
339        }
340        style
341    }
342
343    pub fn load_output_settings(&self, default_colors: bool) {
344        set_output_style(self.load_output_styles(default_colors));
345    }
346
347    #[cfg(feature = "vic3")]
348    fn load_json<F>(&mut self, itype: Item, add_json: F)
349    where
350        F: Fn(&mut Db, Block) + Sync + Send,
351    {
352        for block in self.fileset.filter_map_under(&PathBuf::from(itype.path()), |entry| {
353            if entry.filename().to_string_lossy().ends_with(".json") {
354                parse_json_file(entry)
355            } else {
356                None
357            }
358        }) {
359            add_json(&mut self.database, block);
360        }
361    }
362
363    #[cfg(feature = "ck3")]
364    fn load_reader_export(&mut self) {
365        let path = PathBuf::from("reader_export");
366        for entry in self.fileset.get_files_under(&path) {
367            if entry.filename().to_string_lossy().ends_with(".txt") {
368                PdxFile::reader_export(entry, &mut self.parser.pdxfile);
369            }
370        }
371    }
372
373    fn load_pdx_files(&mut self, loader: &ItemLoader) {
374        let path = PathBuf::from(loader.itype().path());
375        for mut block in self.fileset.filter_map_under(&path, |entry| {
376            if entry.filename().to_string_lossy().ends_with(loader.extension()) {
377                PdxFile::read_encoded(entry, loader.encoding(), &self.parser)
378            } else {
379                None
380            }
381        }) {
382            if loader.whole_file() {
383                let fname = block.loc.filename();
384                // unwrap is safe here because of the ends_with check above.
385                let key = fname.strip_suffix(loader.extension()).unwrap();
386                let key = Token::new(key, block.loc);
387                (loader.adder())(&mut self.database, key, block);
388            } else {
389                for (key, block) in block.drain_definitions_warn() {
390                    (loader.adder())(&mut self.database, key, block);
391                }
392            }
393        }
394    }
395
396    fn load_all_normal_pdx_files(&mut self) {
397        for loader in inventory::iter::<ItemLoader> {
398            if loader.for_game(Game::game()) {
399                self.load_pdx_files(loader);
400            }
401        }
402    }
403
404    fn load_all_generic(&mut self) {
405        scope(|s| {
406            s.spawn(|_| self.fileset.handle(&mut self.dds, &self.parser));
407            s.spawn(|_| self.fileset.handle(&mut self.events, &self.parser));
408            s.spawn(|_| self.fileset.handle(&mut self.localization, &self.parser));
409            s.spawn(|_| self.fileset.handle(&mut self.scripted_lists, &self.parser));
410            s.spawn(|_| self.fileset.handle(&mut self.defines, &self.parser));
411            s.spawn(|_| self.fileset.handle(&mut self.scripted_modifiers, &self.parser));
412            s.spawn(|_| self.fileset.handle(&mut self.script_values, &self.parser));
413            s.spawn(|_| self.fileset.handle(&mut self.triggers, &self.parser));
414            s.spawn(|_| self.fileset.handle(&mut self.effects, &self.parser));
415            s.spawn(|_| self.fileset.handle(&mut self.assets, &self.parser));
416            s.spawn(|_| self.fileset.handle(&mut self.gui, &self.parser));
417            s.spawn(|_| self.fileset.handle(&mut self.on_actions, &self.parser));
418            s.spawn(|_| self.fileset.handle(&mut self.coas, &self.parser));
419            s.spawn(|_| self.fileset.handle(&mut self.music, &self.parser));
420        });
421
422        self.load_all_normal_pdx_files();
423    }
424
425    #[cfg(feature = "ck3")]
426    fn load_all_ck3(&mut self) {
427        scope(|s| {
428            s.spawn(|_| self.fileset.handle(&mut self.interaction_cats, &self.parser));
429            s.spawn(|_| self.fileset.handle(&mut self.province_histories, &self.parser));
430            s.spawn(|_| self.fileset.handle(&mut self.province_properties, &self.parser));
431            s.spawn(|_| self.fileset.handle(&mut self.province_terrains, &self.parser));
432            s.spawn(|_| self.fileset.handle(&mut self.gameconcepts, &self.parser));
433            s.spawn(|_| self.fileset.handle(&mut self.titles, &self.parser));
434            s.spawn(|_| self.fileset.handle(&mut self.characters, &self.parser));
435            s.spawn(|_| self.fileset.handle(&mut self.traits, &self.parser));
436            s.spawn(|_| self.fileset.handle(&mut self.title_history, &self.parser));
437            s.spawn(|_| self.fileset.handle(&mut self.doctrines, &self.parser));
438            s.spawn(|_| self.fileset.handle(&mut self.menatarmstypes, &self.parser));
439            s.spawn(|_| self.fileset.handle(&mut self.data_bindings, &self.parser));
440            s.spawn(|_| self.fileset.handle(&mut self.provinces_ck3, &self.parser));
441            s.spawn(|_| self.fileset.handle(&mut self.wars, &self.parser));
442        });
443        crate::ck3::data::buildings::Building::finalize(&mut self.database);
444    }
445
446    #[cfg(feature = "vic3")]
447    fn load_all_vic3(&mut self) {
448        self.fileset.handle(&mut self.history, &self.parser);
449        self.fileset.handle(&mut self.provinces_vic3, &self.parser);
450        self.fileset.handle(&mut self.data_bindings, &self.parser);
451        self.load_json(Item::TerrainMask, TerrainMask::add_json);
452    }
453
454    #[cfg(feature = "imperator")]
455    fn load_all_imperator(&mut self) {
456        self.fileset.handle(&mut self.decisions_imperator, &self.parser);
457        self.fileset.handle(&mut self.provinces_imperator, &self.parser);
458    }
459
460    pub fn load_all(&mut self) {
461        #[cfg(feature = "ck3")]
462        self.load_reader_export();
463        self.load_all_generic();
464        match Game::game() {
465            #[cfg(feature = "ck3")]
466            Game::Ck3 => self.load_all_ck3(),
467            #[cfg(feature = "vic3")]
468            Game::Vic3 => self.load_all_vic3(),
469            #[cfg(feature = "imperator")]
470            Game::Imperator => self.load_all_imperator(),
471        }
472        self.database.add_subitems();
473    }
474
475    fn validate_all_generic<'a>(&'a self, s: &Scope<'a>) {
476        s.spawn(|_| self.fileset.validate(self));
477        s.spawn(|_| self.scripted_lists.validate(self));
478        s.spawn(|_| self.defines.validate(self));
479        s.spawn(|_| self.scripted_modifiers.validate(self));
480        s.spawn(|_| self.script_values.validate(self));
481        s.spawn(|_| self.triggers.validate(self));
482        s.spawn(|_| self.effects.validate(self));
483        s.spawn(|_| self.events.validate(self));
484        s.spawn(|_| self.assets.validate(self));
485        s.spawn(|_| self.gui.validate(self));
486        s.spawn(|_| self.on_actions.validate(self));
487        s.spawn(|_| self.coas.validate(self));
488        s.spawn(|_| self.music.validate(self));
489    }
490
491    #[cfg(feature = "ck3")]
492    fn validate_all_ck3<'a>(&'a self, s: &Scope<'a>) {
493        s.spawn(|_| self.interaction_cats.validate(self));
494        s.spawn(|_| self.province_histories.validate(self));
495        s.spawn(|_| self.province_properties.validate(self));
496        s.spawn(|_| self.province_terrains.validate(self));
497        s.spawn(|_| self.gameconcepts.validate(self));
498        s.spawn(|_| self.titles.validate(self));
499        s.spawn(|_| self.characters.validate(self));
500        s.spawn(|_| self.traits.validate(self));
501        s.spawn(|_| self.title_history.validate(self));
502        s.spawn(|_| self.doctrines.validate(self));
503        s.spawn(|_| self.menatarmstypes.validate(self));
504        s.spawn(|_| self.data_bindings.validate(self));
505        s.spawn(|_| self.provinces_ck3.validate(self));
506        s.spawn(|_| self.wars.validate(self));
507        s.spawn(|_| Climate::validate_all(&self.database, self));
508    }
509
510    #[cfg(feature = "vic3")]
511    fn validate_all_vic3<'a>(&'a self, s: &Scope<'a>) {
512        s.spawn(|_| self.history.validate(self));
513        s.spawn(|_| self.provinces_vic3.validate(self));
514        s.spawn(|_| self.data_bindings.validate(self));
515        s.spawn(|_| StrategicRegion::crosscheck(self));
516        s.spawn(|_| BuyPackage::crosscheck(self));
517    }
518
519    #[cfg(feature = "imperator")]
520    fn validate_all_imperator<'a>(&'a self, s: &Scope<'a>) {
521        s.spawn(|_| self.decisions_imperator.validate(self));
522        s.spawn(|_| self.provinces_imperator.validate(self));
523    }
524
525    pub fn validate_all(&self) {
526        scope(|s| {
527            self.validate_all_generic(s);
528            match Game::game() {
529                #[cfg(feature = "ck3")]
530                Game::Ck3 => self.validate_all_ck3(s),
531                #[cfg(feature = "vic3")]
532                Game::Vic3 => self.validate_all_vic3(s),
533                #[cfg(feature = "imperator")]
534                Game::Imperator => self.validate_all_imperator(s),
535            }
536        });
537        self.database.validate(self);
538
539        self.localization.validate_pass2(self);
540    }
541
542    pub fn check_rivers(&mut self) {
543        let mut rivers = Rivers::default();
544        self.fileset.handle(&mut rivers, &self.parser);
545        rivers.validate(self);
546    }
547
548    #[cfg(feature = "ck3")]
549    pub fn check_pod(&mut self) {
550        self.province_histories.check_pod_faiths(self, &self.titles);
551        self.characters.check_pod_flags(self);
552        self.localization.check_pod_loca(self);
553    }
554
555    pub fn check_unused(&mut self) {
556        self.localization.check_unused(self);
557        self.fileset.check_unused_dds(self);
558    }
559
560    pub(crate) fn item_has_property(&self, itype: Item, key: &str, property: &str) -> bool {
561        self.database.has_property(itype, key, property, self)
562    }
563
564    #[cfg(feature = "ck3")] // vic3 happens not to use
565    pub(crate) fn item_lc_has_property(
566        &self,
567        itype: Item,
568        key: &Lowercase,
569        property: &str,
570    ) -> bool {
571        self.database.lc_has_property(itype, key, property, self)
572    }
573
574    #[cfg(feature = "ck3")]
575    fn item_exists_ck3(&self, itype: Item, key: &str) -> bool {
576        match itype {
577            Item::ActivityState => ACTIVITY_STATES.contains(&key),
578            Item::ArtifactHistory => ARTIFACT_HISTORY.contains(&key),
579            Item::ArtifactRarity => ARTIFACT_RARITIES.contains(&&*key.to_ascii_lowercase()),
580            Item::Character => self.characters.exists(key),
581            Item::CharacterInteractionCategory => self.interaction_cats.exists(key),
582            Item::DangerType => DANGER_TYPES.contains(&key),
583            Item::DlcFeature => DLC_FEATURES_CK3.contains(&key),
584            Item::Doctrine => self.doctrines.exists(key),
585            Item::DoctrineCategory => self.doctrines.category_exists(key),
586            Item::DoctrineParameter => self.doctrines.parameter_exists(key),
587            Item::GameConcept => self.gameconcepts.exists(key),
588            Item::GeneticConstraint => self.traits.constraint_exists(key),
589            Item::MenAtArms => self.menatarmstypes.exists(key),
590            Item::MenAtArmsBase => self.menatarmstypes.base_exists(key),
591            Item::PrisonType => PRISON_TYPES.contains(&key),
592            Item::Province => self.provinces_ck3.exists(key),
593            Item::RewardItem => REWARD_ITEMS.contains(&key),
594            Item::Sexuality => SEXUALITIES.contains(&key),
595            Item::Skill => SKILLS.contains(&key),
596            Item::Sound => self.valid_sound(key),
597            Item::Title => self.titles.exists(key),
598            Item::TitleHistory => self.title_history.exists(key),
599            Item::TitleHistoryType => TITLE_HISTORY_TYPES.contains(&key),
600            Item::Trait => self.traits.exists(key),
601            Item::TraitFlag => self.traits.flag_exists(key),
602            Item::TraitTrack => self.traits.track_exists(key),
603            Item::TraitCategory => TRAIT_CATEGORIES.contains(&key),
604            _ => self.database.exists(itype, key),
605        }
606    }
607
608    #[cfg(feature = "vic3")]
609    fn item_exists_vic3(&self, itype: Item, key: &str) -> bool {
610        match itype {
611            Item::Approval => APPROVALS.contains(&key),
612            Item::Attitude => ATTITUDES.contains(&&*key.to_lowercase()),
613            Item::CharacterRole => CHARACTER_ROLES.contains(&key),
614            Item::CountryTier => COUNTRY_TIERS.contains(&key),
615            Item::DlcFeature => DLC_FEATURES_VIC3.contains(&key),
616            Item::EventCategory => EVENT_CATEGORIES.contains(&key),
617            Item::InfamyThreshold => INFAMY_THRESHOLDS.contains(&key),
618            Item::Level => LEVELS.contains(&key),
619            Item::RelationsThreshold => RELATIONS.contains(&key),
620            Item::SecretGoal => SECRET_GOALS.contains(&key),
621            Item::Sound => self.valid_sound(key),
622            Item::Strata => STRATA.contains(&key),
623            Item::TerrainKey => TERRAIN_KEYS.contains(&key),
624            Item::TransferOfPower => TRANSFER_OF_POWER.contains(&key),
625            Item::Wargoal => WARGOALS.contains(&key),
626            _ => self.database.exists(itype, key),
627        }
628    }
629
630    #[cfg(feature = "imperator")]
631    fn item_exists_imperator(&self, itype: Item, key: &str) -> bool {
632        match itype {
633            Item::DlcName => DLC_NAME_IMPERATOR.contains(&key),
634            Item::Decision => self.decisions_imperator.exists(key),
635            Item::Province => self.provinces_imperator.exists(key),
636            Item::Sound => self.valid_sound(key),
637            _ => self.database.exists(itype, key),
638        }
639    }
640
641    pub(crate) fn item_exists(&self, itype: Item, key: &str) -> bool {
642        match itype {
643            Item::Asset => self.assets.asset_exists(key),
644            Item::BlendShape => self.assets.blend_shape_exists(key),
645            Item::Coa => self.coas.exists(key),
646            Item::CoaTemplate => self.coas.template_exists(key),
647            Item::Define => self.defines.exists(key),
648            Item::Entity => self.assets.entity_exists(key),
649            Item::Entry => self.fileset.entry_exists(key),
650            Item::Event => self.events.exists(key),
651            Item::EventNamespace => self.events.namespace_exists(key),
652            Item::File => self.fileset.exists(key),
653            Item::GeneAttribute => self.assets.attribute_exists(key),
654            Item::GuiLayer => self.gui.layer_exists(key),
655            Item::GuiTemplate => self.gui.template_exists(key),
656            Item::GuiType => self.gui.type_exists(&Lowercase::new(key)),
657            Item::Localization => self.localization.exists(key),
658            Item::Music => self.music.exists(key),
659            Item::OnAction => self.on_actions.exists(key),
660            Item::Pdxmesh => self.assets.mesh_exists(key),
661            Item::ScriptedEffect => self.effects.exists(key),
662            Item::ScriptedList => self.scripted_lists.exists(key),
663            Item::ScriptedModifier => self.scripted_modifiers.exists(key),
664            Item::ScriptedTrigger => self.triggers.exists(key),
665            Item::ScriptValue => self.script_values.exists(key),
666            Item::TextFormat => self.gui.textformat_exists(key),
667            Item::TextIcon => self.gui.texticon_exists(key),
668            Item::TextureFile => self.assets.texture_exists(key),
669            Item::WidgetName => self.gui.name_exists(key),
670            Item::Directory | Item::Shortcut => true, // TODO
671            _ => match Game::game() {
672                #[cfg(feature = "ck3")]
673                Game::Ck3 => self.item_exists_ck3(itype, key),
674                #[cfg(feature = "vic3")]
675                Game::Vic3 => self.item_exists_vic3(itype, key),
676                #[cfg(feature = "imperator")]
677                Game::Imperator => self.item_exists_imperator(itype, key),
678            },
679        }
680    }
681
682    /// Return true iff the item `key` is found with a case insensitive match.
683    /// This function is **incomplete**. It only contains the item types for which case insensitive
684    /// matches are needed; this is currently the ones used in `src/ck3/tables/modif.rs`.
685    #[cfg(feature = "ck3")]
686    fn item_exists_lc_ck3(&self, itype: Item, key: &Lowercase) -> bool {
687        match itype {
688            Item::MenAtArmsBase => self.menatarmstypes.base_exists_lc(key),
689            Item::Trait => self.traits.exists_lc(key),
690            Item::TraitTrack => self.traits.track_exists_lc(key),
691            _ => self.database.exists_lc(itype, key),
692        }
693    }
694
695    /// Return true iff the item `key` is found with a case insensitive match.
696    /// This function is **incomplete**. It only contains the item types for which case insensitive
697    /// matches are needed; this is currently the ones used in `src/vic3/tables/modif.rs`.
698    #[cfg(feature = "vic3")]
699    fn item_exists_lc_vic3(&self, itype: Item, key: &Lowercase) -> bool {
700        match itype {
701            Item::TerrainKey => TERRAIN_KEYS.contains(&key.as_str()),
702            _ => self.database.exists_lc(itype, key),
703        }
704    }
705
706    /// Return true iff the item `key` is found with a case insensitive match.
707    /// This function is **incomplete**. It only contains the item types for which case insensitive
708    /// matches are needed; this is currently the ones used in `src/imperator/tables/modif.rs`.
709    #[cfg(feature = "imperator")]
710    fn item_exists_lc_imperator(&self, itype: Item, key: &Lowercase) -> bool {
711        #[allow(clippy::match_single_binding)]
712        match itype {
713            _ => self.database.exists_lc(itype, key),
714        }
715    }
716
717    /// Return true iff the item `key` is found with a case insensitive match.
718    /// This function is **incomplete**. It only contains the item types for which case insensitive
719    /// matches are needed; this is currently the ones used in modif lookups.
720    pub(crate) fn item_exists_lc(&self, itype: Item, key: &Lowercase) -> bool {
721        #[allow(clippy::match_single_binding)]
722        match itype {
723            _ => match Game::game() {
724                #[cfg(feature = "ck3")]
725                Game::Ck3 => self.item_exists_lc_ck3(itype, key),
726                #[cfg(feature = "vic3")]
727                Game::Vic3 => self.item_exists_lc_vic3(itype, key),
728                #[cfg(feature = "imperator")]
729                Game::Imperator => self.item_exists_lc_imperator(itype, key),
730            },
731        }
732    }
733
734    pub(crate) fn mark_used(&self, itype: Item, key: &str) {
735        match itype {
736            Item::File => self.fileset.mark_used(key),
737            Item::Localization => self.localization.mark_used(key),
738            _ => (),
739        }
740    }
741
742    pub(crate) fn verify_exists(&self, itype: Item, token: &Token) {
743        self.verify_exists_implied(itype, token.as_str(), token);
744    }
745
746    pub(crate) fn verify_exists_max_sev(&self, itype: Item, token: &Token, max_sev: Severity) {
747        self.verify_exists_implied_max_sev(itype, token.as_str(), token, max_sev);
748    }
749
750    pub(crate) fn verify_exists_implied_max_sev(
751        &self,
752        itype: Item,
753        key: &str,
754        token: &Token,
755        max_sev: Severity,
756    ) {
757        match itype {
758            Item::Entry => self.fileset.verify_entry_exists(key, token, max_sev),
759            Item::File => self.fileset.verify_exists_implied(key, token, max_sev),
760            Item::Localization => self.localization.verify_exists_implied(key, token, max_sev),
761            Item::Music => self.music.verify_exists_implied(key, token, max_sev),
762            Item::Province => match Game::game() {
763                #[cfg(feature = "ck3")]
764                Game::Ck3 => self.provinces_ck3.verify_exists_implied(key, token, max_sev),
765                #[cfg(feature = "vic3")]
766                Game::Vic3 => self.provinces_vic3.verify_exists_implied(key, token, max_sev),
767                #[cfg(feature = "imperator")]
768                Game::Imperator => {
769                    self.provinces_imperator.verify_exists_implied(key, token, max_sev);
770                }
771            },
772            Item::TextureFile => {
773                if let Some(entry) = self.assets.get_texture(key) {
774                    // TODO: avoid allocating a string here
775                    self.fileset.mark_used(&entry.path().to_string_lossy());
776                } else {
777                    let msg = format!("no texture file {key} anywhere under {}", itype.path());
778                    report(ErrorKey::MissingFile, itype.severity().at_most(max_sev))
779                        .conf(itype.confidence())
780                        .msg(msg)
781                        .loc(token)
782                        .push();
783                }
784            }
785            _ => {
786                if !self.item_exists(itype, key) {
787                    let path = itype.path();
788                    let msg = if path.is_empty() {
789                        format!("unknown {itype} {key}")
790                    } else {
791                        format!("{itype} {key} not defined in {path}")
792                    };
793                    report(ErrorKey::MissingItem, itype.severity().at_most(max_sev))
794                        .conf(itype.confidence())
795                        .msg(msg)
796                        .loc(token)
797                        .push();
798                }
799            }
800        }
801    }
802
803    pub(crate) fn verify_exists_implied(&self, itype: Item, key: &str, token: &Token) {
804        self.verify_exists_implied_max_sev(itype, key, token, Severity::Error);
805    }
806
807    #[cfg(feature = "ck3")]
808    pub(crate) fn verify_icon(&self, define: &str, token: &Token, suffix: &str) {
809        if let Some(icon_path) = self.get_defined_string_warn(token, define) {
810            let pathname = format!("{icon_path}/{token}{suffix}");
811            // It's `Severity::Warning` because a missing icon is only a UI issue.
812            self.verify_exists_implied_max_sev(Item::File, &pathname, token, Severity::Warning);
813        }
814    }
815
816    #[cfg(feature = "ck3")]
817    pub(crate) fn mark_used_icon(&self, define: &str, token: &Token, suffix: &str) {
818        if let Some(icon_path) = self.get_defined_string_warn(token, define) {
819            let pathname = format!("{icon_path}/{token}{suffix}");
820            self.fileset.mark_used(&pathname);
821        }
822    }
823
824    pub(crate) fn validate_use(&self, itype: Item, key: &Token, block: &Block) {
825        self.database.validate_use(itype, key, block, self);
826    }
827
828    #[cfg(feature = "ck3")] // happens not to be used by vic3
829    pub(crate) fn validate_call(
830        &self,
831        itype: Item,
832        key: &Token,
833        block: &Block,
834        sc: &mut ScopeContext,
835    ) {
836        self.database.validate_call(itype, key, block, self, sc);
837    }
838
839    /// Validate the use of a localization within a specific `ScopeContext`.
840    /// This allows validation of the named scopes used within the localization's datafunctions.
841    pub(crate) fn validate_localization_sc(&self, key: &str, sc: &mut ScopeContext) {
842        self.localization.validate_use(key, self, sc);
843    }
844
845    #[allow(dead_code)]
846    pub(crate) fn get_item<T: DbKind>(
847        &self,
848        itype: Item,
849        key: &str,
850    ) -> Option<(&Token, &Block, &T)> {
851        self.database.get_item(itype, key)
852    }
853
854    pub(crate) fn get_key_block(&self, itype: Item, key: &str) -> Option<(&Token, &Block)> {
855        self.database.get_key_block(itype, key)
856    }
857
858    pub(crate) fn get_trigger(&self, key: &Token) -> Option<&Trigger> {
859        #[cfg(feature = "ck3")]
860        if Game::is_ck3() {
861            if let Some(trigger) = self.triggers.get(key.as_str()) {
862                return Some(trigger);
863            }
864            if let Some(trigger) = self.events.get_trigger(key) {
865                return Some(trigger);
866            }
867            return None;
868        }
869        self.triggers.get(key.as_str())
870    }
871
872    pub(crate) fn get_effect(&self, key: &Token) -> Option<&Effect> {
873        #[cfg(feature = "ck3")]
874        if Game::is_ck3() {
875            if let Some(effect) = self.effects.get(key.as_str()) {
876                return Some(effect);
877            }
878            if let Some(effect) = self.events.get_effect(key) {
879                return Some(effect);
880            }
881            return None;
882        }
883        self.effects.get(key.as_str())
884    }
885
886    #[cfg(feature = "ck3")] // happens not to be used by vic3
887    pub(crate) fn get_defined_string(&self, key: &str) -> Option<&Token> {
888        self.defines.get_bv(key).and_then(BV::get_value)
889    }
890
891    #[cfg(any(feature = "ck3", feature = "vic3"))]
892    pub(crate) fn get_defined_array(&self, key: &str) -> Option<&Block> {
893        self.defines.get_bv(key).and_then(BV::get_block)
894    }
895
896    #[allow(clippy::missing_panics_doc)] // only panics on poisoned mutex
897    #[cfg(feature = "ck3")] // happens not to be used by vic3
898    pub(crate) fn get_defined_string_warn(&self, token: &Token, key: &str) -> Option<&Token> {
899        let result = self.get_defined_string(key);
900        let mut cache = self.warned_defines.write().unwrap();
901        if result.is_none() && !cache.contains(key) {
902            let msg = format!("{key} not defined in common/defines/");
903            err(ErrorKey::MissingItem).msg(msg).loc(token).push();
904            cache.insert(key.to_string());
905        }
906        result
907    }
908
909    #[allow(clippy::missing_panics_doc)] // only panics on poisoned mutex
910    #[cfg(any(feature = "ck3", feature = "vic3"))]
911    pub(crate) fn get_defined_array_warn(&self, token: &Token, key: &str) -> Option<&Block> {
912        let result = self.get_defined_array(key);
913        let mut cache = self.warned_defines.write().unwrap();
914        if result.is_none() && !cache.contains(key) {
915            let msg = format!("{key} not defined in common/defines/");
916            err(ErrorKey::MissingItem).msg(msg).loc(token).push();
917            cache.insert(key.to_string());
918        }
919        result
920    }
921
922    #[cfg(feature = "ck3")]
923    pub fn iter_keys_ck3<'a>(&'a self, itype: Item) -> Box<dyn Iterator<Item = &'a Token> + 'a> {
924        match itype {
925            Item::Character => Box::new(self.characters.iter_keys()),
926            Item::CharacterInteractionCategory => Box::new(self.interaction_cats.iter_keys()),
927            Item::Doctrine => Box::new(self.doctrines.iter_keys()),
928            Item::DoctrineCategory => Box::new(self.doctrines.iter_category_keys()),
929            Item::DoctrineParameter => Box::new(self.doctrines.iter_parameter_keys()),
930            Item::GameConcept => Box::new(self.gameconcepts.iter_keys()),
931            Item::GeneticConstraint => Box::new(self.traits.iter_constraint_keys()),
932            Item::MenAtArms => Box::new(self.menatarmstypes.iter_keys()),
933            Item::MenAtArmsBase => Box::new(self.menatarmstypes.iter_base_keys()),
934            Item::Province => Box::new(self.provinces_ck3.iter_keys()),
935            Item::Title => Box::new(self.titles.iter_keys()),
936            Item::TitleHistory => Box::new(self.title_history.iter_keys()),
937            Item::Trait => Box::new(self.traits.iter_keys()),
938            Item::TraitFlag => Box::new(self.traits.iter_flag_keys()),
939            Item::TraitTrack => Box::new(self.traits.iter_track_keys()),
940            _ => Box::new(self.database.iter_keys(itype)),
941        }
942    }
943
944    #[cfg(feature = "vic3")]
945    fn iter_keys_vic3<'a>(&'a self, itype: Item) -> Box<dyn Iterator<Item = &'a Token> + 'a> {
946        Box::new(self.database.iter_keys(itype))
947    }
948
949    #[cfg(feature = "imperator")]
950    fn iter_keys_imperator<'a>(&'a self, itype: Item) -> Box<dyn Iterator<Item = &'a Token> + 'a> {
951        match itype {
952            Item::Decision => Box::new(self.decisions_imperator.iter_keys()),
953            Item::Province => Box::new(self.provinces_imperator.iter_keys()),
954            _ => Box::new(self.database.iter_keys(itype)),
955        }
956    }
957
958    pub fn iter_keys<'a>(&'a self, itype: Item) -> Box<dyn Iterator<Item = &'a Token> + 'a> {
959        match itype {
960            Item::Asset => Box::new(self.assets.iter_asset_keys()),
961            Item::BlendShape => Box::new(self.assets.iter_blend_shape_keys()),
962            Item::Coa => Box::new(self.coas.iter_keys()),
963            Item::CoaTemplate => Box::new(self.coas.iter_template_keys()),
964            Item::Define => Box::new(self.defines.iter_keys()),
965            Item::Entity => Box::new(self.assets.iter_entity_keys()),
966            Item::Event => Box::new(self.events.iter_keys()),
967            Item::EventNamespace => Box::new(self.events.iter_namespace_keys()),
968            Item::File => Box::new(self.fileset.iter_keys()),
969            Item::GeneAttribute => Box::new(self.assets.iter_attribute_keys()),
970            Item::GuiLayer => Box::new(self.gui.iter_layer_keys()),
971            Item::GuiTemplate => Box::new(self.gui.iter_template_keys()),
972            Item::GuiType => Box::new(self.gui.iter_type_keys()),
973            Item::Localization => Box::new(self.localization.iter_keys()),
974            Item::Music => Box::new(self.music.iter_keys()),
975            Item::OnAction => Box::new(self.on_actions.iter_keys()),
976            Item::Pdxmesh => Box::new(self.assets.iter_mesh_keys()),
977            Item::ScriptedEffect => Box::new(self.effects.iter_keys()),
978            Item::ScriptedList => Box::new(self.scripted_lists.iter_keys()),
979            Item::ScriptedModifier => Box::new(self.scripted_modifiers.iter_keys()),
980            Item::ScriptedTrigger => Box::new(self.triggers.iter_keys()),
981            Item::ScriptValue => Box::new(self.script_values.iter_keys()),
982            Item::TextFormat => Box::new(self.gui.iter_textformat_keys()),
983            Item::TextIcon => Box::new(self.gui.iter_texticon_keys()),
984            Item::TextureFile => Box::new(self.assets.iter_texture_keys()),
985            Item::WidgetName => Box::new(self.gui.iter_names()),
986            _ => match Game::game() {
987                #[cfg(feature = "ck3")]
988                Game::Ck3 => self.iter_keys_ck3(itype),
989                #[cfg(feature = "vic3")]
990                Game::Vic3 => self.iter_keys_vic3(itype),
991                #[cfg(feature = "imperator")]
992                Game::Imperator => self.iter_keys_imperator(itype),
993            },
994        }
995    }
996
997    fn valid_sound(&self, name: &str) -> bool {
998        // TODO: verify that file:/ values work
999        if let Some(filename) = name.strip_prefix("file:/") {
1000            self.fileset.exists(filename)
1001        } else {
1002            let sounds_set = match Game::game() {
1003                #[cfg(feature = "ck3")]
1004                Game::Ck3 => &crate::ck3::tables::sounds::SOUNDS_SET,
1005                #[cfg(feature = "vic3")]
1006                Game::Vic3 => &crate::vic3::tables::sounds::SOUNDS_SET,
1007                #[cfg(feature = "imperator")]
1008                Game::Imperator => &crate::imperator::tables::sounds::SOUNDS_SET,
1009            };
1010            sounds_set.contains(&Lowercase::new(name))
1011        }
1012    }
1013}
1014
1015impl Drop for Everything {
1016    fn drop(&mut self) {
1017        // For the sake of the benchmark code, restore MACRO_MAP to a clean slate
1018        MACRO_MAP.clear();
1019    }
1020}