tiger_lib/ck3/data/
situations.rs

1use crate::block::Block;
2use crate::ck3::modif::ModifKinds;
3use crate::context::ScopeContext;
4use crate::db::{Db, DbKind};
5use crate::everything::Everything;
6use crate::game::GameFlags;
7use crate::item::{Item, ItemLoader, LoadAsFile, Recursive};
8use crate::modif::validate_modifs;
9use crate::pdxfile::PdxEncoding;
10use crate::report::{ErrorKey, err, warn};
11use crate::scopes::Scopes;
12use crate::script_value::validate_non_dynamic_script_value;
13use crate::token::Token;
14use crate::tooltipped::Tooltipped;
15use crate::validate::{validate_duration, validate_possibly_named_color};
16use crate::validator::Validator;
17
18#[derive(Clone, Debug)]
19pub struct Situation {}
20
21#[derive(Clone, Debug)]
22pub struct SituationCatalyst {}
23
24#[derive(Clone, Debug)]
25pub struct SituationHistory {}
26
27#[derive(Clone, Debug)]
28pub struct SituationGroupType {}
29
30inventory::submit! {
31    ItemLoader::Normal(GameFlags::Ck3, Item::Situation, Situation::add)
32}
33
34inventory::submit! {
35    ItemLoader::Normal(GameFlags::Ck3, Item::SituationCatalyst, SituationCatalyst::add)
36}
37
38inventory::submit! {
39    ItemLoader::Full(GameFlags::Ck3, Item::SituationHistory, PdxEncoding::Utf8Bom, ".txt", LoadAsFile::Yes, Recursive::No,  SituationHistory::add)
40}
41
42inventory::submit! {
43    ItemLoader::Normal(GameFlags::Ck3, Item::SituationGroupType, SituationGroupType::add)
44}
45
46impl Situation {
47    pub fn add(db: &mut Db, key: Token, block: Block) {
48        db.add(Item::Situation, key, block, Box::new(Self {}));
49    }
50}
51
52impl SituationCatalyst {
53    pub fn add(db: &mut Db, key: Token, block: Block) {
54        db.add(Item::SituationCatalyst, key, block, Box::new(Self {}));
55    }
56}
57
58impl SituationHistory {
59    pub fn add(db: &mut Db, file: Token, block: Block) {
60        db.add(Item::SituationHistory, file, block, Box::new(Self {}));
61    }
62}
63
64impl SituationGroupType {
65    pub fn add(db: &mut Db, file: Token, block: Block) {
66        db.add(Item::SituationGroupType, file, block, Box::new(Self {}));
67    }
68}
69
70impl DbKind for Situation {
71    fn add_subitems(&self, _key: &Token, block: &Block, db: &mut Db) {
72        if let Some(block) = block.get_field_block("sub_regions") {
73            for (key, _) in block.iter_definitions() {
74                db.add_flag(Item::SituationSubRegion, key.clone());
75            }
76        }
77        if let Some(block) = block.get_field_block("participant_groups") {
78            for (key, _) in block.iter_definitions() {
79                db.add_flag(Item::SituationParticipantGroup, key.clone());
80            }
81        }
82        if let Some(block) = block.get_field_block("phases") {
83            for (key, block) in block.iter_definitions() {
84                if let Some(block) = block.get_field_block("parameters") {
85                    for (key, _) in block.iter_assignments() {
86                        db.add_flag(Item::SituationPhaseParameter, key.clone());
87                    }
88                }
89                if let Some(block) = block.get_field_block("modifier_sets") {
90                    for (_, block) in block.iter_definitions() {
91                        for (_, block) in block.iter_definitions() {
92                            if let Some(block) = block.get_field_block("parameters") {
93                                for (key, _) in block.iter_assignments() {
94                                    db.add_flag(
95                                        Item::SituationParticipantGroupParameter,
96                                        key.clone(),
97                                    );
98                                }
99                            }
100                        }
101                    }
102                }
103                db.add_flag(Item::SituationPhase, key.clone());
104            }
105        }
106    }
107
108    fn validate(&self, key: &Token, block: &Block, data: &Everything) {
109        let mut vd = Validator::new(block, data);
110
111        let loca = format!("situation_{key}");
112        data.verify_exists_implied(Item::Localization, &loca, key);
113        let loca = format!("situation_{key}_desc");
114        data.verify_exists_implied(Item::Localization, &loca, key);
115        let loca = format!("situation_type_{key}");
116        data.verify_exists_implied(Item::Localization, &loca, key);
117        let loca = format!("situation_type_{key}_desc");
118        data.verify_exists_implied(Item::Localization, &loca, key);
119
120        vd.field_choice(
121            "window",
122            &["situation", "the_great_steppe", "silk_road", "dynastic_cycle"],
123        );
124        if let Some(token) = vd.field_value("gui_window_name") {
125            let pathname = format!("gui/{token}.gui");
126            data.verify_exists_implied(Item::File, &pathname, token);
127        }
128        if let Some(token) = vd.field_value("gui_participation_window_name") {
129            let pathname = format!("gui/{token}.gui");
130            data.verify_exists_implied(Item::File, &pathname, token);
131        }
132
133        vd.field_bool("gui_tooltip_group_focused");
134        vd.field_item("illustration", Item::File);
135        vd.multi_field_validated_block("icon", |block, data| {
136            let mut vd = Validator::new(block, data);
137            vd.field_trigger_rooted("trigger", Tooltipped::No, Scopes::Situation);
138            vd.field_item("reference", Item::File);
139        });
140        vd.field_item("situation_group_type", Item::SituationGroupType);
141        vd.field_integer("sort_order");
142
143        vd.field_validated_value("map_mode", |_, mut vvd| {
144            vvd.maybe_is("participant_groups");
145            vvd.maybe_is("sub_regions");
146            vvd.item(Item::MapMode);
147        });
148
149        // TODO: You are restricted to max 255 sub-regions
150        vd.req_field("sub_regions");
151        vd.field_validated_block("sub_regions", |block, data| {
152            let mut vd = Validator::new(block, data);
153            let mut count = 0;
154            vd.unknown_block_fields(|sub_region_key, block| {
155                count += 1;
156                validate_sub_region(sub_region_key, block, data, key);
157            });
158            if count == 0 {
159                let msg = "situation needs at least one sub-region";
160                err(ErrorKey::FieldMissing).msg(msg).loc(key).push();
161            }
162        });
163
164        // TODO: You are restricted to max 255 participant groups
165        vd.req_field("participant_groups");
166        vd.field_validated_block("participant_groups", |block, data| {
167            let mut vd = Validator::new(block, data);
168            let mut count = 0;
169            vd.unknown_block_fields(|pg_key, block| {
170                count += 1;
171                validate_participant_group(pg_key, block, data, key);
172            });
173            if count == 0 {
174                let msg = "situation needs at least one participant group";
175                err(ErrorKey::FieldMissing).msg(msg).loc(key).push();
176            }
177        });
178
179        vd.req_field("phases");
180        vd.field_validated_block("phases", |block, data| {
181            let mut vd = Validator::new(block, data);
182            let mut count = 0;
183            vd.unknown_block_fields(|phase_key, block| {
184                count += 1;
185                validate_phase(phase_key, block, data, key);
186            });
187            if count == 0 {
188                let msg = "situation needs at least one phase";
189                err(ErrorKey::FieldMissing).msg(msg).loc(key).push();
190            }
191        });
192
193        vd.field_effect_rooted("on_start", Tooltipped::No, Scopes::Situation);
194        vd.field_effect_rooted("on_end", Tooltipped::No, Scopes::Situation);
195        vd.field_effect_rooted("on_monthly", Tooltipped::No, Scopes::Situation);
196        vd.field_effect_rooted("on_yearly", Tooltipped::No, Scopes::Situation);
197        vd.field_effect_rooted("on_join", Tooltipped::Yes, Scopes::Situation);
198        vd.field_effect_rooted("on_leave", Tooltipped::Yes, Scopes::Situation);
199
200        vd.field_bool("is_unique");
201        vd.field_bool("keep_full_history");
202        vd.field_bool("migration");
203        // TODO: check that the start phase is part of this situation's phases
204        vd.field_item("start_phase", Item::SituationPhase);
205        vd.field_bool("use_situation_phase_flat_icons");
206    }
207}
208
209impl DbKind for SituationCatalyst {
210    fn validate(&self, _key: &Token, block: &Block, data: &Everything) {
211        // vd is created in order to warn about unknown fields when it is dropped.
212        let _vd = Validator::new(block, data);
213    }
214}
215
216impl DbKind for SituationHistory {
217    fn validate(&self, _file: &Token, block: &Block, data: &Everything) {
218        let mut vd = Validator::new(block, data);
219        vd.validate_history_blocks(|_, _, block, data| {
220            let mut vd = Validator::new(block, data);
221            vd.field_effect_rooted("effect", Tooltipped::No, Scopes::None);
222        });
223    }
224}
225
226impl DbKind for SituationGroupType {
227    fn validate(&self, _key: &Token, block: &Block, data: &Everything) {
228        let mut vd = Validator::new(block, data);
229
230        vd.field_integer("sort_order");
231        vd.field_list("gui_tags");
232    }
233}
234
235fn validate_participant_group(key: &Token, block: &Block, data: &Everything, situation: &Token) {
236    fn sc_builder(key: &Token) -> ScopeContext {
237        let mut sc = ScopeContext::new(Scopes::Character, key);
238        sc.define_name("situation", Scopes::Situation, key);
239        sc.define_name("situation_sub_region", Scopes::SituationSubRegion, key);
240        sc
241    }
242    fn sc_with_group(key: &Token) -> ScopeContext {
243        let mut sc = sc_builder(key);
244        sc.define_name("situation_participant_group", Scopes::SituationParticipantGroup, key);
245        sc
246    }
247
248    let mut vd = Validator::new(block, data);
249
250    let loca = format!("{situation}_participant_group_{key}");
251    data.verify_exists_implied(Item::Localization, &loca, key);
252    let loca = format!("{situation}_participant_group_{key}_desc");
253    data.verify_exists_implied(Item::Localization, &loca, key);
254
255    vd.field_item("icon", Item::File);
256    vd.field_bool("auto_add_rulers");
257    vd.field_bool("auto_add_landless_rulers");
258    vd.field_validated("map_color", validate_possibly_named_color);
259    vd.field_bool("require_capital_in_sub_region");
260    vd.field_bool("require_domain_in_sub_region");
261    vd.field_bool("require_realm_in_sub_region");
262    vd.field_bool("require_domicile_in_sub_region");
263
264    vd.field_trigger_builder("is_character_valid", Tooltipped::Yes, sc_builder);
265    vd.field_effect_builder("on_join", Tooltipped::Yes, sc_with_group);
266    vd.field_effect_builder("on_leave", Tooltipped::Yes, sc_with_group);
267}
268
269fn validate_phase(key: &Token, block: &Block, data: &Everything, situation: &Token) {
270    fn sc_builder(key: &Token) -> ScopeContext {
271        let mut sc = ScopeContext::new(Scopes::Situation, key);
272        sc.define_name("situation", Scopes::Situation, key);
273        sc.define_name("situation_sub_region", Scopes::SituationSubRegion, key);
274        sc
275    }
276    fn sc_builder2(key: &Token) -> ScopeContext {
277        let mut sc = ScopeContext::new(Scopes::Situation, key);
278        sc.define_name("situation_sub_region", Scopes::SituationSubRegion, key);
279        sc
280    }
281
282    let mut vd = Validator::new(block, data);
283
284    let loca = format!("{situation}_{key}_situation_phase");
285    data.verify_exists_implied(Item::Localization, &loca, key);
286    // TODO: {key} and {key}_desc also seem to exist.
287
288    vd.field_validated_block("parameters", validate_parameters);
289
290    vd.field_effect_builder("on_start", Tooltipped::No, sc_builder);
291    vd.field_effect_builder("on_end", Tooltipped::No, sc_builder);
292    vd.field_item("illustration", Item::File);
293    vd.field_item("icon", Item::File);
294    vd.field_item("map_province_effect", Item::ProvinceEffect);
295    vd.field_numeric_range("map_province_effect_intensity", 0.0..=1.0);
296    vd.field_validated_block_sc("max_duration", &mut sc_builder2(key), validate_duration);
297    vd.field_choice(
298        "max_duration_next_phase",
299        &[
300            "highest_points",
301            "weighted_random_points",
302            "random_non_takeover",
303            "weighted_non_takeover",
304        ],
305    );
306
307    vd.field_validated_block("future_phases", |block, data| {
308        let mut vd = Validator::new(block, data);
309
310        vd.validate_item_key_blocks(Item::SituationPhase, |_, block, data| {
311            let mut vd = Validator::new(block, data);
312
313            vd.field_choice("takeover_type", &["none", "points", "duration"]);
314            vd.field_script_value_no_breakdown_builder("takeover_points", sc_builder2);
315            vd.field_script_value_no_breakdown_builder("weight", sc_builder2);
316            vd.field_validated_block_sc(
317                "takeover_duration",
318                &mut sc_builder2(key),
319                validate_duration,
320            );
321            vd.field_validated_block("catalysts", |block, data| {
322                let mut vd = Validator::new(block, data);
323
324                vd.unknown_fields(|key, bv| {
325                    data.verify_exists(Item::SituationCatalyst, key);
326                    validate_non_dynamic_script_value(bv, data);
327                });
328            });
329        });
330    });
331
332    vd.advice_field("modifier_named_sets", "docs say modifier_named_sets but it's modifier_sets");
333    vd.field_validated_block("modifier_sets", |block, data| {
334        let mut vd = Validator::new(block, data);
335
336        vd.unknown_block_fields(|key, block| {
337            let mut vd = Validator::new(block, data);
338            data.verify_exists(Item::Localization, key);
339
340            vd.field_item("icon", Item::File);
341            vd.field_validated_block("all", validate_modifier_set);
342            // TODO: the participant groups should be from this situation.
343            vd.validate_item_key_blocks(Item::SituationParticipantGroup, |_, block, data| {
344                validate_modifier_set(block, data);
345            });
346        });
347    });
348}
349
350fn validate_sub_region(key: &Token, block: &Block, data: &Everything, situation: &Token) {
351    let mut vd = Validator::new(block, data);
352
353    let loca = format!("{situation}_sub_region_{key}");
354    data.verify_exists_implied(Item::Localization, &loca, key);
355
356    vd.field_item("illustration", Item::File);
357    vd.field_item("icon", Item::File);
358    vd.field_validated("map_color", validate_possibly_named_color);
359    vd.field_list_items("geographical_regions", Item::Region);
360
361    // undocumented
362
363    vd.field_item("capital_province", Item::Province);
364}
365
366fn validate_modifier_set(block: &Block, data: &Everything) {
367    let mut vd = Validator::new(block, data);
368
369    vd.field_validated_block("county_modifier", |block, data| {
370        let vd = Validator::new(block, data);
371        validate_modifs(block, data, ModifKinds::County, vd);
372    });
373    vd.field_validated_block("character_modifier", |block, data| {
374        let vd = Validator::new(block, data);
375        validate_modifs(block, data, ModifKinds::Character, vd);
376    });
377    vd.multi_field_validated_block("doctrine_character_modifier", |block, data| {
378        let mut vd = Validator::new(block, data);
379        vd.field_item("name", Item::Localization);
380        vd.field_item("doctrine", Item::Doctrine);
381        validate_modifs(block, data, ModifKinds::Character, vd);
382    });
383    vd.field_validated_block("parameters", validate_parameters);
384}
385
386fn validate_parameters(block: &Block, data: &Everything) {
387    let mut vd = Validator::new(block, data);
388    vd.unknown_value_fields(|key, value| {
389        let loca = format!("situation_parameter_{key}");
390        data.verify_exists_implied(Item::Localization, &loca, key);
391        // TODO: {key}_name also seems to exist.
392
393        if !value.is("yes") {
394            let msg = "only `yes` makes sense here";
395            warn(ErrorKey::Validation).msg(msg).loc(value).push();
396        }
397    });
398}