1use crate::block::Block;
2use crate::ck3::modif::ModifKinds;
3use crate::ck3::validate::{validate_cost, validate_maa_stats};
4use crate::context::ScopeContext;
5use crate::db::{Db, DbKind};
6use crate::desc::validate_desc;
7use crate::everything::Everything;
8use crate::game::GameFlags;
9use crate::item::{Item, ItemLoader};
10use crate::modif::validate_modifs;
11use crate::report::{ErrorKey, err};
12use crate::scopes::Scopes;
13use crate::token::Token;
14use crate::tooltipped::Tooltipped;
15use crate::validate::validate_possibly_named_color;
16use crate::validator::{Validator, ValueValidator};
17
18#[derive(Clone, Debug)]
19pub struct CultureEra {}
20
21inventory::submit! {
22 ItemLoader::Normal(GameFlags::Ck3, Item::CultureEra, CultureEra::add)
23}
24
25impl CultureEra {
26 pub fn add(db: &mut Db, key: Token, block: Block) {
27 db.add(Item::CultureEra, key, block, Box::new(Self {}));
28 }
29}
30
31impl DbKind for CultureEra {
32 fn validate(&self, key: &Token, block: &Block, data: &Everything) {
33 let mut vd = Validator::new(block, data);
34
35 vd.req_field("year");
36 vd.field_integer("year");
37
38 data.verify_exists(Item::Localization, key);
39 let loca = format!("{key}_desc");
40 data.verify_exists_implied(Item::Localization, &loca, key);
41
42 vd.field_item("invalid_for_government", Item::GovernmentType);
43 vd.multi_field_item("custom", Item::Localization);
44
45 validate_modifiers(&mut vd);
46
47 vd.multi_field_validated_block("maa_upgrade", |block, data| {
48 let mut vd = Validator::new(block, data);
49 vd.field_item("men_at_arms", Item::MenAtArms);
50 validate_maa_stats(&mut vd);
51 });
52
53 vd.multi_field_item("unlock_building", Item::Building);
54 vd.multi_field_item("unlock_decision", Item::Decision);
55 vd.multi_field_item("unlock_casus_belli", Item::CasusBelli);
56 vd.multi_field_item("unlock_maa", Item::MenAtArms);
57 vd.multi_field_item("unlock_law", Item::Law);
58 }
59
60 }
63
64#[derive(Clone, Debug)]
65pub struct Culture {}
66
67inventory::submit! {
68 ItemLoader::Normal(GameFlags::Ck3, Item::Culture, Culture::add)
69}
70
71impl Culture {
72 pub fn add(db: &mut Db, key: Token, block: Block) {
73 db.add(Item::Culture, key, block, Box::new(Self {}));
74 }
75}
76
77impl DbKind for Culture {
78 fn add_subitems(&self, _key: &Token, block: &Block, db: &mut Db) {
79 if let Some(list) = block.get_field_list("coa_gfx") {
80 for token in list {
81 db.add_flag(Item::CoaGfx, token);
82 }
83 }
84 if let Some(list) = block.get_field_list("building_gfx") {
85 for token in list {
86 db.add_flag(Item::BuildingGfx, token);
87 }
88 }
89 if let Some(list) = block.get_field_list("clothing_gfx") {
90 for token in list {
91 db.add_flag(Item::ClothingGfx, token);
92 }
93 }
94 if let Some(list) = block.get_field_list("unit_gfx") {
95 for token in list {
96 db.add_flag(Item::UnitGfx, token);
97 }
98 }
99 }
100
101 fn validate(&self, key: &Token, block: &Block, data: &Everything) {
102 let mut vd = Validator::new(block, data);
103
104 data.verify_exists(Item::Localization, key);
108 let loca = format!("{key}_prefix");
109 data.verify_exists_implied(Item::Localization, &loca, key);
110 let loca = format!("{key}_collective_noun");
111 data.verify_exists_implied(Item::Localization, &loca, key);
112
113 vd.field_date("created");
114 vd.field_list_items("parents", Item::Culture);
115
116 vd.field_validated("color", validate_possibly_named_color);
117
118 vd.field_item("ethos", Item::CultureEthos);
119 vd.field_item("heritage", Item::CultureHeritage);
120 vd.field_item("language", Item::Language);
121 vd.field_item("martial_custom", Item::MartialCustom);
122 vd.field_item("head_determination", Item::HeadDetermination);
123
124 vd.field_list_items("traditions", Item::CultureTradition);
125 vd.field_value("name_order_convention");
126 vd.multi_field_item("name_list", Item::NameList);
127
128 vd.multi_field_list_items("coa_gfx", Item::Localization);
129 vd.field_list_items("building_gfx", Item::Localization);
130 vd.multi_field_list_items("clothing_gfx", Item::Localization);
131 vd.field_list_items("unit_gfx", Item::Localization);
132
133 for field in &["house_coa_frame", "dynasty_coa_frame"] {
134 if let Some(token) = vd.field_value(field) {
135 let pathname = format!("gfx/interface/coat_of_arms/frames/{token}.dds");
136 data.verify_exists_implied(Item::File, &pathname, token);
137 let pathname = format!("gfx/interface/coat_of_arms/frames/{token}_mask.dds");
138 data.verify_exists_implied(Item::File, &pathname, token);
139 }
140 }
141 vd.field_list_numeric_exactly("house_coa_mask_offset", 2);
142 vd.field_list_numeric_exactly("house_coa_mask_scale", 2);
143
144 vd.field_validated_block("ethnicities", |block, data| {
145 let mut vd = Validator::new(block, data);
146 for (_, value) in vd.integer_values() {
147 data.verify_exists(Item::Ethnicity, value);
148 }
149 });
150
151 vd.multi_field_validated_block("dlc_tradition", |block, data| {
152 let mut vd = Validator::new(block, data);
153 vd.req_field("trait");
154 vd.req_field("requires_dlc_flag");
155 vd.field_item("trait", Item::CultureTradition);
156 vd.field_item("requires_dlc_flag", Item::DlcFeature);
157 vd.field_item("fallback", Item::CultureTradition);
158 });
159
160 vd.field_item("history_loc_override", Item::Localization);
161 }
162}
163
164#[derive(Clone, Debug)]
165pub struct CulturePillar {}
166
167inventory::submit! {
168 ItemLoader::Normal(GameFlags::Ck3, Item::CulturePillar, CulturePillar::add)
169}
170
171impl CulturePillar {
172 pub fn add(db: &mut Db, key: Token, block: Block) {
173 db.add(Item::CulturePillar, key, block, Box::new(Self {}));
174 }
175}
176
177impl DbKind for CulturePillar {
178 fn add_subitems(&self, key: &Token, block: &Block, db: &mut Db) {
179 if let Some(block) = block.get_field_block("parameters") {
180 for (key, value) in block.iter_assignments() {
181 if value.is("yes") {
182 db.add_flag(Item::CultureParameter, key.clone());
183 }
184 }
185 }
186 if let Some(pillar) = block.get_field_value("type") {
187 if pillar.is("language") {
188 db.add_flag(Item::Language, key.clone());
189 } else if pillar.is("ethos") {
190 db.add_flag(Item::CultureEthos, key.clone());
191 } else if pillar.is("heritage") {
192 db.add_flag(Item::CultureHeritage, key.clone());
193 } else if pillar.is("martial_custom") {
194 db.add_flag(Item::MartialCustom, key.clone());
195 } else if pillar.is("head_determination") {
196 db.add_flag(Item::HeadDetermination, key.clone());
197 }
198 }
199 }
200
201 fn validate(&self, key: &Token, block: &Block, data: &Everything) {
202 let mut vd = Validator::new(block, data);
203 vd.field_choice(
204 "type",
205 &["ethos", "heritage", "language", "martial_custom", "head_determination"],
206 );
207 vd.field_item("name", Item::Localization);
208 if !block.has_key("name") {
209 let loca = format!("{key}_name");
210 data.verify_exists_implied(Item::Localization, &loca, key);
211 }
212 if block.field_value_is("type", "ethos") {
213 vd.field_item("desc", Item::Localization);
214 if !block.has_key("desc") {
215 let loca = format!("{key}_desc");
216 data.verify_exists_implied(Item::Localization, &loca, key);
217 }
218 } else if block.field_value_is("type", "heritage") {
219 let loca = format!("{key}_collective_noun");
220 data.verify_exists_implied(Item::Localization, &loca, key);
221 }
222
223 validate_modifiers(&mut vd);
224
225 let mut sc = ScopeContext::new(Scopes::Culture, key);
226 sc.define_name("character", Scopes::Character, key);
227 sc.define_list("traits", Scopes::CulturePillar | Scopes::CultureTradition, key); vd.field_script_value("ai_will_do", &mut sc);
229 vd.field_trigger("is_shown", Tooltipped::No, &mut sc);
230 vd.field_trigger("can_pick", Tooltipped::Yes, &mut sc);
231 if block.field_value_is("type", "language") {
232 vd.field_validated("color", validate_possibly_named_color);
233 } else {
234 vd.ban_field("color", || "languages");
235 }
236 if block.field_value_is("type", "heritage") {
237 vd.field_value("audio_parameter");
238 } else {
239 vd.ban_field("audio_parameter", || "heritages");
240 }
241 if block.field_value_is("type", "head_determination") {
242 vd.field_value("head_determination_type");
243 } else {
244 vd.ban_field("head_determination_type", || "head_determination");
245 }
246 vd.field_validated_block("parameters", validate_parameters);
247 }
248}
249
250#[derive(Clone, Debug)]
251pub struct CultureTradition {}
252
253inventory::submit! {
254 ItemLoader::Normal(GameFlags::Ck3, Item::CultureTradition, CultureTradition::add)
255}
256
257impl CultureTradition {
258 pub fn add(db: &mut Db, key: Token, block: Block) {
259 db.add(Item::CultureTradition, key, block, Box::new(Self {}));
260 }
261}
262
263impl DbKind for CultureTradition {
264 fn add_subitems(&self, _key: &Token, block: &Block, db: &mut Db) {
265 if let Some(block) = block.get_field_block("parameters") {
266 for (key, value) in block.iter_assignments() {
267 if value.is("yes") {
268 db.add_flag(Item::CultureParameter, key.clone());
269 }
270 }
271 }
272 if let Some(value) = block.get_field_value("category") {
273 db.add_flag(Item::CultureTraditionCategory, value.clone());
274 }
275 }
276
277 fn validate(&self, key: &Token, block: &Block, data: &Everything) {
278 let mut vd = Validator::new(block, data);
279 vd.field_item("name", Item::Localization);
280 if !block.has_key("name") {
281 let loca = format!("{key}_name");
282 data.verify_exists_implied(Item::Localization, &loca, key);
283 }
284 vd.field_item("desc", Item::Localization);
285 if !block.has_key("desc") {
286 let loca = format!("{key}_desc");
287 data.verify_exists_implied(Item::Localization, &loca, key);
288 }
289 vd.field_validated_block("parameters", validate_parameters);
290 vd.field_value("category");
291 vd.field_validated_block("layers", |block, data| {
292 let mut layer_path = Vec::new();
293 if let Some(block) =
294 data.get_defined_array_warn(key, "NGraphics|CULTURE_TRADITION_LAYER_PATHS")
295 {
296 for path in block.iter_values_warn() {
297 layer_path.push(path.as_str());
298 }
299 }
300
301 let mut vd = Validator::new(block, data);
302 vd.unknown_value_fields(|key, value| {
303 if let Some(layer_idx) =
304 key.expect_integer().and_then(|i| match usize::try_from(i) {
305 Ok(u) if u < layer_path.len() => Some(u),
306 _ => {
307 let msg = format!(
308 "layer index out of range between 0 and {}",
309 layer_path.len()
310 );
311 err(ErrorKey::Range).msg(msg).loc(key).push();
312 None
313 }
314 })
315 {
316 let loca = format!("{}/{}", layer_path[layer_idx], value);
317 data.verify_exists_implied(Item::Entry, &loca, value);
318 }
319 });
320 });
321
322 vd.field_trigger_builder("can_pick", Tooltipped::Yes, |key| {
323 let mut sc = ScopeContext::new(Scopes::Culture, key);
324 sc.define_name("replacing", Scopes::CultureTradition, key);
325 sc.define_name("character", Scopes::Character, key);
326 sc.define_list("traits", Scopes::CulturePillar | Scopes::CultureTradition, key); sc
328 });
329 vd.field_trigger_builder("can_pick_for_hybridization", Tooltipped::Yes, |key| {
330 let mut sc = ScopeContext::new(Scopes::Culture, key);
331 sc.define_name("character", Scopes::Character, key);
332 sc.define_list("traits", Scopes::CulturePillar | Scopes::CultureTradition, key); sc
334 });
335 validate_modifiers(&mut vd);
336 vd.multi_field_validated_block("doctrine_character_modifier", |block, data| {
337 let mut vd = Validator::new(block, data);
338 vd.field_item("doctrine", Item::Doctrine);
339 vd.field_item("name", Item::Localization);
340 validate_modifs(block, data, ModifKinds::Character, vd);
341 });
342 vd.field_validated_key_block("cost", |key, block, data| {
343 let mut sc = ScopeContext::new(Scopes::Culture, key);
344 sc.define_name("replacing", Scopes::CultureTradition, key);
345 sc.define_name("character", Scopes::Character, key);
346 sc.define_list("traits", Scopes::CulturePillar | Scopes::CultureTradition, key); validate_cost(block, data, &mut sc);
348 });
349 let mut sc = ScopeContext::new(Scopes::Culture, key);
350 sc.define_name("character", Scopes::Character, key);
351 sc.define_list("traits", Scopes::CulturePillar | Scopes::CultureTradition, key); vd.field_trigger("is_shown", Tooltipped::No, &mut sc);
353 sc.define_name("replacing", Scopes::CultureTradition, key);
354 vd.field_script_value_no_breakdown("ai_will_do", &mut sc);
355 }
356}
357
358const INTEGER_PARAMETERS: &[&str] = &[
359 "number_of_spouses",
360 "number_of_consorts",
361 "number_of_consorts_barony",
362 "number_of_consorts_county",
363 "number_of_consorts_duchy",
364 "number_of_consorts_kingdom",
365 "number_of_consorts_empire",
366 "number_of_consorts_hegemony",
367];
368
369fn validate_parameters(block: &Block, data: &Everything) {
370 let mut vd = Validator::new(block, data);
371 vd.unknown_value_fields(|key, value| {
372 if INTEGER_PARAMETERS.contains(&key.as_str()) {
373 ValueValidator::new(value, data).integer_range(0..);
374 } else {
375 ValueValidator::new(value, data).bool();
376 }
377 let loca = format!("culture_parameter_{}", key.as_str().to_ascii_lowercase());
379 data.verify_exists_implied(Item::Localization, &loca, key);
380 });
381}
382
383fn validate_modifiers(vd: &mut Validator) {
384 vd.multi_field_validated_block("character_modifier", |block, data| {
385 let vd = Validator::new(block, data);
386 validate_modifs(block, data, ModifKinds::Character, vd);
387 });
388 vd.multi_field_validated_block("culture_modifier", |block, data| {
389 let vd = Validator::new(block, data);
390 validate_modifs(block, data, ModifKinds::Culture, vd);
391 });
392 vd.multi_field_validated_block("county_modifier", |block, data| {
393 let vd = Validator::new(block, data);
394 validate_modifs(block, data, ModifKinds::County, vd);
395 });
396 vd.multi_field_validated_block("province_modifier", |block, data| {
397 let vd = Validator::new(block, data);
398 validate_modifs(block, data, ModifKinds::Province, vd);
399 });
400}
401
402#[derive(Clone, Debug)]
403pub struct CultureAesthetic {}
404
405inventory::submit! {
406 ItemLoader::Normal(GameFlags::Ck3, Item::CultureAesthetic, CultureAesthetic::add)
407}
408
409impl CultureAesthetic {
410 pub fn add(db: &mut Db, key: Token, block: Block) {
411 db.add(Item::CultureAesthetic, key, block, Box::new(Self {}));
412 }
413}
414
415impl DbKind for CultureAesthetic {
416 fn validate(&self, key: &Token, block: &Block, data: &Everything) {
417 let mut vd = Validator::new(block, data);
418 let loca = format!("{key}_name");
419 data.verify_exists_implied(Item::Localization, &loca, key);
420
421 vd.field_item("name_list", Item::NameList);
422 vd.field_list_items("building_gfx", Item::BuildingGfx);
423 vd.field_list_items("clothing_gfx", Item::ClothingGfx);
424 vd.field_list_items("unit_gfx", Item::UnitGfx);
425 vd.field_list_items("coa_gfx", Item::CoaGfx);
426
427 vd.field_trigger_builder("is_shown", Tooltipped::No, |key| {
428 let mut sc = ScopeContext::new(Scopes::Culture, key);
429 sc.define_name("character", Scopes::Character, key);
430 sc.define_list("trait", Scopes::CultureTradition | Scopes::CulturePillar, key);
431 sc
432 });
433 }
434}
435
436#[derive(Clone, Debug)]
437pub struct CultureCreationName {}
438
439inventory::submit! {
440 ItemLoader::Normal(GameFlags::Ck3, Item::CultureCreationName, CultureCreationName::add)
441}
442
443impl CultureCreationName {
444 pub fn add(db: &mut Db, key: Token, block: Block) {
445 db.add(Item::CultureCreationName, key, block, Box::new(Self {}));
446 }
447}
448
449impl DbKind for CultureCreationName {
450 fn validate(&self, key: &Token, block: &Block, data: &Everything) {
451 let mut vd = Validator::new(block, data);
452 let mut sc = ScopeContext::new(Scopes::Character, key);
453 sc.define_name("culture", Scopes::Culture, key);
454 if block.field_value_is("hybrid", "yes") {
455 sc.define_name("other_culture", Scopes::Culture, key);
456 }
457
458 if !vd.field_validated_sc("name", &mut sc, validate_desc) {
459 let loca = format!("{key}_name");
460 data.verify_exists_implied(Item::Localization, &loca, key);
461 }
462
463 if !vd.field_validated_sc("collective_noun", &mut sc, validate_desc) {
464 let loca = format!("{key}_collective_noun");
465 data.verify_exists_implied(Item::Localization, &loca, key);
466 }
467
468 if !vd.field_validated_sc("prefix", &mut sc, validate_desc) {
469 let loca = format!("{key}_prefix"); data.verify_exists_implied(Item::Localization, &loca, key);
471 }
472
473 vd.field_trigger("trigger", Tooltipped::No, &mut sc);
474
475 vd.field_bool("hybrid");
476 }
477}
478
479#[derive(Clone, Debug)]
480pub struct NameEquivalency {}
481
482inventory::submit! {
483 ItemLoader::Normal(GameFlags::Ck3, Item::NameEquivalency, NameEquivalency::add)
484}
485
486impl NameEquivalency {
487 pub fn add(db: &mut Db, key: Token, block: Block) {
488 db.add(Item::NameEquivalency, key, block, Box::new(Self {}));
489 }
490}
491
492impl DbKind for NameEquivalency {
493 fn validate(&self, _key: &Token, block: &Block, data: &Everything) {
494 let mut vd = Validator::new(block, data);
495 for name in vd.values() {
496 data.verify_exists(Item::Localization, name);
497 }
498 }
499}