Skip to main content

tiger_lib/ck3/data/
religions.rs

1use crate::block::{BV, Block};
2use crate::ck3::tables::misc::CUSTOM_RELIGION_LOCAS;
3use crate::ck3::validate::validate_traits;
4use crate::context::ScopeContext;
5use crate::db::{Db, DbKind};
6use crate::everything::Everything;
7use crate::fileset::FileKind;
8use crate::game::GameFlags;
9use crate::helpers::TigerHashMap;
10use crate::item::{Item, ItemLoader};
11use crate::report::{ErrorKey, err, warn};
12use crate::scopes::Scopes;
13use crate::token::Token;
14use crate::tooltipped::Tooltipped;
15use crate::validate::validate_possibly_named_color;
16use crate::validator::Validator;
17
18#[derive(Clone, Debug)]
19pub struct Religion {}
20
21inventory::submit! {
22    ItemLoader::Normal(GameFlags::Ck3, Item::Religion, Religion::add)
23}
24
25impl Religion {
26    pub fn add(db: &mut Db, key: Token, block: Block) {
27        db.add(Item::Religion, key, block, Box::new(Self {}));
28    }
29}
30
31impl DbKind for Religion {
32    fn add_subitems(&self, key: &Token, block: &Block, db: &mut Db) {
33        if let Some(block) = block.get_field_block("faiths") {
34            for (faith, block) in block.iter_definitions() {
35                if let Some(token) = block.get_field_value("graphical_faith") {
36                    db.add_flag(Item::GraphicalFaith, token.clone());
37                }
38                if let Some(token) = block.get_field_value("icon") {
39                    db.add_flag(Item::FaithIcon, token.clone());
40                } else {
41                    db.add_flag(Item::FaithIcon, faith.clone());
42                }
43                if let Some(token) = block.get_field_value("reformed_icon") {
44                    db.add_flag(Item::FaithIcon, token.clone());
45                }
46                let kind = Box::new(Faith { religion: key.clone() });
47                db.add(Item::Faith, faith.clone(), block.clone(), kind);
48            }
49        }
50        if let Some(block) = block.get_field_block("custom_faith_icons") {
51            for token in block.iter_values() {
52                db.add_flag(Item::FaithIcon, token.clone());
53            }
54        }
55        if let Some(token) = block.get_field_value("graphical_faith") {
56            db.add_flag(Item::GraphicalFaith, token.clone());
57        }
58    }
59
60    fn validate(&self, key: &Token, block: &Block, data: &Everything) {
61        data.verify_exists(Item::Localization, key);
62        let loca = format!("{key}_adj");
63        data.verify_exists_implied(Item::Localization, &loca, key);
64        let loca = format!("{key}_adherent");
65        data.verify_exists_implied(Item::Localization, &loca, key);
66        let loca = format!("{key}_adherent_plural");
67        data.verify_exists_implied(Item::Localization, &loca, key);
68        let loca = format!("{key}_desc");
69        data.verify_exists_implied(Item::Localization, &loca, key);
70
71        // let modif = format!("{key}_opinion");
72        // data.verify_exists_implied(Item::ModifierFormat, &modif, key);
73
74        let mut vd = Validator::new(block, data);
75
76        vd.req_field("family");
77        vd.field_item("family", Item::ReligionFamily);
78
79        validate_doctrines("religion", data, &mut vd);
80
81        vd.field_icon("doctrine_background_icon", "NGameIcons|FAITH_DOCTRINE_BACKGROUND_PATH", "");
82        validate_piety_icon_group(vd.field_value("piety_icon_group"), data);
83        vd.field_value("graphical_faith");
84        vd.field_bool("pagan_roots");
85        vd.field_validated_block("traits", validate_traits);
86
87        vd.field_validated_list("custom_faith_icons", |icon, data| {
88            data.verify_icon("NGameIcons|FAITH_ICON_PATH", icon, ".dds");
89        });
90
91        vd.field_list("reserved_male_names"); // TODO
92        vd.field_list("reserved_female_names"); // TODO
93
94        vd.field_validated_block("holy_order_names", validate_holy_order_names);
95        vd.field_list_items("holy_order_maa", Item::MenAtArms);
96        vd.field_validated_block("localization", validate_localization);
97        vd.field_validated_block("faiths", |block, data| {
98            let mut vd = Validator::new(block, data);
99            vd.unknown_block_fields(|_, _| ()); // validated by Faith class
100        });
101    }
102
103    fn has_property(
104        &self,
105        _key: &Token,
106        block: &Block,
107        property: &str,
108        _data: &Everything,
109    ) -> bool {
110        if let Some(block) = block.get_field_block("localization") {
111            block.has_key(property)
112        } else {
113            false
114        }
115    }
116}
117
118fn validate_doctrines(iname: &str, data: &Everything, vd: &mut Validator) {
119    // TODO: maybe cache doctrine_types and number_of_picks,
120    // though the cache might only make sense if done globally instead of per religion/faith.
121    let mut groups: TigerHashMap<&str, Vec<Token>> = TigerHashMap::default();
122    vd.multi_field_validated_value("doctrine", |_, mut vd| {
123        vd.item(Item::Doctrine);
124        let doctrine = vd.value();
125        for (group, block) in data.database.iter_key_block(Item::DoctrineGroup) {
126            if let Some(doctrines) = block.get_field_block("doctrine_types") {
127                if !doctrines.iter_values().any(|t| t == doctrine) {
128                    continue;
129                }
130            } else {
131                continue;
132            }
133
134            if let Some(seen) = groups.get_mut(group.as_str()) {
135                let picks_token = block.get_field_value("number_of_picks");
136                let picks = picks_token.and_then(Token::get_integer).unwrap_or(1);
137                #[allow(clippy::cast_possible_wrap)]
138                if let Some(other_doctrine) = seen.iter().find(|&d| d == doctrine) {
139                    let msg = format!("duplicate doctrine {doctrine}");
140                    warn(ErrorKey::DuplicateField)
141                        .msg(msg)
142                        .loc(doctrine)
143                        .loc_msg(other_doctrine, "earlier doctrine")
144                        .push();
145                } else if picks == 1 {
146                    // SAFETY: we never push empty vecs into this hash
147                    let other_doctrine = &seen[0];
148                    let msg = format!("{doctrine} and {other_doctrine} are both from {group}");
149                    let info = format!("{group} only allows 1 pick");
150                    err(ErrorKey::Conflict)
151                        .msg(msg)
152                        .info(info)
153                        .loc(doctrine)
154                        .loc_msg(other_doctrine, "earlier doctrine")
155                        .push();
156                } else if picks == (seen.len() as i64) {
157                    let msg = format!("{iname} has more than {picks} doctrines from {group}");
158                    // SAFETY: picks_token can be unwrapped because Some(Token) is the only
159                    // way to get picks > 1
160                    err(ErrorKey::Conflict)
161                        .msg(msg)
162                        .loc(doctrine)
163                        .loc_msg(picks_token.unwrap(), "picks")
164                        .push();
165                }
166                seen.push(doctrine.clone());
167            } else {
168                groups.insert(group.as_str(), vec![doctrine.clone()]);
169            }
170        }
171    });
172
173    vd.multi_field_validated_block("doctrine_selection_pair", |block, data| {
174        let mut vd = Validator::new(block, data);
175        vd.field_item("requires_dlc_flag", Item::DlcFeature);
176        vd.field_item("doctrine", Item::Doctrine);
177        vd.field_item("fallback_doctrine", Item::Doctrine);
178    });
179}
180
181fn validate_localization(block: &Block, data: &Everything) {
182    let mut vd = Validator::new(block, data);
183    for field in CUSTOM_RELIGION_LOCAS {
184        vd.field_validated(field, |bv, data| match bv {
185            BV::Value(token) => data.verify_exists(Item::Localization, token),
186            BV::Block(block) => {
187                let mut vd = Validator::new(block, data);
188                for token in vd.values() {
189                    data.verify_exists(Item::Localization, token);
190                }
191            }
192        });
193    }
194}
195
196fn validate_holy_order_names(block: &Block, data: &Everything) {
197    let mut vd = Validator::new(block, data);
198    for block in vd.blocks() {
199        let mut vd = Validator::new(block, data);
200        vd.req_field("name");
201        vd.field_item("name", Item::Localization);
202        vd.field_item("coat_of_arms", Item::Coa);
203    }
204}
205
206/// Loaded via [`Religion`]
207#[derive(Clone, Debug)]
208pub struct Faith {
209    religion: Token,
210}
211
212impl Faith {
213    fn check_have_customs(&self, key: &Token, block: &Block, data: &Everything) {
214        let locas = block.get_field_block("localization");
215        for loca in CUSTOM_RELIGION_LOCAS {
216            if let Some(block) = locas
217                && block.has_key(loca)
218            {
219                continue;
220            }
221            if data.item_has_property(Item::Religion, self.religion.as_str(), loca) {
222                continue;
223            }
224            let msg = format!("faith or religion missing localization for {loca}");
225            warn(ErrorKey::MissingLocalization).msg(msg).loc(key).push();
226        }
227    }
228}
229
230impl DbKind for Faith {
231    fn validate(&self, key: &Token, block: &Block, data: &Everything) {
232        data.verify_exists(Item::Localization, key);
233        let loca = format!("{key}_adj");
234        data.verify_exists_implied(Item::Localization, &loca, key);
235        let loca = format!("{key}_adherent");
236        data.verify_exists_implied(Item::Localization, &loca, key);
237        let loca = format!("{key}_adherent_plural");
238        data.verify_exists_implied(Item::Localization, &loca, key);
239        let loca = format!("{key}_desc");
240        data.verify_exists_implied(Item::Localization, &loca, key);
241
242        let pagan = block
243            .get_field_values("doctrine")
244            .iter()
245            .any(|value| data.item_has_property(Item::Doctrine, value.as_str(), "unreformed"));
246        if pagan {
247            let loca = format!("{key}_old");
248            data.verify_exists_implied(Item::Localization, &loca, key);
249            let loca = format!("{key}_old_adj");
250            data.verify_exists_implied(Item::Localization, &loca, key);
251            let loca = format!("{key}_old_adherent");
252            data.verify_exists_implied(Item::Localization, &loca, key);
253            let loca = format!("{key}_old_adherent_plural");
254            data.verify_exists_implied(Item::Localization, &loca, key);
255        }
256
257        // let modif = format!("{key}_opinion");
258        // data.verify_exists_implied(Item::ModifierFormat, &modif, key);
259
260        let mut vd = Validator::new(block, data);
261
262        vd.req_field("color");
263        vd.field_validated("color", validate_possibly_named_color);
264
265        let icon = vd.field_value("icon").unwrap_or(key);
266        data.verify_icon("NGameIcons|FAITH_ICON_PATH", icon, ".dds");
267        if pagan {
268            vd.req_field_fatal("reformed_icon");
269        } else {
270            vd.ban_field("reformed_icon", || "unreformed faiths");
271        }
272        vd.field_icon("reformed_icon", "NGameIcons|FAITH_ICON_PATH", ".dds");
273        vd.field_value("graphical_faith");
274        validate_piety_icon_group(vd.field_value("piety_icon_group"), data);
275
276        vd.field_icon("doctrine_background_icon", "NGameIcons|FAITH_DOCTRINE_BACKGROUND_PATH", "");
277
278        vd.field_item("religious_head", Item::Title);
279        vd.req_field("holy_site");
280        vd.multi_field_item("holy_site", Item::HolySite);
281        validate_doctrines("faith", data, &mut vd);
282
283        vd.field_list("reserved_male_names");
284        vd.field_list("reserved_female_names");
285        vd.field_validated_block("localization", validate_localization);
286        vd.field_validated_block("holy_order_names", validate_holy_order_names);
287        vd.field_list_items("holy_order_maa", Item::MenAtArms); // TODO: verify this is allowed
288
289        self.check_have_customs(key, block, data);
290    }
291
292    fn has_property(
293        &self,
294        key: &Token,
295        _block: &Block,
296        property: &str,
297        _data: &Everything,
298    ) -> bool {
299        if property == "is_modded" {
300            return key.loc.kind == FileKind::Mod;
301        }
302        false
303    }
304}
305
306#[derive(Clone, Debug)]
307pub struct ReligionFamily {}
308
309inventory::submit! {
310    ItemLoader::Normal(GameFlags::Ck3, Item::ReligionFamily, ReligionFamily::add)
311}
312
313impl ReligionFamily {
314    pub fn add(db: &mut Db, key: Token, block: Block) {
315        db.add(Item::ReligionFamily, key, block, Box::new(Self {}));
316    }
317}
318
319impl DbKind for ReligionFamily {
320    fn add_subitems(&self, _key: &Token, block: &Block, db: &mut Db) {
321        if let Some(token) = block.get_field_value("graphical_faith") {
322            db.add_flag(Item::GraphicalFaith, token.clone());
323        }
324    }
325
326    fn validate(&self, key: &Token, block: &Block, data: &Everything) {
327        let mut vd = Validator::new(block, data);
328
329        // let modif = format!("{key}_opinion");
330        // data.verify_exists_implied(Item::ModifierFormat, &modif, key);
331
332        let name = vd.field_value("name").unwrap_or(key);
333        data.verify_exists(Item::Localization, name);
334        let loca = format!("{name}_desc");
335        data.verify_exists_implied(Item::Localization, &loca, name);
336
337        vd.field_bool("is_pagan");
338        vd.field_value("graphical_faith");
339        validate_piety_icon_group(vd.field_value("piety_icon_group"), data);
340        vd.field_icon("doctrine_background_icon", "NGameIcons|FAITH_DOCTRINE_BACKGROUND_PATH", "");
341        vd.field_item("hostility_doctrine", Item::Doctrine);
342    }
343}
344
345#[derive(Clone, Debug)]
346pub struct FervorModifier {}
347
348inventory::submit! {
349    ItemLoader::Normal(GameFlags::Ck3, Item::FervorModifier, FervorModifier::add)
350}
351
352impl FervorModifier {
353    pub fn add(db: &mut Db, key: Token, block: Block) {
354        db.add(Item::FervorModifier, key, block, Box::new(Self {}));
355    }
356}
357
358impl DbKind for FervorModifier {
359    fn validate(&self, key: &Token, block: &Block, data: &Everything) {
360        let mut vd = Validator::new(block, data);
361        let mut sc = ScopeContext::new(Scopes::Faith, key);
362        vd.field_script_value("value", &mut sc);
363        vd.field_trigger("trigger", Tooltipped::No, &mut sc);
364    }
365}
366
367fn validate_piety_icon_group(group: Option<&Token>, data: &Everything) {
368    if let Some(group) = group {
369        if let Some(valid) = data.get_defined_array_warn(group, "NGameIcons|PIETY_GROUPS") {
370            for valid_group in valid.iter_values() {
371                if group == valid_group {
372                    return;
373                }
374            }
375        }
376        let msg = "piety icon group not listed in PIETY_GROUPS define";
377        warn(ErrorKey::Choice).msg(msg).loc(group).push();
378    }
379}