tiger_lib/ck3/data/
cultures.rs

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    // TODO: validate that none have the same year
61    // If they have the same year, the game gets confused about which era is later
62}
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        // let modif = format!("{key}_opinion");
105        // data.verify_exists_implied(Item::ModifierFormat, &modif, key);
106
107        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); // undocumented
228        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); // undocumented
327            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); // undocumented
333            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); // undocumented
347            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); // undocumented
352        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        // culture parameter loca are lowercased, verified in 1.11
378        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"); // docs say {key}_trigger
470            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}