tiger_lib/ck3/
validate.rs

1//! Validation functions that are useful for more than one data module in ck3.
2
3use crate::block::{BV, Block};
4use crate::ck3::tables::misc::AI_TARGETS;
5use crate::context::ScopeContext;
6use crate::desc::validate_desc;
7use crate::everything::Everything;
8use crate::item::Item;
9use crate::report::{ErrorKey, err, fatal};
10use crate::scopes::Scopes;
11use crate::tooltipped::Tooltipped;
12use crate::trigger::{validate_target, validate_target_ok_this};
13use crate::validate::{validate_modifiers_with_base, validate_scope_chain};
14use crate::validator::Validator;
15
16pub fn validate_theme_background(bv: &BV, data: &Everything, sc: &mut ScopeContext) {
17    match bv {
18        BV::Value(token) => {
19            data.verify_exists(Item::EventBackground, token);
20            let block = Block::new(token.loc);
21            data.validate_call(Item::EventBackground, token, &block, sc);
22        }
23        BV::Block(block) => {
24            let mut vd = Validator::new(block, data);
25
26            vd.field_trigger("trigger", Tooltipped::No, sc);
27            if vd.field_value("event_background").is_some() {
28                let msg = "`event_background` now causes a crash. It has been replaced by `reference` since 1.9";
29                fatal(ErrorKey::Crash)
30                    .msg(msg)
31                    .loc(block.get_key("event_background").unwrap())
32                    .push();
33            }
34            vd.req_field("reference");
35            if let Some(token) = vd.field_value("reference") {
36                data.verify_exists(Item::EventBackground, token);
37                data.validate_call(Item::EventBackground, token, block, sc);
38            }
39        }
40    }
41}
42
43pub fn validate_theme_header_background(bv: &BV, data: &Everything, sc: &mut ScopeContext) {
44    match bv {
45        BV::Value(token) => data.verify_exists(Item::File, token),
46        BV::Block(block) => {
47            let mut vd = Validator::new(block, data);
48
49            vd.field_trigger("trigger", Tooltipped::No, sc);
50            vd.req_field("reference");
51            if let Some(token) = vd.field_value("reference") {
52                data.verify_exists(Item::File, token);
53            }
54        }
55    }
56}
57
58pub fn validate_theme_icon(bv: &BV, data: &Everything, sc: &mut ScopeContext) {
59    validate_theme_header_background(bv, data, sc);
60}
61
62pub fn validate_theme_sound(block: &Block, data: &Everything, sc: &mut ScopeContext) {
63    let mut vd = Validator::new(block, data);
64
65    vd.field_trigger("trigger", Tooltipped::No, sc);
66    vd.field_item("reference", Item::Sound);
67}
68
69pub fn validate_theme_transition(block: &Block, data: &Everything, sc: &mut ScopeContext) {
70    let mut vd = Validator::new(block, data);
71
72    vd.field_trigger("trigger", Tooltipped::No, sc);
73    if let Some(token) = vd.field_value("reference") {
74        data.verify_exists(Item::EventTransition, token);
75        data.validate_call(Item::EventTransition, token, block, sc);
76    }
77}
78
79pub fn validate_theme_effect_2d(bv: &BV, data: &Everything, sc: &mut ScopeContext) {
80    match bv {
81        BV::Value(token) => data.verify_exists(Item::EventEffect2d, token),
82        BV::Block(block) => {
83            let mut vd = Validator::new(block, data);
84
85            vd.field_trigger("trigger", Tooltipped::No, sc);
86            if let Some(token) = vd.field_value("reference") {
87                data.verify_exists(Item::EventEffect2d, token);
88            }
89        }
90    }
91}
92
93pub fn validate_cost(block: &Block, data: &Everything, sc: &mut ScopeContext) {
94    let mut vd = Validator::new(block, data);
95    vd.field_script_value("barter_goods", sc);
96    vd.field_script_value("gold", sc);
97    vd.field_script_value("herd", sc);
98    vd.field_script_value("influence", sc);
99    vd.field_script_value("prestige", sc);
100    vd.field_script_value("piety", sc);
101    vd.field_script_value("renown", sc);
102    vd.field_script_value("treasury", sc);
103    vd.field_script_value("treasury_or_gold", sc);
104    vd.field_bool("round");
105}
106
107pub fn validate_traits(block: &Block, data: &Everything) {
108    let mut vd = Validator::new(block, data);
109    vd.field_validated_block("virtues", validate_virtues_sins);
110    vd.field_validated_block("sins", validate_virtues_sins);
111}
112
113pub fn validate_virtues_sins(block: &Block, data: &Everything) {
114    // Can be single tokens ("wrathful") or assignments ("wrathful = 3")
115    // or even wrathful = { scale = 2 weight = 2 } whatever that means
116    let mut vd = Validator::new(block, data);
117    for token in vd.values() {
118        data.verify_exists(Item::Trait, token);
119    }
120    vd.unknown_value_fields(|key, value| {
121        data.verify_exists(Item::Trait, key);
122        value.expect_number();
123    });
124    vd.unknown_block_fields(|key, block| {
125        data.verify_exists(Item::Trait, key);
126        let mut vd = Validator::new(block, data);
127        vd.field_numeric("scale");
128        vd.field_numeric("weight");
129    });
130}
131
132pub fn validate_compare_modifier(block: &Block, data: &Everything, sc: &mut ScopeContext) {
133    let mut vd = Validator::new(block, data);
134
135    // `value` and `factor` are evaluated in the scope created by `target`
136    sc.open_builder();
137    let mut valid_target = false;
138    vd.field_validated_value("target", |_, mut vd| {
139        valid_target = validate_scope_chain(vd.value(), data, sc, false);
140        vd.accept();
141    });
142    sc.finalize_builder();
143    if valid_target {
144        vd.field_script_value("value", sc);
145        vd.field_script_value("factor", sc);
146    } else {
147        vd.field("value");
148        vd.field("factor");
149    }
150    sc.close();
151
152    vd.multi_field_script_value("multiplier", sc);
153    vd.field_script_value("min", sc);
154    vd.field_script_value("max", sc);
155    vd.field_script_value("step", sc); // What does this do?
156    vd.field_script_value("offset", sc); // What does this do?
157    vd.field_validated_sc("desc", sc, validate_desc);
158    vd.field_trigger("trigger", Tooltipped::No, sc);
159}
160
161pub fn validate_opinion_modifier(block: &Block, data: &Everything, sc: &mut ScopeContext) {
162    let mut vd = Validator::new(block, data);
163    if let Some(target) = vd.field_value("who") {
164        validate_target_ok_this(target, data, sc, Scopes::Character);
165    }
166    vd.req_field("opinion_target");
167    if let Some(target) = vd.field_value("opinion_target") {
168        validate_target_ok_this(target, data, sc, Scopes::Character);
169    }
170    vd.field_script_value("multiplier", sc);
171    vd.field_validated_sc("desc", sc, validate_desc);
172    vd.field_script_value("min", sc);
173    vd.field_script_value("max", sc);
174    vd.field_script_value("step", sc); // What does this do?
175    vd.field_trigger("trigger", Tooltipped::No, sc);
176}
177
178pub fn validate_ai_value_modifier(block: &Block, data: &Everything, sc: &mut ScopeContext) {
179    let mut vd = Validator::new(block, data);
180    if let Some(target) = vd.field_value("who") {
181        validate_target_ok_this(target, data, sc, Scopes::Character);
182    }
183    // TODO: verify that this actually works. It's only used 1 time in vanilla.
184    vd.field_validated_block("dread_modified_ai_boldness", |block, data| {
185        let mut vd = Validator::new(block, data);
186        vd.req_field("dreaded_character");
187        vd.req_field("value");
188        vd.field_target_ok_this("dreaded_character", sc, Scopes::Character);
189        vd.field_script_value("value", sc);
190    });
191    vd.field_script_value("ai_boldness", sc);
192    vd.field_script_value("ai_compassion", sc);
193    vd.field_script_value("ai_energy", sc);
194    vd.field_script_value("ai_greed", sc);
195    vd.field_script_value("ai_honor", sc);
196    vd.field_script_value("ai_rationality", sc);
197    vd.field_script_value("ai_sociability", sc);
198    vd.field_script_value("ai_vengefulness", sc);
199    vd.field_script_value("ai_zeal", sc);
200    vd.field_script_value("min", sc);
201    vd.field_script_value("max", sc);
202    vd.field_trigger("trigger", Tooltipped::No, sc);
203}
204
205pub fn validate_compatibility_modifier(block: &Block, data: &Everything, sc: &mut ScopeContext) {
206    let mut vd = Validator::new(block, data);
207    if let Some(target) = vd.field_value("who") {
208        validate_target_ok_this(target, data, sc, Scopes::Character);
209    }
210    if let Some(target) = vd.field_value("compatibility_target") {
211        validate_target_ok_this(target, data, sc, Scopes::Character);
212    }
213    vd.field_script_value("multiplier", sc);
214    //vd.field_validated_sc("desc", sc, validate_desc);
215    vd.field_script_value("min", sc);
216    vd.field_script_value("max", sc);
217    vd.field_trigger("trigger", Tooltipped::No, sc);
218}
219
220pub fn validate_activity_modifier(block: &Block, data: &Everything, sc: &mut ScopeContext) {
221    let mut vd = Validator::new(block, data);
222    vd.field_target("object", sc, Scopes::Activity);
223    vd.field_target("target", sc, Scopes::Character);
224}
225
226pub fn validate_scheme_modifier(block: &Block, data: &Everything, sc: &mut ScopeContext) {
227    let mut vd = Validator::new(block, data);
228    vd.field_target("object", sc, Scopes::Scheme);
229    vd.field_target("target", sc, Scopes::Character);
230}
231
232pub fn validate_random_traits_list(block: &Block, data: &Everything, sc: &mut ScopeContext) {
233    let mut vd = Validator::new(block, data);
234    vd.field_script_value("count", sc);
235    vd.unknown_block_fields(|key, block| {
236        data.verify_exists(Item::Trait, key);
237        let mut vd = Validator::new(block, data);
238        vd.field_validated_block_sc("weight", sc, validate_modifiers_with_base);
239        vd.field_trigger("trigger", Tooltipped::No, sc);
240    });
241}
242
243pub fn validate_random_culture(block: &Block, data: &Everything, sc: &mut ScopeContext) {
244    let mut vd = Validator::new(block, data);
245    vd.unknown_block_fields(|key, block| {
246        validate_target(key, data, sc, Scopes::Culture);
247        let mut vd = Validator::new(block, data);
248        vd.field_validated_block_sc("weight", sc, validate_modifiers_with_base);
249        vd.field_trigger("trigger", Tooltipped::No, sc);
250    });
251}
252
253pub fn validate_random_faith(block: &Block, data: &Everything, sc: &mut ScopeContext) {
254    let mut vd = Validator::new(block, data);
255    vd.unknown_block_fields(|key, block| {
256        validate_target(key, data, sc, Scopes::Faith);
257        let mut vd = Validator::new(block, data);
258        vd.field_validated_block_sc("weight", sc, validate_modifiers_with_base);
259        vd.field_trigger("trigger", Tooltipped::No, sc);
260    });
261}
262
263pub fn validate_maa_stats(vd: &mut Validator) {
264    vd.field_numeric("pursuit");
265    vd.field_numeric("screen");
266    vd.field_numeric("damage");
267    vd.field_numeric("toughness");
268    vd.field_numeric("siege_value");
269}
270
271pub fn validate_portrait_modifier_overrides(block: &Block, data: &Everything) {
272    let mut vd = Validator::new(block, data);
273    vd.unknown_value_fields(|key, value| {
274        data.verify_exists(Item::PortraitModifierGroup, key);
275        if !data.item_has_property(Item::PortraitModifierGroup, key.as_str(), value.as_str()) {
276            let msg = format!("portrait modifier group {key} does not have the modifier {value}");
277            err(ErrorKey::MissingItem).msg(msg).loc(value).push();
278        }
279    });
280}
281
282pub fn validate_quick_trigger(block: &Block, data: &Everything) {
283    let mut vd = Validator::new(block, data);
284
285    vd.field_bool("adult");
286    vd.field_bool("attracted_to_owner");
287    vd.field_bool("owner_attracted");
288    vd.field_bool("prison");
289}
290
291pub fn validate_ai_targets(block: &Block, data: &Everything) {
292    let mut vd = Validator::new(block, data);
293
294    vd.req_field("ai_recipients");
295    vd.multi_field_choice("ai_recipients", AI_TARGETS);
296    vd.field_integer_range("max", 0..);
297    vd.field_numeric_range("chance", 0.0..=1.0);
298
299    let mut found = false;
300    for value in block.get_field_values("ai_recipients") {
301        if value.is("situation_participant_group") {
302            vd.field_item("parameter", Item::Situation);
303            found = true;
304        }
305    }
306    if !found {
307        vd.ban_field("parameter", || "`ai_recipients = situation_participant_group`");
308    }
309}