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