tiger_lib/ck3/data/
struggle.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, warn};
11use crate::scopes::Scopes;
12use crate::script_value::validate_script_value;
13use crate::token::Token;
14use crate::tooltipped::Tooltipped;
15use crate::validate::validate_duration;
16use crate::validator::Validator;
17
18#[derive(Clone, Debug)]
19pub struct Struggle {}
20
21inventory::submit! {
22    ItemLoader::Normal(GameFlags::Ck3, Item::Struggle, Struggle::add)
23}
24
25impl Struggle {
26    pub fn add(db: &mut Db, key: Token, block: Block) {
27        db.add(Item::Struggle, key, block, Box::new(Self {}));
28    }
29}
30
31impl DbKind for Struggle {
32    fn add_subitems(&self, _key: &Token, block: &Block, db: &mut Db) {
33        if let Some(block) = block.get_field_block("phase_list") {
34            for (key, block) in block.iter_definitions() {
35                db.add_flag(Item::StrugglePhase, key.clone());
36                for field in &["war_effects", "culture_effects", "faith_effects", "other_effects"] {
37                    if let Some(block) = block.get_field_block(field) {
38                        for field in &[
39                            "common_parameters",
40                            "involved_parameters",
41                            "interloper_parameters",
42                            "uninvolved_parameters",
43                        ] {
44                            if let Some(block) = block.get_field_block(field) {
45                                for (key, value) in block.iter_assignments() {
46                                    if value.is("yes") {
47                                        db.add_flag(Item::StrugglePhaseParameter, key.clone());
48                                    }
49                                }
50                            }
51                        }
52                    }
53                }
54            }
55        }
56    }
57
58    fn validate(&self, key: &Token, block: &Block, data: &Everything) {
59        let mut vd = Validator::new(block, data);
60        let mut sc = ScopeContext::new(Scopes::Struggle, key);
61
62        data.verify_exists(Item::Localization, key);
63        let loca = format!("{key}_desc");
64        data.verify_exists_implied(Item::Localization, &loca, key);
65
66        vd.field_item("illustration", Item::File);
67        vd.field_item("situation_group_type", Item::SituationGroupType);
68        vd.field_integer("sort_order");
69
70        vd.field_list_items("cultures", Item::Culture);
71        vd.field_list_items("faiths", Item::Faith);
72        vd.field_list_items("regions", Item::Region);
73
74        vd.field_validated_block_sc("transition_state_duration", &mut sc, validate_duration);
75        vd.field_numeric_range("involvement_prerequisite_percentage", 0.0..=1.0);
76
77        vd.req_field("phase_list");
78        vd.field_validated_block("phase_list", |block, data| {
79            let mut has_one = false;
80            let mut has_ending = false;
81            let phases = block.iter_definitions_warn().map(|(key, _)| key).collect::<Vec<_>>();
82            let mut vd = Validator::new(block, data);
83            vd.unknown_block_fields(|key, block| {
84                data.verify_exists(Item::Localization, key);
85                let loca = format!("{key}_desc");
86                data.verify_exists_implied(Item::Localization, &loca, key);
87                data.verify_icon("NGameIcons|STRUGGLE_PHASE_TYPE_ICON_PATH", key, ".dds");
88                has_one = true;
89                validate_phase(block, data, &phases);
90                if let Some(vec) = block.get_field_list("ending_decisions") {
91                    has_ending |= !vec.is_empty();
92                }
93            });
94            if !has_one {
95                warn(ErrorKey::Validation).msg("must have at least one phase").loc(block).push();
96            }
97            // TODO: Verify if it is OK to have an ending phase but no ending decisions
98            if !has_ending {
99                let msg = "must have at least one phase with ending_decisions";
100                warn(ErrorKey::Validation).msg(msg).loc(block).push();
101            }
102        });
103
104        vd.req_field("start_phase");
105        vd.field_item("start_phase", Item::StrugglePhase);
106
107        vd.field_effect("on_start", Tooltipped::No, &mut sc);
108        vd.field_effect("on_end", Tooltipped::No, &mut sc); // TODO: check tooltipped
109        vd.field_effect("on_change_phase", Tooltipped::No, &mut sc); // TODO: check tooltipped
110        vd.field_effect_rooted("on_join", Tooltipped::No, Scopes::Character); // TODO: check tooltipped
111        vd.field_effect("on_monthly", Tooltipped::No, &mut sc);
112    }
113}
114
115fn validate_phase(block: &Block, data: &Everything, phases: &[&Token]) {
116    let mut vd = Validator::new(block, data);
117
118    // Ending phase
119    if vd.field_block("on_start") {
120        // Undocumented
121        vd.field_bool("save_progress");
122        vd.field_effect_rooted("on_start", Tooltipped::Yes, Scopes::Struggle);
123        vd.unknown_fields(|key, _| {
124            let msg = format!("ending phase should not have {key}, which will be ignored");
125            warn(ErrorKey::UnknownField).msg(msg).loc(key).push();
126        });
127    } else {
128        vd.field_validated_block_rooted("duration", Scopes::None, |block, data, sc| {
129            if let Some(bv) = block.get_field("points") {
130                if let Some(token) = bv.expect_value() {
131                    token.expect_integer();
132                }
133            } else {
134                validate_duration(block, data, sc);
135            }
136        });
137
138        vd.field_item("background", Item::File);
139        vd.req_field("future_phases");
140        vd.field_validated_block("future_phases", |block, data| {
141            let mut vd = Validator::new(block, data);
142            let mut has_one = false;
143            vd.unknown_block_fields(|key, block| {
144                let mut vd = Validator::new(block, data);
145                has_one = true;
146                data.verify_exists(Item::StrugglePhase, key);
147                if !phases.contains(&key) {
148                    let msg = format!("{key} is not a struggle phase of this struggle");
149                    warn(ErrorKey::UnknownField).msg(msg).loc(key).push();
150                }
151                vd.field_bool("default");
152                vd.field_validated_block("catalysts", validate_catalyst_list);
153            });
154            if !has_one {
155                warn(ErrorKey::Validation)
156                    .msg("must have at least one future phase")
157                    .loc(block)
158                    .push();
159            }
160        });
161
162        for field in &["war_effects", "culture_effects", "faith_effects", "other_effects"] {
163            vd.field_validated_block(field, validate_phase_effects);
164        }
165
166        vd.field_list_items("ending_decisions", Item::Decision);
167    }
168}
169
170fn validate_catalyst_list(block: &Block, data: &Everything) {
171    let mut vd = Validator::new(block, data);
172    vd.unknown_fields(|key, bv| {
173        if bv.expect_value().is_some() {
174            data.verify_exists(Item::Catalyst, key);
175            let mut sc = ScopeContext::new(Scopes::None, key);
176            validate_script_value(bv, data, &mut sc);
177        }
178    });
179}
180
181fn validate_phase_effects(block: &Block, data: &Everything) {
182    let mut vd = Validator::new(block, data);
183    vd.field_item("name", Item::Localization);
184    vd.field_validated_block("common_parameters", validate_struggle_parameters);
185    vd.field_validated_block("involved_parameters", validate_struggle_parameters);
186    vd.field_validated_block("interloper_parameters", validate_struggle_parameters);
187    vd.field_validated_block("uninvolved_parameters", validate_struggle_parameters);
188
189    for field in &["involved_character_modifier", "interloper_character_modifier"] {
190        vd.field_validated_block(field, |block, data| {
191            let vd = Validator::new(block, data);
192            validate_modifs(block, data, ModifKinds::Character, vd);
193        });
194    }
195
196    for field in &["involved_doctrine_character_modifier", "interloper_doctrine_character_modifier"]
197    {
198        vd.field_validated_block(field, |block, data| {
199            let mut vd = Validator::new(block, data);
200            vd.field_item("doctrine", Item::Doctrine);
201            validate_modifs(block, data, ModifKinds::Character, vd);
202        });
203    }
204
205    for field in &[
206        "all_county_modifier",
207        "involved_county_modifier",
208        "interloper_county_modifier",
209        "uninvolved_county_modifier",
210    ] {
211        vd.field_validated_block(field, |block, data| {
212            let vd = Validator::new(block, data);
213            validate_modifs(block, data, ModifKinds::County, vd);
214        });
215    }
216}
217
218fn validate_struggle_parameters(block: &Block, data: &Everything) {
219    let mut vd = Validator::new(block, data);
220    vd.unknown_value_fields(|key, value| {
221        if !value.is("yes") {
222            let msg = format!("expected `{key} = yes`");
223            warn(ErrorKey::Validation).msg(msg).loc(value).push();
224        }
225
226        let loca = format!("struggle_parameter_{key}");
227        data.verify_exists_implied(Item::Localization, &loca, key);
228    });
229}
230
231#[derive(Clone, Debug)]
232pub struct Catalyst {}
233
234inventory::submit! {
235    ItemLoader::Normal(GameFlags::Ck3, Item::Catalyst, Catalyst::add)
236}
237
238impl Catalyst {
239    pub fn add(db: &mut Db, key: Token, block: Block) {
240        db.add(Item::Catalyst, key, block, Box::new(Self {}));
241    }
242}
243
244impl DbKind for Catalyst {
245    fn validate(&self, _key: &Token, block: &Block, data: &Everything) {
246        let mut _vd = Validator::new(block, data);
247    }
248}
249
250#[derive(Clone, Debug)]
251pub struct StruggleHistory {}
252
253inventory::submit! {
254    ItemLoader::Full(GameFlags::Ck3, Item::StruggleHistory, PdxEncoding::Utf8Bom, ".txt", LoadAsFile::Yes, Recursive::No, StruggleHistory::add)
255}
256
257impl StruggleHistory {
258    pub fn add(db: &mut Db, key: Token, block: Block) {
259        db.add(Item::StruggleHistory, key, block, Box::new(Self {}));
260    }
261}
262
263impl DbKind for StruggleHistory {
264    fn validate(&self, key: &Token, block: &Block, data: &Everything) {
265        let mut vd = Validator::new(block, data);
266        let mut sc = ScopeContext::new(Scopes::None, key);
267
268        vd.unknown_block_fields(|key, block| {
269            key.expect_date();
270            let mut vd = Validator::new(block, data);
271            vd.field_effect("effect", Tooltipped::No, &mut sc);
272        });
273    }
274}