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 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"); vd.field_list("reserved_female_names"); 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(|_, _| ()); });
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 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 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#[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 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); 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 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
370pub 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];