tiger_lib/ck3/data/
religions.rs

1use crate::block::{BV, Block};
2use crate::ck3::validate::validate_traits;
3use crate::context::ScopeContext;
4use crate::db::{Db, DbKind};
5use crate::everything::Everything;
6use crate::fileset::FileKind;
7use crate::game::GameFlags;
8use crate::helpers::TigerHashMap;
9use crate::item::{Item, ItemLoader};
10use crate::report::{ErrorKey, err, warn};
11use crate::scopes::Scopes;
12use crate::token::Token;
13use crate::tooltipped::Tooltipped;
14use crate::validate::validate_possibly_named_color;
15use crate::validator::Validator;
16
17#[derive(Clone, Debug)]
18pub struct Religion {}
19
20inventory::submit! {
21    ItemLoader::Normal(GameFlags::Ck3, Item::Religion, Religion::add)
22}
23
24impl Religion {
25    pub fn add(db: &mut Db, key: Token, block: Block) {
26        db.add(Item::Religion, key, block, Box::new(Self {}));
27    }
28}
29
30impl DbKind for Religion {
31    fn add_subitems(&self, key: &Token, block: &Block, db: &mut Db) {
32        if let Some(block) = block.get_field_block("faiths") {
33            for (faith, block) in block.iter_definitions() {
34                if let Some(token) = block.get_field_value("graphical_faith") {
35                    db.add_flag(Item::GraphicalFaith, token.clone());
36                }
37                if let Some(token) = block.get_field_value("icon") {
38                    db.add_flag(Item::FaithIcon, token.clone());
39                } else {
40                    db.add_flag(Item::FaithIcon, faith.clone());
41                }
42                if let Some(token) = block.get_field_value("reformed_icon") {
43                    db.add_flag(Item::FaithIcon, token.clone());
44                }
45                let kind = Box::new(Faith { religion: key.clone() });
46                db.add(Item::Faith, faith.clone(), block.clone(), kind);
47            }
48        }
49        if let Some(block) = block.get_field_block("custom_faith_icons") {
50            for token in block.iter_values() {
51                db.add_flag(Item::FaithIcon, token.clone());
52            }
53        }
54        if let Some(token) = block.get_field_value("graphical_faith") {
55            db.add_flag(Item::GraphicalFaith, token.clone());
56        }
57    }
58
59    fn validate(&self, key: &Token, block: &Block, data: &Everything) {
60        data.verify_exists(Item::Localization, key);
61        let loca = format!("{key}_adj");
62        data.verify_exists_implied(Item::Localization, &loca, key);
63        let loca = format!("{key}_adherent");
64        data.verify_exists_implied(Item::Localization, &loca, key);
65        let loca = format!("{key}_adherent_plural");
66        data.verify_exists_implied(Item::Localization, &loca, key);
67        let loca = format!("{key}_desc");
68        data.verify_exists_implied(Item::Localization, &loca, key);
69
70        // let modif = format!("{key}_opinion");
71        // data.verify_exists_implied(Item::ModifierFormat, &modif, key);
72
73        let mut vd = Validator::new(block, data);
74
75        vd.req_field("family");
76        vd.field_item("family", Item::ReligionFamily);
77
78        validate_doctrines("religion", data, &mut vd);
79
80        vd.field_icon("doctrine_background_icon", "NGameIcons|FAITH_DOCTRINE_BACKGROUND_PATH", "");
81        validate_piety_icon_group(vd.field_value("piety_icon_group"), data);
82        vd.field_value("graphical_faith");
83        vd.field_bool("pagan_roots");
84        vd.field_validated_block("traits", validate_traits);
85
86        vd.field_validated_list("custom_faith_icons", |icon, data| {
87            data.verify_icon("NGameIcons|FAITH_ICON_PATH", icon, ".dds");
88        });
89
90        vd.field_list("reserved_male_names"); // TODO
91        vd.field_list("reserved_female_names"); // TODO
92
93        vd.field_validated_block("holy_order_names", validate_holy_order_names);
94        vd.field_list_items("holy_order_maa", Item::MenAtArms);
95        vd.field_validated_block("localization", validate_localization);
96        vd.field_validated_block("faiths", |block, data| {
97            let mut vd = Validator::new(block, data);
98            vd.unknown_block_fields(|_, _| ()); // validated by Faith class
99        });
100    }
101
102    fn has_property(
103        &self,
104        _key: &Token,
105        block: &Block,
106        property: &str,
107        _data: &Everything,
108    ) -> bool {
109        if let Some(block) = block.get_field_block("localization") {
110            block.has_key(property)
111        } else {
112            false
113        }
114    }
115}
116
117fn validate_doctrines(iname: &str, data: &Everything, vd: &mut Validator) {
118    let mut categories: TigerHashMap<&str, Vec<Token>> = TigerHashMap::default();
119    vd.multi_field_validated_value("doctrine", |_, mut vd| {
120        vd.item(Item::Doctrine);
121        if let Some(category) = data.doctrines.category(vd.value().as_str()) {
122            let doctrine = vd.value();
123            if let Some(seen) = categories.get_mut(category.as_str()) {
124                let picks_token = data.doctrines.number_of_picks(category.as_str());
125                let picks = picks_token.and_then(Token::get_integer).unwrap_or(1);
126                #[allow(clippy::cast_possible_wrap)]
127                if let Some(other_doctrine) = seen.iter().find(|&d| d == doctrine) {
128                    let msg = format!("duplicate doctrine {doctrine}");
129                    warn(ErrorKey::DuplicateField)
130                        .msg(msg)
131                        .loc(doctrine)
132                        .loc_msg(other_doctrine, "earlier doctrine")
133                        .push();
134                } else if picks == 1 {
135                    // SAFETY: we never push empty vecs into this hash
136                    let other_doctrine = &seen[0];
137                    let msg = format!("{doctrine} and {other_doctrine} are both from {category}");
138                    let info = format!("{category} only allows 1 pick");
139                    err(ErrorKey::Conflict)
140                        .msg(msg)
141                        .info(info)
142                        .loc(doctrine)
143                        .loc_msg(other_doctrine, "earlier doctrine")
144                        .push();
145                } else if picks == (seen.len() as i64) {
146                    let msg = format!("{iname} has more than {picks} doctrines from {category}");
147                    // SAFETY: picks_token can be unwrapped because Some(Token) is the only
148                    // way to get picks > 1
149                    err(ErrorKey::Conflict)
150                        .msg(msg)
151                        .loc(doctrine)
152                        .loc_msg(picks_token.unwrap(), "picks")
153                        .push();
154                }
155                seen.push(doctrine.clone());
156            } else {
157                categories.insert(category.as_str(), vec![doctrine.clone()]);
158            }
159        }
160    });
161
162    vd.multi_field_validated_block("doctrine_selection_pair", |block, data| {
163        let mut vd = Validator::new(block, data);
164        vd.field_item("requires_dlc_flag", Item::DlcFeature);
165        vd.field_item("doctrine", Item::Doctrine);
166        vd.field_item("fallback_doctrine", Item::Doctrine);
167    });
168}
169
170fn validate_localization(block: &Block, data: &Everything) {
171    let mut vd = Validator::new(block, data);
172    for field in CUSTOM_RELIGION_LOCAS {
173        vd.field_validated(field, |bv, data| match bv {
174            BV::Value(token) => data.verify_exists(Item::Localization, token),
175            BV::Block(block) => {
176                let mut vd = Validator::new(block, data);
177                for token in vd.values() {
178                    data.verify_exists(Item::Localization, token);
179                }
180            }
181        });
182    }
183}
184
185fn validate_holy_order_names(block: &Block, data: &Everything) {
186    let mut vd = Validator::new(block, data);
187    for block in vd.blocks() {
188        let mut vd = Validator::new(block, data);
189        vd.req_field("name");
190        vd.field_item("name", Item::Localization);
191        vd.field_item("coat_of_arms", Item::Coa);
192    }
193}
194
195/// Loaded via [`Religion`]
196#[derive(Clone, Debug)]
197pub struct Faith {
198    religion: Token,
199}
200
201impl Faith {
202    fn check_have_customs(&self, key: &Token, block: &Block, data: &Everything) {
203        let locas = block.get_field_block("localization");
204        for loca in CUSTOM_RELIGION_LOCAS {
205            if let Some(block) = locas {
206                if block.has_key(loca) {
207                    continue;
208                }
209            }
210            if data.item_has_property(Item::Religion, self.religion.as_str(), loca) {
211                continue;
212            }
213            let msg = format!("faith or religion missing localization for {loca}");
214            warn(ErrorKey::MissingLocalization).msg(msg).loc(key).push();
215        }
216    }
217}
218
219impl DbKind for Faith {
220    fn validate(&self, key: &Token, block: &Block, data: &Everything) {
221        data.verify_exists(Item::Localization, key);
222        let loca = format!("{key}_adj");
223        data.verify_exists_implied(Item::Localization, &loca, key);
224        let loca = format!("{key}_adherent");
225        data.verify_exists_implied(Item::Localization, &loca, key);
226        let loca = format!("{key}_adherent_plural");
227        data.verify_exists_implied(Item::Localization, &loca, key);
228        let loca = format!("{key}_desc");
229        data.verify_exists_implied(Item::Localization, &loca, key);
230
231        let pagan = block
232            .get_field_values("doctrine")
233            .iter()
234            .any(|value| data.doctrines.unreformed(value.as_str()));
235        if pagan {
236            let loca = format!("{key}_old");
237            data.verify_exists_implied(Item::Localization, &loca, key);
238            let loca = format!("{key}_old_adj");
239            data.verify_exists_implied(Item::Localization, &loca, key);
240            let loca = format!("{key}_old_adherent");
241            data.verify_exists_implied(Item::Localization, &loca, key);
242            let loca = format!("{key}_old_adherent_plural");
243            data.verify_exists_implied(Item::Localization, &loca, key);
244        }
245
246        // let modif = format!("{key}_opinion");
247        // data.verify_exists_implied(Item::ModifierFormat, &modif, key);
248
249        let mut vd = Validator::new(block, data);
250
251        vd.req_field("color");
252        vd.field_validated("color", validate_possibly_named_color);
253
254        let icon = vd.field_value("icon").unwrap_or(key);
255        data.verify_icon("NGameIcons|FAITH_ICON_PATH", icon, ".dds");
256        if pagan {
257            vd.req_field_fatal("reformed_icon");
258        } else {
259            vd.ban_field("reformed_icon", || "unreformed faiths");
260        }
261        vd.field_icon("reformed_icon", "NGameIcons|FAITH_ICON_PATH", ".dds");
262        vd.field_value("graphical_faith");
263        validate_piety_icon_group(vd.field_value("piety_icon_group"), data);
264
265        vd.field_icon("doctrine_background_icon", "NGameIcons|FAITH_DOCTRINE_BACKGROUND_PATH", "");
266
267        vd.field_item("religious_head", Item::Title);
268        vd.req_field("holy_site");
269        vd.multi_field_item("holy_site", Item::HolySite);
270        validate_doctrines("faith", data, &mut vd);
271
272        vd.field_list("reserved_male_names");
273        vd.field_list("reserved_female_names");
274        vd.field_validated_block("localization", validate_localization);
275        vd.field_validated_block("holy_order_names", validate_holy_order_names);
276        vd.field_list_items("holy_order_maa", Item::MenAtArms); // TODO: verify this is allowed
277
278        self.check_have_customs(key, block, data);
279    }
280
281    fn has_property(
282        &self,
283        key: &Token,
284        _block: &Block,
285        property: &str,
286        _data: &Everything,
287    ) -> bool {
288        if property == "is_modded" {
289            return key.loc.kind == FileKind::Mod;
290        }
291        false
292    }
293}
294
295#[derive(Clone, Debug)]
296pub struct ReligionFamily {}
297
298inventory::submit! {
299    ItemLoader::Normal(GameFlags::Ck3, Item::ReligionFamily, ReligionFamily::add)
300}
301
302impl ReligionFamily {
303    pub fn add(db: &mut Db, key: Token, block: Block) {
304        db.add(Item::ReligionFamily, key, block, Box::new(Self {}));
305    }
306}
307
308impl DbKind for ReligionFamily {
309    fn add_subitems(&self, _key: &Token, block: &Block, db: &mut Db) {
310        if let Some(token) = block.get_field_value("graphical_faith") {
311            db.add_flag(Item::GraphicalFaith, token.clone());
312        }
313    }
314
315    fn validate(&self, key: &Token, block: &Block, data: &Everything) {
316        let mut vd = Validator::new(block, data);
317
318        // let modif = format!("{key}_opinion");
319        // data.verify_exists_implied(Item::ModifierFormat, &modif, key);
320
321        let name = vd.field_value("name").unwrap_or(key);
322        data.verify_exists(Item::Localization, name);
323        let loca = format!("{name}_desc");
324        data.verify_exists_implied(Item::Localization, &loca, name);
325
326        vd.field_bool("is_pagan");
327        vd.field_value("graphical_faith");
328        validate_piety_icon_group(vd.field_value("piety_icon_group"), data);
329        vd.field_icon("doctrine_background_icon", "NGameIcons|FAITH_DOCTRINE_BACKGROUND_PATH", "");
330        vd.field_item("hostility_doctrine", Item::Doctrine);
331    }
332}
333
334#[derive(Clone, Debug)]
335pub struct FervorModifier {}
336
337inventory::submit! {
338    ItemLoader::Normal(GameFlags::Ck3, Item::FervorModifier, FervorModifier::add)
339}
340
341impl FervorModifier {
342    pub fn add(db: &mut Db, key: Token, block: Block) {
343        db.add(Item::FervorModifier, key, block, Box::new(Self {}));
344    }
345}
346
347impl DbKind for FervorModifier {
348    fn validate(&self, key: &Token, block: &Block, data: &Everything) {
349        let mut vd = Validator::new(block, data);
350        let mut sc = ScopeContext::new(Scopes::Faith, key);
351        vd.field_script_value("value", &mut sc);
352        vd.field_trigger("trigger", Tooltipped::No, &mut sc);
353    }
354}
355
356fn validate_piety_icon_group(group: Option<&Token>, data: &Everything) {
357    if let Some(group) = group {
358        if let Some(valid) = data.get_defined_array_warn(group, "NGameIcons|PIETY_GROUPS") {
359            for valid_group in valid.iter_values() {
360                if group == valid_group {
361                    return;
362                }
363            }
364        }
365        let msg = "piety icon group not listed in PIETY_GROUPS define";
366        warn(ErrorKey::Choice).msg(msg).loc(group).push();
367    }
368}
369
370// LAST UPDATED CK3 VERSION 1.12.1
371// Taken from the Faith.random_ values in `data_types_uncategorized.txt`
372pub const CUSTOM_RELIGION_LOCAS: &[&str] = &[
373    "AltPriestTermPlural",
374    "BishopFemale",
375    "BishopFemalePlural",
376    "BishopMale",
377    "BishopMalePlural",
378    "BishopNeuter",
379    "BishopNeuterPlural",
380    "CreatorHerHim",
381    "CreatorHerHis",
382    "CreatorName",
383    "CreatorNamePossessive",
384    "CreatorSheHe",
385    "DeathDeityHerHim",
386    "DeathDeityHerHis",
387    "DeathDeityName",
388    "DeathDeityNamePossessive",
389    "DeathDeitySheHe",
390    "DevilHerHis",
391    "DevilHerselfHimself",
392    "DevilName",
393    "DevilNamePossessive",
394    "DevilSheHe",
395    "DevoteeFemale",
396    "DevoteeFemalePlural",
397    "DevoteeMale",
398    "DevoteeMalePlural",
399    "DevoteeNeuter",
400    "DevoteeNeuterPlural",
401    "DivineRealm",
402    "DivineRealm2",
403    "DivineRealm3",
404    "EvilGodNames",
405    "FateGodHerHim",
406    "FateGodHerHis",
407    "FateGodName",
408    "FateGodNamePossessive",
409    "FateGodSheHe",
410    "FertilityGodHerHim",
411    "FertilityGodHerHis",
412    "FertilityGodName",
413    "FertilityGodNamePossessive",
414    "FertilityGodSheHe",
415    "GHWName",
416    "GHWNamePlural",
417    "GoodGodNames",
418    "HealthGodHerHim",
419    "HealthGodHerHis",
420    "HealthGodName",
421    "HealthGodNamePossessive",
422    "HealthGodSheHe",
423    "HighGodHerHis",
424    "HighGodHerselfHimself",
425    "HighGodName",
426    "HighGodName2",
427    "HighGodNameAlternate",
428    "HighGodNameAlternatePossessive",
429    "HighGodNamePossessive",
430    "HighGodNameSheHe",
431    "HouseOfWorship",
432    "HouseOfWorship2",
433    "HouseOfWorship3",
434    "HouseOfWorshipPlural",
435    "HouseholdGodHerHim",
436    "HouseholdGodHerHis",
437    "HouseholdGodName",
438    "HouseholdGodNamePossessive",
439    "HouseholdGodSheHe",
440    "KnowledgeGodHerHim",
441    "KnowledgeGodHerHis",
442    "KnowledgeGodName",
443    "KnowledgeGodNamePossessive",
444    "KnowledgeGodSheHe",
445    "NegativeAfterLife",
446    "NegativeAfterLife2",
447    "NegativeAfterLife3",
448    "NightGodHerHim",
449    "NightGodHerHis",
450    "NightGodName",
451    "NightGodNamePossessive",
452    "NightGodSheHe",
453    "PantheonTerm",
454    "PantheonTerm2",
455    "PantheonTerm3",
456    "PantheonTermHasHave",
457    "PositiveAfterLife",
458    "PositiveAfterLife2",
459    "PositiveAfterLife3",
460    "PriestFemale",
461    "PriestFemalePlural",
462    "PriestMale",
463    "PriestMalePlural",
464    "PriestNeuter",
465    "PriestNeuterPlural",
466    "ReligiousHeadName",
467    "ReligiousHeadTitleName",
468    "ReligiousSymbol",
469    "ReligiousSymbol2",
470    "ReligiousSymbol3",
471    "ReligiousText",
472    "ReligiousText2",
473    "ReligiousText3",
474    "TricksterGodHerHim",
475    "TricksterGodHerHis",
476    "TricksterGodName",
477    "TricksterGodNamePossessive",
478    "TricksterGodSheHe",
479    "WarGodHerHim",
480    "WarGodHerHis",
481    "WarGodName",
482    "WarGodNamePossessive",
483    "WarGodSheHe",
484    "WaterGodHerHim",
485    "WaterGodHerHis",
486    "WaterGodName",
487    "WaterGodNamePossessive",
488    "WaterGodSheHe",
489    "WealthGodHerHim",
490    "WealthGodHerHis",
491    "WealthGodName",
492    "WealthGodNamePossessive",
493    "WealthGodSheHe",
494    "WitchGodHerHim",
495    "WitchGodHerHis",
496    "WitchGodMistressMaster",
497    "WitchGodMotherFather",
498    "WitchGodName",
499    "WitchGodNamePossessive",
500    "WitchGodSheHe",
501];