tiger_lib/ck3/
effect_validation.rs

1use crate::block::{BV, Block};
2use crate::ck3::data::legends::LegendChronicle;
3use crate::ck3::tables::misc::{
4    BANNED_TITLE_HISTORY_TYPES, LEGEND_QUALITY, OUTBREAK_INTENSITIES, TITLE_HISTORY_TYPES,
5};
6use crate::ck3::validate::{
7    validate_random_culture, validate_random_faith, validate_random_traits_list,
8};
9use crate::context::{ScopeContext, Temporary};
10use crate::data::effect_localization::validate_effect_localization;
11use crate::desc::validate_desc;
12use crate::effect::{validate_effect, validate_effect_internal};
13use crate::effect_validation::validate_random_list;
14use crate::everything::Everything;
15use crate::helpers::{TigerHashSet, stringify_choices, stringify_list};
16use crate::item::Item;
17use crate::lowercase::Lowercase;
18use crate::report::{ErrorKey, Severity, err, warn};
19use crate::scopes::Scopes;
20use crate::script_value::{validate_non_dynamic_script_value, validate_script_value};
21use crate::special_tokens::SpecialTokens;
22use crate::token::Token;
23use crate::tooltipped::Tooltipped;
24use crate::trigger::{validate_target, validate_target_ok_this};
25use crate::validate::{
26    ListType, validate_duration, validate_identifier, validate_mandatory_duration,
27    validate_optional_duration, validate_optional_duration_int, validate_possibly_named_color,
28};
29use crate::validator::{Validator, ValueValidator};
30
31pub fn validate_add_activity_log_entry(
32    key: &Token,
33    block: &Block,
34    data: &Everything,
35    sc: &mut ScopeContext,
36    mut vd: Validator,
37    tooltipped: Tooltipped,
38    special_tokens: &mut SpecialTokens,
39) -> bool {
40    let caller = Lowercase::new(key.as_str());
41    vd.req_field("key");
42    vd.req_field("character");
43    if let Some(token) = vd.field_value("key") {
44        let loca = format!("{token}_title");
45        data.verify_exists_implied(Item::Localization, &loca, token);
46    }
47    vd.field_script_value("score", sc);
48    vd.field_validated_block("tags", |b, data| {
49        let mut vd = Validator::new(b, data);
50        vd.values(); // TODO
51    });
52    vd.field_bool("show_in_conclusion");
53    vd.field_target("character", sc, Scopes::Character);
54    vd.field_target("target", sc, Scopes::Character);
55    vd.field_target("location", sc, Scopes::Province);
56    vd.field_target("artifact", sc, Scopes::Artifact);
57    // effects can be put directly in this block
58    validate_effect_internal(
59        &caller,
60        ListType::None,
61        block,
62        data,
63        sc,
64        &mut vd,
65        tooltipped,
66        special_tokens,
67    )
68}
69
70pub fn validate_add_artifact_history(
71    _key: &Token,
72    _block: &Block,
73    _data: &Everything,
74    sc: &mut ScopeContext,
75    mut vd: Validator,
76    _tooltipped: Tooltipped,
77) {
78    vd.req_field("type");
79    vd.req_field("recipient");
80    vd.field_item("type", Item::ArtifactHistory);
81    vd.field_date("date");
82    vd.field_target("actor", sc, Scopes::Character);
83    vd.field_target("recipient", sc, Scopes::Character);
84    vd.field_target("location", sc, Scopes::Province);
85}
86
87pub fn validate_add_artifact_title_history(
88    _key: &Token,
89    _block: &Block,
90    _data: &Everything,
91    sc: &mut ScopeContext,
92    mut vd: Validator,
93    _tooltipped: Tooltipped,
94) {
95    vd.req_field("target");
96    vd.req_field("date");
97    vd.field_target("target", sc, Scopes::LandedTitle);
98    vd.field_date("date");
99}
100
101pub fn validate_add_from_contribution(
102    _key: &Token,
103    _block: &Block,
104    _data: &Everything,
105    sc: &mut ScopeContext,
106    mut vd: Validator,
107    _tooltipped: Tooltipped,
108) {
109    vd.field_script_value("prestige", sc);
110    vd.field_script_value("gold", sc);
111    vd.field_script_value("piety", sc);
112    vd.field_script_value("influence", sc);
113    vd.field_script_value("merit", sc);
114    vd.field_script_value("renown", sc);
115    vd.field_script_value("treasury", sc);
116    vd.field_script_value("herd", sc);
117    vd.field_script_value("provisions", sc);
118    // TODO: barter_goods is not in the example for defenders. Is it available?
119    vd.field_script_value("barter_goods", sc);
120    vd.field_validated_block("opinion", |block, data| {
121        let mut vd = Validator::new(block, data);
122        vd.field_item("modifier", Item::OpinionModifier);
123    });
124}
125
126pub fn validate_add_hook(
127    _key: &Token,
128    _block: &Block,
129    data: &Everything,
130    sc: &mut ScopeContext,
131    mut vd: Validator,
132    _tooltipped: Tooltipped,
133) {
134    vd.req_field("type");
135    vd.req_field("target");
136    vd.field_item("type", Item::Hook);
137    vd.field_target("target", sc, Scopes::Character);
138    if let Some(token) = vd.field_value("secret") {
139        if !data.item_exists(Item::Secret, token.as_str()) {
140            validate_target(token, data, sc, Scopes::Secret);
141        }
142    }
143    validate_optional_duration(&mut vd, sc);
144}
145
146pub fn validate_add_opinion(
147    _key: &Token,
148    _block: &Block,
149    _data: &Everything,
150    sc: &mut ScopeContext,
151    mut vd: Validator,
152    _tooltipped: Tooltipped,
153) {
154    vd.req_field("modifier");
155    vd.req_field("target");
156    vd.field_item("modifier", Item::OpinionModifier);
157    vd.field_target("target", sc, Scopes::Character);
158    vd.field_script_value("opinion", sc); // undocumented
159    validate_optional_duration(&mut vd, sc);
160}
161
162pub fn validate_add_relation_flag(
163    _key: &Token,
164    _block: &Block,
165    _data: &Everything,
166    sc: &mut ScopeContext,
167    mut vd: Validator,
168    _tooltipped: Tooltipped,
169) {
170    vd.req_field("relation");
171    vd.req_field("flag");
172    vd.req_field("target");
173    vd.field_item("relation", Item::Relation);
174    // TODO: check that the flag belongs to the relation
175    vd.field_value("flag");
176    vd.field_target("target", sc, Scopes::Character);
177}
178
179pub fn validate_scheme_cooldown(
180    _key: &Token,
181    _block: &Block,
182    _data: &Everything,
183    sc: &mut ScopeContext,
184    mut vd: Validator,
185    _tooltipped: Tooltipped,
186) {
187    vd.req_field("target");
188    vd.req_field("type");
189    vd.field_target("target", sc, Scopes::Character);
190    vd.field_item("type", Item::Scheme);
191    validate_optional_duration_int(&mut vd);
192}
193
194pub fn validate_scheme_modifier(
195    key: &Token,
196    block: &Block,
197    data: &Everything,
198    sc: &mut ScopeContext,
199    mut vd: Validator,
200    _tooltipped: Tooltipped,
201) {
202    vd.req_field("type");
203    if let Some(token) = vd.field_value("type") {
204        data.verify_exists(Item::Modifier, token);
205        data.database.validate_call(Item::Modifier, token, block, data, sc);
206        data.database.validate_property_use(Item::Modifier, token, data, key, key.as_str());
207    }
208    validate_optional_duration(&mut vd, sc);
209}
210
211pub fn validate_add_secret(
212    _key: &Token,
213    _block: &Block,
214    _data: &Everything,
215    sc: &mut ScopeContext,
216    mut vd: Validator,
217    _tooltipped: Tooltipped,
218) {
219    vd.req_field("type");
220    vd.field_item("type", Item::Secret);
221    vd.field_target("target", sc, Scopes::Character);
222    if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
223        sc.define_name_token(name.as_str(), Scopes::Secret, name, Temporary::No);
224    }
225}
226
227pub fn validate_guest_subset(
228    _key: &Token,
229    _block: &Block,
230    _data: &Everything,
231    sc: &mut ScopeContext,
232    mut vd: Validator,
233    _tooltipped: Tooltipped,
234) {
235    vd.req_field("name");
236    vd.req_field("target");
237    vd.field_item("name", Item::GuestSubset);
238    vd.field_target("target", sc, Scopes::Character);
239    vd.field_item("phase", Item::ActivityPhase);
240}
241
242pub fn validate_add_trait_xp(
243    _key: &Token,
244    _block: &Block,
245    _data: &Everything,
246    sc: &mut ScopeContext,
247    mut vd: Validator,
248    _tooltipped: Tooltipped,
249) {
250    vd.req_field("trait");
251    vd.req_field("value");
252    // TODO: if the trait is an Item, verify that the TraitTrack belongs to this trait
253    vd.field_item_or_target("trait", sc, Item::Trait, Scopes::Trait);
254    vd.field_item("track", Item::TraitTrack);
255    vd.field_script_value("value", sc);
256}
257
258/// Used by a variety of `add_..._modifier` effects
259pub fn validate_add_modifier(
260    key: &Token,
261    bv: &BV,
262    data: &Everything,
263    sc: &mut ScopeContext,
264    _tooltipped: Tooltipped,
265) {
266    let caller = key.as_str().to_ascii_lowercase();
267    let visible = caller == "add_character_modifier"
268        || caller == "add_house_modifier"
269        || caller == "add_dynasty_modifier"
270        || caller == "add_county_modifier"
271        || caller == "add_house_unity_modifier"
272        || caller == "add_legend_county_modifier"
273        || caller == "add_legend_owner_modifier"
274        || caller == "add_legend_province_modifier";
275    match bv {
276        BV::Value(token) => {
277            data.verify_exists(Item::Modifier, token);
278            if visible {
279                data.verify_exists(Item::Localization, token);
280            }
281            let block = Block::new(key.loc);
282            data.database.validate_call(Item::Modifier, token, &block, data, sc);
283            data.database.validate_property_use(Item::Modifier, token, data, key, key.as_str());
284        }
285        BV::Block(block) => {
286            let mut vd = Validator::new(block, data);
287            vd.set_case_sensitive(false);
288            vd.req_field("modifier");
289            if let Some(token) = vd.field_value("modifier") {
290                data.verify_exists(Item::Modifier, token);
291                if visible && !block.has_key("desc") {
292                    data.verify_exists(Item::Localization, token);
293                }
294                data.database.validate_call(Item::Modifier, token, block, data, sc);
295                data.database.validate_property_use(Item::Modifier, token, data, key, key.as_str());
296            }
297            vd.field_validated_sc("desc", sc, validate_desc);
298            vd.field_validated_sc("duration_desc", sc, validate_desc);
299            validate_optional_duration(&mut vd, sc);
300        }
301    }
302}
303
304pub fn validate_add_truce(
305    _key: &Token,
306    block: &Block,
307    _data: &Everything,
308    sc: &mut ScopeContext,
309    mut vd: Validator,
310    _tooltipped: Tooltipped,
311) {
312    vd.req_field("character");
313    vd.field_target("character", sc, Scopes::Character);
314    vd.field_bool("override");
315    vd.field_choice("result", &["white_peace", "victory", "defeat"]);
316    vd.field_item("casus_belli", Item::CasusBelli);
317    vd.field_validated_sc("name", sc, validate_desc);
318    vd.field_target("war", sc, Scopes::War);
319    validate_optional_duration(&mut vd, sc);
320    if block.has_key("war") && block.has_key("casus_belli") {
321        let msg = "cannot use both `war` and `casus_belli`";
322        err(ErrorKey::Validation).msg(msg).loc(block).push();
323    }
324}
325
326pub fn validate_add_unity(
327    _key: &Token,
328    _block: &Block,
329    _data: &Everything,
330    sc: &mut ScopeContext,
331    mut vd: Validator,
332    _tooltipped: Tooltipped,
333) {
334    vd.req_field("value");
335    vd.req_field("character");
336    vd.field_script_value("value", sc);
337    vd.field_target("character", sc, Scopes::Character);
338    vd.field_validated_sc("desc", sc, validate_desc);
339}
340
341pub fn validate_assign_council_task(
342    _key: &Token,
343    _block: &Block,
344    _data: &Everything,
345    sc: &mut ScopeContext,
346    mut vd: Validator,
347    _tooltipped: Tooltipped,
348) {
349    vd.req_field("council_task");
350    vd.req_field("target");
351    vd.field_target("council_task", sc, Scopes::CouncilTask);
352    vd.field_target("target", sc, Scopes::Character);
353    vd.field_bool("fire_on_actions");
354}
355
356pub fn validate_assign_councillor_type(
357    _key: &Token,
358    _block: &Block,
359    _data: &Everything,
360    sc: &mut ScopeContext,
361    mut vd: Validator,
362    _tooltipped: Tooltipped,
363) {
364    vd.req_field("type");
365    vd.req_field("target");
366    vd.field_item("type", Item::CouncilPosition);
367    vd.field_target("target", sc, Scopes::Character);
368    vd.field_bool("fire_on_actions");
369    vd.field_bool("remove_existing_councillor");
370}
371
372pub fn validate_battle_event(
373    _key: &Token,
374    _block: &Block,
375    data: &Everything,
376    sc: &mut ScopeContext,
377    mut vd: Validator,
378    _tooltipped: Tooltipped,
379) {
380    vd.req_field("left_portrait");
381    vd.req_field("key");
382    if let Some(token) = vd.field_value("key") {
383        let loca = format!("{token}_friendly");
384        data.verify_exists_implied(Item::Localization, &loca, token);
385        let loca = format!("{token}_enemy");
386        data.verify_exists_implied(Item::Localization, &loca, token);
387    }
388    vd.field_target("left_portrait", sc, Scopes::Character);
389    vd.field_target("right_portrait", sc, Scopes::Character);
390    vd.field_value("type"); // TODO, undocumented
391    vd.field_bool("target_right"); // undocumented
392}
393
394pub fn validate_change_cultural_acceptance(
395    _key: &Token,
396    _block: &Block,
397    _data: &Everything,
398    sc: &mut ScopeContext,
399    mut vd: Validator,
400    _tooltipped: Tooltipped,
401) {
402    vd.req_field("target");
403    vd.req_field("value");
404    vd.field_target("target", sc, Scopes::Culture);
405    vd.field_script_value("value", sc);
406    vd.field_validated_sc("desc", sc, validate_desc);
407}
408
409pub fn validate_change_liege(
410    _key: &Token,
411    _block: &Block,
412    _data: &Everything,
413    sc: &mut ScopeContext,
414    mut vd: Validator,
415    _tooltipped: Tooltipped,
416) {
417    vd.req_field("liege");
418    vd.req_field("change");
419    vd.field_target("liege", sc, Scopes::Character);
420    vd.field_target("change", sc, Scopes::TitleAndVassalChange);
421}
422
423pub fn validate_change_struggle_phase(
424    _key: &Token,
425    bv: &BV,
426    data: &Everything,
427    _sc: &mut ScopeContext,
428    _tooltipped: Tooltipped,
429) {
430    match bv {
431        BV::Value(token) => {
432            data.verify_exists(Item::StrugglePhase, token);
433        }
434        BV::Block(block) => {
435            let mut vd = Validator::new(block, data);
436            vd.set_case_sensitive(false);
437            vd.req_field("struggle_phase");
438            vd.req_field("with_transition");
439            vd.field_item("struggle_phase", Item::StrugglePhase);
440            vd.field_bool("with_transition");
441        }
442    }
443}
444
445pub fn validate_change_struggle_phase_duration(
446    _key: &Token,
447    _block: &Block,
448    _data: &Everything,
449    sc: &mut ScopeContext,
450    mut vd: Validator,
451    _tooltipped: Tooltipped,
452) {
453    vd.req_field("duration");
454    vd.field_validated_block_sc("duration", sc, |block, data, sc| {
455        if let Some(bv) = block.get_field("points") {
456            validate_script_value(bv, data, sc);
457        } else {
458            validate_duration(block, data, sc);
459        }
460    });
461}
462
463pub fn validate_change_title_holder(
464    _key: &Token,
465    _block: &Block,
466    _data: &Everything,
467    sc: &mut ScopeContext,
468    mut vd: Validator,
469    _tooltipped: Tooltipped,
470) {
471    vd.req_field("holder");
472    vd.req_field("change");
473    vd.field_target("holder", sc, Scopes::Character);
474    vd.field_target("change", sc, Scopes::TitleAndVassalChange);
475    vd.field_bool("take_baronies");
476    vd.field_target("government_base", sc, Scopes::Character);
477}
478
479pub fn validate_change_trait_rank(
480    key: &Token,
481    _block: &Block,
482    _data: &Everything,
483    sc: &mut ScopeContext,
484    mut vd: Validator,
485    _tooltipped: Tooltipped,
486) {
487    let caller = key.as_str().to_ascii_lowercase();
488    vd.req_field("trait");
489    vd.req_field("rank");
490    // TODO: check that it's a rankable trait
491    vd.field_item("trait", Item::Trait);
492    vd.field_script_value("rank", sc);
493    if caller == "change_trait_rank" {
494        vd.field_script_value("max", sc);
495    }
496}
497
498pub fn validate_copy_localized_text(
499    _key: &Token,
500    _block: &Block,
501    _data: &Everything,
502    sc: &mut ScopeContext,
503    mut vd: Validator,
504    _tooltipped: Tooltipped,
505) {
506    vd.req_field("key");
507    vd.req_field("target");
508    vd.field_value("key");
509    vd.field_target("target", sc, Scopes::Character);
510}
511
512pub fn validate_create_accolade(
513    _key: &Token,
514    _block: &Block,
515    _data: &Everything,
516    sc: &mut ScopeContext,
517    mut vd: Validator,
518    _tooltipped: Tooltipped,
519) {
520    vd.req_field("knight");
521    vd.req_field("primary");
522    vd.req_field("secondary");
523    vd.field_target("knight", sc, Scopes::Character);
524    vd.field_item("primary", Item::AccoladeType);
525    vd.field_item("secondary", Item::AccoladeType);
526    vd.field_item("name", Item::Localization);
527}
528
529pub fn validate_create_artifact(
530    key: &Token,
531    _block: &Block,
532    _data: &Everything,
533    sc: &mut ScopeContext,
534    mut vd: Validator,
535    _tooltipped: Tooltipped,
536) {
537    let caller = key.as_str().to_ascii_lowercase();
538    vd.field_validated_sc("name", sc, validate_desc);
539    vd.field_validated_sc("description", sc, validate_desc);
540    vd.field_item("rarity", Item::ArtifactRarity);
541    vd.field_item("type", Item::ArtifactType);
542    vd.multi_field_item("modifier", Item::Modifier);
543    vd.field_script_value("durability", sc);
544    vd.field_script_value("max_durability", sc);
545    vd.field_bool("decaying");
546    vd.multi_field_validated_block_sc("history", sc, validate_artifact_history);
547    vd.field_item("template", Item::ArtifactTemplate);
548    vd.field_item("visuals", Item::ArtifactVisual);
549    vd.field_bool("generate_history");
550    vd.field_script_value("quality", sc);
551    vd.field_script_value("wealth", sc);
552    vd.field_target("creator", sc, Scopes::Character);
553    vd.field_target(
554        "visuals_source",
555        sc,
556        Scopes::LandedTitle | Scopes::Dynasty | Scopes::DynastyHouse,
557    );
558
559    if caller == "create_artifact" {
560        if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
561            sc.define_name_token(name.as_str(), Scopes::Artifact, name, Temporary::No);
562        }
563        vd.field_target("title_history", sc, Scopes::LandedTitle);
564        vd.field_date("title_history_date");
565    } else {
566        vd.ban_field("save_scope_as", || "`create_artifact`");
567        vd.ban_field("title_history", || "`create_artifact`");
568        vd.ban_field("title_history_date", || "`create_artifact`");
569    }
570}
571
572pub fn validate_create_character(
573    _key: &Token,
574    block: &Block,
575    data: &Everything,
576    sc: &mut ScopeContext,
577    mut vd: Validator,
578    _tooltipped: Tooltipped,
579) {
580    // docs say save_event_target instead of save_scope
581    vd.replaced_field("save_event_target_as", "save_scope_as");
582    vd.replaced_field("save_temporary_event_target_as", "save_temporary_scope_as");
583    if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
584        sc.define_name_token(name.as_str(), Scopes::Character, name, Temporary::No);
585    }
586    if let Some(name) = vd.field_identifier("save_temporary_scope_as", "scope name") {
587        sc.define_name_token(name.as_str(), Scopes::Character, name, Temporary::Yes);
588    }
589
590    vd.field_validated_sc("name", sc, validate_desc);
591    vd.field_script_value("age", sc);
592    if let Some(token) = vd.field_value("gender") {
593        if !token.is("male") && !token.is("female") {
594            validate_target_ok_this(token, data, sc, Scopes::Character);
595        }
596    }
597    vd.field_script_value("gender_female_chance", sc);
598    vd.field_target_ok_this("opposite_gender", sc, Scopes::Character);
599    vd.multi_field_item("trait", Item::Trait);
600    vd.multi_field_validated_block_sc("random_traits_list", sc, validate_random_traits_list);
601    vd.field_bool("random_traits");
602    vd.field_script_value("health", sc);
603    vd.field_script_value("fertility", sc);
604    vd.field_target_ok_this("mother", sc, Scopes::Character);
605    vd.field_target_ok_this("father", sc, Scopes::Character);
606    vd.field_target_ok_this("real_father", sc, Scopes::Character);
607    vd.req_field_one_of(&["location", "employer"]);
608    vd.field_target_ok_this("employer", sc, Scopes::Character);
609    vd.field_target_ok_this("location", sc, Scopes::Province);
610    if let Some(token) = vd.field_value("template") {
611        // undocumented
612        data.verify_exists(Item::CharacterTemplate, token);
613        data.validate_call(Item::CharacterTemplate, token, block, sc);
614    }
615    vd.field_item("template", Item::CharacterTemplate); // undocumented
616    vd.field_target_ok_this("template_character", sc, Scopes::Character);
617    vd.field_item_or_target("faith", sc, Item::Faith, Scopes::Faith);
618    vd.field_validated_block_sc("random_faith", sc, validate_random_faith);
619    vd.field_item_or_target("random_faith_in_religion", sc, Item::Religion, Scopes::Faith);
620    vd.field_item_or_target("culture", sc, Item::Culture, Scopes::Culture);
621    vd.field_validated_block_sc("random_culture", sc, validate_random_culture);
622    // TODO: figure out what a culture group is, and whether this key still works at all
623    vd.field_value("random_culture_in_group");
624    vd.field_item_or_target("dynasty_house", sc, Item::House, Scopes::DynastyHouse);
625    if let Some(token) = vd.field_value("dynasty") {
626        if !token.is("generate") && !token.is("inherit") && !token.is("none") {
627            validate_target(token, data, sc, Scopes::Dynasty);
628        }
629    }
630    vd.field_validated_value("ethnicity", |_, mut vd| {
631        vd.maybe_is("culture");
632        vd.maybe_is("mother");
633        vd.maybe_is("father");
634        vd.maybe_is("parents");
635        vd.item(Item::Ethnicity);
636    });
637    // TODO: Find out the syntax of this. Docs are unclear, no examples in vanilla.
638    vd.field_block("ethnicities");
639    vd.field_script_value("diplomacy", sc);
640    vd.field_script_value("intrigue", sc);
641    vd.field_script_value("martial", sc);
642    vd.field_script_value("learning", sc);
643    vd.field_script_value("prowess", sc);
644    vd.field_script_value("stewardship", sc);
645    vd.field_validated_key_block("after_creation", |key, block, data| {
646        sc.open_scope(Scopes::Character, key.clone());
647        validate_effect(block, data, sc, Tooltipped::No); // TODO: verify
648        sc.close();
649    });
650}
651
652pub fn validate_create_character_memory(
653    key: &Token,
654    block: &Block,
655    _data: &Everything,
656    sc: &mut ScopeContext,
657    mut vd: Validator,
658    _tooltipped: Tooltipped,
659) {
660    vd.req_field("type");
661    vd.field_item("type", Item::MemoryType);
662    // TODO: also check that all participants are specified
663    vd.field_validated_block("participants", |b, data| {
664        let mut vd = Validator::new(b, data);
665        let memtype = block.get_field_value("type");
666        vd.unknown_value_fields(|key, token| {
667            if let Some(memtype) = memtype {
668                if !data.item_has_property(Item::MemoryType, memtype.as_str(), key.as_str()) {
669                    let msg =
670                        format!("memory type `{memtype}` does not define participant `{key}`");
671                    warn(ErrorKey::Validation).msg(msg).loc(key).push();
672                }
673            }
674            validate_target_ok_this(token, data, sc, Scopes::Character);
675        });
676    });
677    vd.field_validated_block_sc("duration", sc, validate_duration);
678    sc.define_name("new_memory", Scopes::CharacterMemory, key);
679}
680
681pub fn validate_create_confederation(
682    key: &Token,
683    _block: &Block,
684    _data: &Everything,
685    sc: &mut ScopeContext,
686    mut vd: Validator,
687    _tooltipped: Tooltipped,
688) {
689    vd.req_field("name");
690    vd.field_validated_sc("name", sc, validate_desc);
691    vd.field_target("type", sc, Scopes::ConfederationType);
692    vd.field_target("leader", sc, Scopes::DynastyHouse);
693    sc.define_name("new_confederation", Scopes::Confederation, key);
694}
695
696pub fn validate_create_dynamic_title(
697    key: &Token,
698    _block: &Block,
699    _data: &Everything,
700    sc: &mut ScopeContext,
701    mut vd: Validator,
702    _tooltipped: Tooltipped,
703) {
704    vd.req_field("tier");
705    vd.req_field("name");
706    vd.field_choice("tier", &["duchy", "kingdom", "empire", "hegemony"]);
707    vd.field_validated_sc("name", sc, validate_desc);
708    vd.advice_field("adjective", "changed to adj in 1.13");
709    vd.field_validated_sc("adj", sc, validate_desc);
710    vd.field_validated_sc("pre", sc, validate_desc);
711    vd.field_validated_sc("article", sc, validate_desc);
712    sc.define_name("new_title", Scopes::LandedTitle, key);
713}
714
715pub fn validate_create_nomad_title(
716    _key: &Token,
717    _block: &Block,
718    _data: &Everything,
719    sc: &mut ScopeContext,
720    mut vd: Validator,
721    _tooltipped: Tooltipped,
722) {
723    vd.field_validated_sc("name", sc, validate_desc);
724    vd.field_validated_sc("prefix", sc, validate_desc);
725    vd.field_validated_sc("adjective", sc, validate_desc);
726    vd.field_target("holder", sc, Scopes::Character);
727    vd.field_item("government", Item::GovernmentType);
728    if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
729        sc.define_name_token(name.as_str(), Scopes::LandedTitle, name, Temporary::No);
730    }
731}
732
733pub fn validate_create_holy_order(
734    _key: &Token,
735    _block: &Block,
736    _data: &Everything,
737    sc: &mut ScopeContext,
738    mut vd: Validator,
739    _tooltipped: Tooltipped,
740) {
741    vd.req_field("leader");
742    vd.req_field("capital");
743    vd.field_target("leader", sc, Scopes::Character);
744    vd.field_target("capital", sc, Scopes::LandedTitle);
745    vd.field_item("name", Item::Localization);
746    vd.field_item("coat_of_arms", Item::Coa);
747    if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
748        sc.define_name_token(name.as_str(), Scopes::HolyOrder, name, Temporary::No);
749    }
750    if let Some(name) = vd.field_identifier("save_temporary_scope_as", "scope name") {
751        sc.define_name_token(name.as_str(), Scopes::HolyOrder, name, Temporary::Yes);
752    }
753}
754
755pub fn validate_create_title_and_vassal_change(
756    _key: &Token,
757    _block: &Block,
758    _data: &Everything,
759    sc: &mut ScopeContext,
760    mut vd: Validator,
761    _tooltipped: Tooltipped,
762) {
763    vd.req_field("type");
764    vd.req_field("save_scope_as");
765    if let Some(history_type) = vd.field_value("type") {
766        let valid_types: Vec<_> = TITLE_HISTORY_TYPES
767            .iter()
768            .filter(|t| !BANNED_TITLE_HISTORY_TYPES.contains(t))
769            .copied()
770            .collect();
771        if BANNED_TITLE_HISTORY_TYPES.contains(&history_type.as_str()) {
772            let msg = format!(
773                "types {} cannot be used from script",
774                stringify_list(BANNED_TITLE_HISTORY_TYPES)
775            );
776            let info = format!("choose_from {}", stringify_choices(&valid_types));
777            err(ErrorKey::Choice).msg(msg).info(info).loc(history_type).push();
778        } else {
779            vd.field_choice("type", &valid_types);
780        }
781    }
782    if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
783        sc.define_name_token(name.as_str(), Scopes::TitleAndVassalChange, name, Temporary::No);
784    }
785    vd.field_bool("add_claim_on_loss");
786}
787
788pub fn validate_delay_travel_plan(
789    _key: &Token,
790    _block: &Block,
791    _data: &Everything,
792    sc: &mut ScopeContext,
793    mut vd: Validator,
794    _tooltipped: Tooltipped,
795) {
796    vd.field_bool("add");
797    validate_optional_duration(&mut vd, sc);
798}
799
800pub fn validate_divide_war_chest(
801    _key: &Token,
802    _block: &Block,
803    _data: &Everything,
804    sc: &mut ScopeContext,
805    mut vd: Validator,
806    _tooltipped: Tooltipped,
807) {
808    vd.field_bool("defenders");
809    vd.field_script_value("fraction", sc);
810    vd.field_bool("gold");
811    vd.field_bool("piety");
812    vd.field_bool("prestige");
813}
814
815pub fn validate_duel(
816    key: &Token,
817    block: &Block,
818    data: &Everything,
819    sc: &mut ScopeContext,
820    mut vd: Validator,
821    tooltipped: Tooltipped,
822    special_tokens: &mut SpecialTokens,
823) -> bool {
824    vd.field_item("skill", Item::Skill);
825    vd.field_list_items("skills", Item::Skill);
826    vd.field_target("target", sc, Scopes::Character);
827    vd.field_script_value("value", sc);
828    if let Some(value) = vd.field_value("localization") {
829        validate_effect_localization(value, data, tooltipped);
830    }
831    vd.field_validated_value("challenge_variable", |_, mut vd| {
832        vd.identifier("variable name");
833        let loca = format!("{}_name", vd.value());
834        data.verify_exists_implied(Item::Localization, &loca, vd.value());
835        vd.accept();
836    });
837    vd.field_validated_list("challenge_variables", |token, data| {
838        validate_identifier(token, "variable name", Severity::Error);
839        let loca = format!("{token}_name");
840        data.verify_exists_implied(Item::Localization, &loca, token);
841    });
842    sc.define_name("duel_value", Scopes::Value, key);
843    let has_tooltip = validate_random_list(key, block, data, sc, vd, tooltipped, special_tokens);
844    if has_tooltip {
845        special_tokens.insert(key);
846    }
847    has_tooltip
848}
849
850pub fn validate_faction_start_war(
851    _key: &Token,
852    _block: &Block,
853    _data: &Everything,
854    sc: &mut ScopeContext,
855    mut vd: Validator,
856    _tooltipped: Tooltipped,
857) {
858    vd.field_target("title", sc, Scopes::LandedTitle);
859}
860
861pub fn validate_force_add_to_agent_slot(
862    _key: &Token,
863    _block: &Block,
864    _data: &Everything,
865    sc: &mut ScopeContext,
866    mut vd: Validator,
867    _tooltipped: Tooltipped,
868) {
869    vd.field_target("agent_slot", sc, Scopes::AgentSlot);
870    validate_optional_duration(&mut vd, sc);
871}
872
873pub fn validate_force_vote_as(
874    _key: &Token,
875    _block: &Block,
876    _data: &Everything,
877    sc: &mut ScopeContext,
878    mut vd: Validator,
879    _tooltipped: Tooltipped,
880) {
881    vd.field_target("target", sc, Scopes::Character);
882    validate_optional_duration(&mut vd, sc);
883}
884
885pub fn validate_imprison(
886    _key: &Token,
887    _block: &Block,
888    _data: &Everything,
889    sc: &mut ScopeContext,
890    mut vd: Validator,
891    _tooltipped: Tooltipped,
892) {
893    vd.field_target("target", sc, Scopes::Character);
894    vd.field_item("type", Item::PrisonType);
895    // The docs also have a "reason" key, but no indication what it is
896}
897
898pub fn validate_join_faction_forced(
899    _key: &Token,
900    _block: &Block,
901    _data: &Everything,
902    sc: &mut ScopeContext,
903    mut vd: Validator,
904    _tooltipped: Tooltipped,
905) {
906    vd.field_target("faction", sc, Scopes::Faction);
907    vd.field_target("forced_by", sc, Scopes::Character);
908    validate_optional_duration(&mut vd, sc);
909}
910
911pub fn validate_make_pregnant(
912    _key: &Token,
913    _block: &Block,
914    _data: &Everything,
915    sc: &mut ScopeContext,
916    mut vd: Validator,
917    _tooltipped: Tooltipped,
918) {
919    vd.field_target("father", sc, Scopes::Character);
920    vd.field_integer("number_of_children");
921    vd.field_bool("known_bastard");
922}
923
924pub fn validate_move_budget(
925    key: &Token,
926    _block: &Block,
927    _data: &Everything,
928    sc: &mut ScopeContext,
929    mut vd: Validator,
930    _tooltipped: Tooltipped,
931) {
932    if key.is("move_budget_gold") {
933        vd.field_script_value_no_breakdown("gold", sc);
934    } else if key.is("move_budget_treasury") {
935        vd.field_script_value_no_breakdown("treasury", sc);
936    }
937    let choices = &["budget_war_chest", "budget_reserved", "budget_short_term", "budget_long_term"];
938    vd.field_choice("from", choices);
939    vd.field_choice("to", choices);
940}
941
942pub fn validate_open_interaction_window(
943    key: &Token,
944    _block: &Block,
945    _data: &Everything,
946    sc: &mut ScopeContext,
947    mut vd: Validator,
948    _tooltipped: Tooltipped,
949) {
950    let caller = key.as_str().to_ascii_lowercase();
951    vd.req_field("interaction");
952    vd.req_field("actor");
953    vd.req_field("recipient");
954    vd.field_value("interaction"); // TODO
955    vd.field_bool("redirect");
956    vd.field_target_ok_this("actor", sc, Scopes::Character);
957    vd.field_target_ok_this("recipient", sc, Scopes::Character);
958    vd.field_target_ok_this("secondary_actor", sc, Scopes::Character);
959    vd.field_target_ok_this("secondary_recipient", sc, Scopes::Character);
960    if caller == "open_interaction_window" {
961        vd.field_target("target_title", sc, Scopes::LandedTitle);
962    }
963    if caller == "run_interaction" {
964        vd.field_choice("execute_threshold", &["accept", "maybe", "decline"]);
965        vd.field_choice("send_threshold", &["accept", "maybe", "decline"]);
966    }
967}
968
969pub fn validate_pay_gold(
970    _key: &Token,
971    _block: &Block,
972    _data: &Everything,
973    sc: &mut ScopeContext,
974    mut vd: Validator,
975    _tooltipped: Tooltipped,
976) {
977    vd.req_field("target");
978    vd.field_target("target", sc, Scopes::Character);
979    vd.field_script_value("gold", sc);
980    // undocumented; it means multiply the gold amount by (whose?) yearly income
981    vd.field_bool("yearly_income");
982}
983
984pub fn validate_pay_income(
985    _key: &Token,
986    _block: &Block,
987    _data: &Everything,
988    sc: &mut ScopeContext,
989    mut vd: Validator,
990    _tooltipped: Tooltipped,
991) {
992    vd.req_field("target");
993    vd.field_target("target", sc, Scopes::Character);
994    validate_optional_duration(&mut vd, sc);
995}
996
997pub fn validate_current_phase_guest_subset(
998    _key: &Token,
999    _block: &Block,
1000    _data: &Everything,
1001    sc: &mut ScopeContext,
1002    mut vd: Validator,
1003    _tooltipped: Tooltipped,
1004) {
1005    vd.req_field("name");
1006    vd.req_field("target");
1007    vd.field_item("name", Item::GuestSubset);
1008    vd.field_target("target", sc, Scopes::Character);
1009}
1010
1011pub fn validate_remove_opinion(
1012    _key: &Token,
1013    _block: &Block,
1014    _data: &Everything,
1015    sc: &mut ScopeContext,
1016    mut vd: Validator,
1017    _tooltipped: Tooltipped,
1018) {
1019    vd.req_field("target");
1020    vd.req_field("modifier");
1021    vd.field_target("target", sc, Scopes::Character);
1022    vd.field_item("modifier", Item::OpinionModifier);
1023    vd.field_bool("single");
1024}
1025
1026pub fn validate_replace_court_position(
1027    _key: &Token,
1028    _block: &Block,
1029    _data: &Everything,
1030    sc: &mut ScopeContext,
1031    mut vd: Validator,
1032    _tooltipped: Tooltipped,
1033) {
1034    vd.req_field("recipient");
1035    vd.req_field("court_position");
1036    vd.field_target("recipient", sc, Scopes::Character);
1037    vd.field_target("holder", sc, Scopes::Character);
1038    vd.field_item("court_position", Item::CourtPosition);
1039}
1040
1041pub fn validate_revoke_court_position(
1042    _key: &Token,
1043    _block: &Block,
1044    _data: &Everything,
1045    sc: &mut ScopeContext,
1046    mut vd: Validator,
1047    _tooltipped: Tooltipped,
1048) {
1049    vd.req_field("court_position");
1050    vd.field_item("court_position", Item::CourtPosition);
1051    vd.field_target("recipient", sc, Scopes::Character);
1052    vd.field_target("holder", sc, Scopes::Character);
1053}
1054
1055pub fn validate_save_opinion_value(
1056    key: &Token,
1057    _block: &Block,
1058    _data: &Everything,
1059    sc: &mut ScopeContext,
1060    mut vd: Validator,
1061    _tooltipped: Tooltipped,
1062) {
1063    vd.req_field("name");
1064    vd.req_field("target");
1065    let temp =
1066        if key.is("save_temporary_opinion_value_as") { Temporary::Yes } else { Temporary::No };
1067    if let Some(name) = vd.field_value("name") {
1068        sc.define_name_token(name.as_str(), Scopes::Value, name, temp);
1069    }
1070    vd.field_target("target", sc, Scopes::Character);
1071}
1072
1073pub fn validate_scheme_freeze(
1074    _key: &Token,
1075    _block: &Block,
1076    _data: &Everything,
1077    sc: &mut ScopeContext,
1078    mut vd: Validator,
1079    _tooltipped: Tooltipped,
1080) {
1081    vd.field_item("reason", Item::Localization);
1082    validate_optional_duration(&mut vd, sc);
1083}
1084
1085pub fn validate_set_council_task(
1086    _key: &Token,
1087    _block: &Block,
1088    _data: &Everything,
1089    sc: &mut ScopeContext,
1090    mut vd: Validator,
1091    _tooltipped: Tooltipped,
1092) {
1093    vd.req_field("task_type");
1094    // TODO: figure out for which task types `target` is required
1095    vd.field_item("task_type", Item::CouncilTask);
1096    // This has been verified as of 1.9.2, it does require a Province here and not a LandedTitle
1097    vd.field_target("target", sc, Scopes::Character | Scopes::Province);
1098}
1099
1100pub fn validate_set_culture_name(
1101    _key: &Token,
1102    _block: &Block,
1103    _data: &Everything,
1104    sc: &mut ScopeContext,
1105    mut vd: Validator,
1106    _tooltipped: Tooltipped,
1107) {
1108    vd.req_field("noun");
1109    vd.field_validated_sc("noun", sc, validate_desc);
1110    vd.field_validated_sc("collective_noun", sc, validate_desc);
1111    vd.field_validated_sc("prefix", sc, validate_desc);
1112}
1113
1114pub fn validate_set_death_reason(
1115    _key: &Token,
1116    _block: &Block,
1117    _data: &Everything,
1118    sc: &mut ScopeContext,
1119    mut vd: Validator,
1120    _tooltipped: Tooltipped,
1121) {
1122    vd.req_field("death_reason");
1123    vd.field_item("death_reason", Item::DeathReason);
1124    vd.field_target("killer", sc, Scopes::Character);
1125    vd.field_target("artifact", sc, Scopes::Artifact);
1126}
1127
1128pub fn validate_set_ghw_target(
1129    key: &Token,
1130    _block: &Block,
1131    _data: &Everything,
1132    sc: &mut ScopeContext,
1133    mut vd: Validator,
1134    _tooltipped: Tooltipped,
1135) {
1136    let caller = key.as_str().to_ascii_lowercase();
1137    vd.req_field("target_character");
1138    vd.req_field("target_title");
1139    vd.field_target("target_character", sc, Scopes::Character);
1140    vd.field_target("target_title", sc, Scopes::LandedTitle);
1141    if caller == "start_great_holy_war" {
1142        vd.field_script_value("delay", sc);
1143        vd.field_target("war", sc, Scopes::War);
1144    }
1145}
1146
1147pub fn validate_set_legend_chapter(
1148    _key: &Token,
1149    _block: &Block,
1150    _data: &Everything,
1151    _sc: &mut ScopeContext,
1152    mut vd: Validator,
1153    _tooltipped: Tooltipped,
1154) {
1155    vd.field_item("name", Item::LegendChapter);
1156    vd.field_item("localization_key", Item::Localization);
1157}
1158
1159pub fn validate_set_legend_property(
1160    _key: &Token,
1161    _block: &Block,
1162    _data: &Everything,
1163    sc: &mut ScopeContext,
1164    mut vd: Validator,
1165    _tooltipped: Tooltipped,
1166) {
1167    vd.field_item("name", Item::LegendProperty);
1168    // TODO: look up possible scope types from the legend properties
1169    vd.field_target("target", sc, Scopes::all());
1170}
1171
1172pub fn validate_setup_cb(
1173    key: &Token,
1174    _block: &Block,
1175    _data: &Everything,
1176    sc: &mut ScopeContext,
1177    mut vd: Validator,
1178    _tooltipped: Tooltipped,
1179) {
1180    let caller = key.as_str().to_ascii_lowercase();
1181    vd.req_field("attacker");
1182    vd.req_field("defender");
1183    // vd.req_field("change"); is optional if you just want it to set scope:cb_prestige_factor
1184    vd.field_target("attacker", sc, Scopes::Character);
1185    vd.field_target("defender", sc, Scopes::Character);
1186    vd.field_target("change", sc, Scopes::TitleAndVassalChange);
1187    vd.field_bool("victory");
1188    if caller == "setup_claim_cb" {
1189        vd.req_field("claimant");
1190        vd.field_target("claimant", sc, Scopes::Character);
1191        vd.field_bool("take_occupied");
1192        vd.field_bool("civil_war");
1193        vd.field_choice("titles", &["target_titles", "faction_titles"]);
1194    } else if caller == "setup_de_jure_cb" {
1195        vd.field_target("title", sc, Scopes::LandedTitle);
1196    } else if caller == "setup_invasion_cb" {
1197        vd.field_identifier("titles", "list name");
1198        vd.field_bool("take_occupied");
1199        vd.field_target("claimant", sc, Scopes::Character);
1200    }
1201    sc.define_name("cb_prestige_factor", Scopes::Value, key);
1202}
1203
1204pub fn validate_spawn_army(
1205    _key: &Token,
1206    _block: &Block,
1207    _data: &Everything,
1208    sc: &mut ScopeContext,
1209    mut vd: Validator,
1210    _tooltipped: Tooltipped,
1211) {
1212    // TODO: either levies or men_at_arms
1213    vd.req_field("location");
1214    vd.field_script_value("levies", sc);
1215    vd.multi_field_validated_block("men_at_arms", |b, data| {
1216        let mut vd = Validator::new(b, data);
1217        vd.req_field("type");
1218        vd.field_item("type", Item::MenAtArms);
1219        vd.field_script_value("men", sc);
1220        vd.field_script_value("stacks", sc);
1221        vd.field_bool("inheritable"); // undocumented
1222    });
1223    vd.field_target("location", sc, Scopes::Province);
1224    vd.field_target("origin", sc, Scopes::Province);
1225    vd.field_target("war", sc, Scopes::War);
1226    vd.field_bool("war_keep_on_attacker_victory");
1227    vd.field_bool("inheritable");
1228    vd.field_bool("uses_supply");
1229    vd.field_target("army", sc, Scopes::Army);
1230    if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
1231        sc.define_name_token(name.as_str(), Scopes::Army, name, Temporary::No);
1232    }
1233    if let Some(name) = vd.field_identifier("save_temporary_scope_as", "scope name") {
1234        sc.define_name_token(name.as_str(), Scopes::Army, name, Temporary::Yes);
1235    }
1236    vd.field_validated_sc("name", sc, validate_desc);
1237}
1238
1239pub fn validate_start_scheme(
1240    _key: &Token,
1241    _block: &Block,
1242    _data: &Everything,
1243    sc: &mut ScopeContext,
1244    mut vd: Validator,
1245    _tooltipped: Tooltipped,
1246) {
1247    vd.req_field("type");
1248    vd.req_field_one_of(&[
1249        "target_character",
1250        "target_title",
1251        "target_culture",
1252        "target_faith",
1253        "targets_nothing",
1254    ]);
1255    vd.field_item("type", Item::Scheme);
1256    vd.field_target("contract", sc, Scopes::TaskContract);
1257    vd.advice_field("target", "replaced with target_character in 1.13");
1258    vd.field_target("target_character", sc, Scopes::Character);
1259    vd.field_target("target_title", sc, Scopes::LandedTitle);
1260    vd.field_target("target_culture", sc, Scopes::Culture);
1261    vd.field_target("target_faith", sc, Scopes::Faith);
1262    vd.field_bool("targets_nothing");
1263    if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
1264        sc.define_name_token(name.as_str(), Scopes::Scheme, name, Temporary::No);
1265    }
1266
1267    // undocumented
1268
1269    // TODO: verify if still valid in 1.13
1270    vd.field_target("artifact", sc, Scopes::Artifact);
1271}
1272
1273pub fn validate_start_struggle(
1274    _key: &Token,
1275    _block: &Block,
1276    _data: &Everything,
1277    _sc: &mut ScopeContext,
1278    mut vd: Validator,
1279    _tooltipped: Tooltipped,
1280) {
1281    vd.req_field("struggle_type");
1282    vd.req_field("start_phase");
1283    vd.field_item("struggle_type", Item::Struggle);
1284    vd.field_item("start_phase", Item::StrugglePhase);
1285}
1286
1287pub fn validate_start_travel_plan(
1288    _key: &Token,
1289    _block: &Block,
1290    data: &Everything,
1291    sc: &mut ScopeContext,
1292    mut vd: Validator,
1293    _tooltipped: Tooltipped,
1294) {
1295    vd.req_field("destination");
1296    for token in vd.multi_field_value("destination") {
1297        validate_target(token, data, sc, Scopes::Province);
1298    }
1299    vd.field_target("travel_leader", sc, Scopes::Character);
1300    for token in vd.multi_field_value("companion") {
1301        validate_target(token, data, sc, Scopes::Character);
1302    }
1303    vd.field_bool("travel_with_domicile");
1304    vd.field_bool("can_cancel_planning");
1305    vd.field_bool("players_use_planner");
1306    vd.field_bool("return_trip");
1307    vd.field_event("on_arrival_event", sc);
1308    vd.field_action("on_arrival_on_action", sc);
1309    vd.field_event("on_start_event", sc);
1310    vd.field_action("on_start_on_action", sc);
1311    vd.field_event("on_travel_planner_cancel_event", sc);
1312    vd.field_action("on_travel_planner_cancel_on_action", sc);
1313    vd.field_choice("on_arrival_destinations", &["all", "first", "last", "all_but_last"]);
1314}
1315
1316pub fn validate_start_war(
1317    _key: &Token,
1318    _block: &Block,
1319    data: &Everything,
1320    sc: &mut ScopeContext,
1321    mut vd: Validator,
1322    _tooltipped: Tooltipped,
1323) {
1324    vd.field_item("casus_belli", Item::CasusBelli);
1325    vd.field_item("cb", Item::CasusBelli);
1326    vd.field_target("target", sc, Scopes::Character);
1327    vd.field_target_ok_this("claimant", sc, Scopes::Character);
1328    for token in vd.multi_field_value("target_title") {
1329        validate_target(token, data, sc, Scopes::LandedTitle);
1330    }
1331}
1332
1333pub fn validate_stress_impact(
1334    _key: &Token,
1335    _block: &Block,
1336    data: &Everything,
1337    sc: &mut ScopeContext,
1338    mut vd: Validator,
1339    _tooltipped: Tooltipped,
1340) {
1341    vd.field_script_value("base", sc);
1342    vd.unknown_fields(|token, bv| {
1343        data.verify_exists(Item::Trait, token);
1344        validate_non_dynamic_script_value(bv, data);
1345    });
1346}
1347
1348pub fn validate_try_create_important_action(
1349    _key: &Token,
1350    _block: &Block,
1351    data: &Everything,
1352    sc: &mut ScopeContext,
1353    mut vd: Validator,
1354    _tooltipped: Tooltipped,
1355) {
1356    vd.req_field("important_action_type");
1357    vd.field_item("important_action_type", Item::ImportantAction);
1358    vd.unknown_value_fields(|_, value| {
1359        validate_target_ok_this(value, data, sc, Scopes::all_but_none());
1360    });
1361}
1362
1363pub fn validate_try_create_suggestion(
1364    _key: &Token,
1365    _block: &Block,
1366    _data: &Everything,
1367    sc: &mut ScopeContext,
1368    mut vd: Validator,
1369    _tooltipped: Tooltipped,
1370) {
1371    vd.req_field("suggestion_type");
1372    vd.field_item("suggestion_type", Item::Suggestion);
1373    vd.field_target_ok_this("actor", sc, Scopes::Character);
1374    vd.field_target_ok_this("recipient", sc, Scopes::Character);
1375    vd.field_target_ok_this("secondary_actor", sc, Scopes::Character);
1376    vd.field_target_ok_this("secondary_recipient", sc, Scopes::Character);
1377    vd.field_target_ok_this("landed_title", sc, Scopes::LandedTitle);
1378}
1379
1380pub fn validate_contract_set_obligation_level(
1381    _key: &Token,
1382    _block: &Block,
1383    data: &Everything,
1384    sc: &mut ScopeContext,
1385    mut vd: Validator,
1386    _tooltipped: Tooltipped,
1387) {
1388    vd.req_field("type");
1389    vd.req_field("level");
1390    if let Some(token) = vd.field_value("type") {
1391        if !data.item_exists(Item::SubjectContract, token.as_str()) {
1392            validate_target(token, data, sc, Scopes::VassalContract);
1393        }
1394    }
1395    if let Some(token) = vd.field_value("level") {
1396        if !token.is_integer()
1397            && !data.item_exists(Item::SubjectContractObligationLevel, token.as_str())
1398        {
1399            validate_target(token, data, sc, Scopes::VassalObligationLevel);
1400        }
1401    }
1402}
1403
1404pub fn validate_add_artifact_modifier(
1405    _key: &Token,
1406    mut vd: ValueValidator,
1407    _sc: &mut ScopeContext,
1408    _tooltipped: Tooltipped,
1409) {
1410    vd.item(Item::Modifier);
1411    // TODO validate `property_use`
1412    // TODO: this causes hundreds of warnings. Probably because the tooltip tracking isn't smart enough to figure out
1413    // things like "scope:newly_created_artifact does not exist yet at tooltipping time, so the body of the if won't
1414    // be tooltipped here".
1415    //
1416    // if tooltipped.is_tooltipped() {
1417    //     data.verify_exists(Item::Localization, value);
1418    // }
1419}
1420
1421pub fn validate_generate_coa(
1422    _key: &Token,
1423    mut vd: ValueValidator,
1424    _sc: &mut ScopeContext,
1425    _tooltipped: Tooltipped,
1426) {
1427    vd.maybe_is("yes");
1428    vd.item(Item::CoaTemplateList);
1429}
1430
1431pub fn validate_set_coa(
1432    _key: &Token,
1433    mut vd: ValueValidator,
1434    sc: &mut ScopeContext,
1435    _tooltipped: Tooltipped,
1436) {
1437    let options = Scopes::LandedTitle | Scopes::Dynasty | Scopes::DynastyHouse;
1438    vd.item_or_target(sc, Item::Coa, options);
1439}
1440
1441pub fn validate_set_focus(
1442    _key: &Token,
1443    mut vd: ValueValidator,
1444    _sc: &mut ScopeContext,
1445    _tooltipped: Tooltipped,
1446) {
1447    vd.maybe_is("no");
1448    vd.item(Item::Focus);
1449}
1450
1451pub fn validate_set_title_name(
1452    _key: &Token,
1453    mut vd: ValueValidator,
1454    _sc: &mut ScopeContext,
1455    _tooltipped: Tooltipped,
1456) {
1457    vd.item(Item::Localization);
1458    vd.item_used_with_suffix(Item::Localization, "_adj");
1459}
1460
1461pub fn validate_activate_struggle_catalyst(
1462    _key: &Token,
1463    bv: &BV,
1464    data: &Everything,
1465    sc: &mut ScopeContext,
1466    _tooltipped: Tooltipped,
1467) {
1468    match bv {
1469        BV::Value(token) => data.verify_exists(Item::Catalyst, token),
1470        BV::Block(block) => {
1471            let mut vd = Validator::new(block, data);
1472            vd.set_case_sensitive(false);
1473            vd.req_field("catalyst");
1474            vd.req_field("character");
1475            vd.field_item("catalyst", Item::Catalyst);
1476            vd.field_target("character", sc, Scopes::Character);
1477        }
1478    }
1479}
1480
1481pub fn validate_add_character_flag(
1482    _key: &Token,
1483    bv: &BV,
1484    data: &Everything,
1485    sc: &mut ScopeContext,
1486    _tooltipped: Tooltipped,
1487) {
1488    match bv {
1489        BV::Value(_token) => (),
1490        BV::Block(block) => {
1491            let mut vd = Validator::new(block, data);
1492            vd.set_case_sensitive(false);
1493            vd.req_field("flag");
1494            vd.multi_field_value("flag");
1495            validate_optional_duration(&mut vd, sc);
1496        }
1497    }
1498}
1499
1500pub fn validate_add_dead_character_flag(
1501    _key: &Token,
1502    block: &Block,
1503    _data: &Everything,
1504    sc: &mut ScopeContext,
1505    mut vd: Validator,
1506    _tooltipped: Tooltipped,
1507) {
1508    vd.set_case_sensitive(false);
1509    vd.req_field("flag");
1510    vd.multi_field_value("flag");
1511    validate_mandatory_duration(block, &mut vd, sc);
1512}
1513
1514pub fn validate_begin_create_holding(
1515    _key: &Token,
1516    bv: &BV,
1517    data: &Everything,
1518    sc: &mut ScopeContext,
1519    _tooltipped: Tooltipped,
1520) {
1521    match bv {
1522        BV::Value(token) => data.verify_exists(Item::HoldingType, token),
1523        BV::Block(block) => {
1524            let mut vd = Validator::new(block, data);
1525            vd.set_case_sensitive(false);
1526            vd.req_field("type");
1527            vd.field_item("type", Item::HoldingType);
1528            vd.field_validated_block("refund_cost", |b, data| {
1529                let mut vd = Validator::new(b, data);
1530                vd.set_case_sensitive(false);
1531                vd.field_script_value("gold", sc);
1532                vd.field_script_value("prestige", sc);
1533                vd.field_script_value("piety", sc);
1534            });
1535        }
1536    }
1537}
1538
1539pub fn validate_change_first_name(
1540    _key: &Token,
1541    bv: &BV,
1542    data: &Everything,
1543    sc: &mut ScopeContext,
1544    _tooltipped: Tooltipped,
1545) {
1546    // TODO: docs now say change_first_name = <dynamic_description> but no uses in vanilla
1547    match bv {
1548        BV::Value(token) => {
1549            if data.item_exists(Item::Localization, token.as_str()) {
1550                data.mark_used(Item::Localization, token.as_str());
1551            } else {
1552                validate_target(token, data, sc, Scopes::Flag);
1553            }
1554        }
1555        BV::Block(block) => {
1556            let mut vd = Validator::new(block, data);
1557            vd.set_case_sensitive(false);
1558            vd.req_field("template_character");
1559            vd.field_target("template_character", sc, Scopes::Character);
1560        }
1561    }
1562}
1563
1564pub fn validate_close_view(
1565    _key: &Token,
1566    bv: &BV,
1567    data: &Everything,
1568    sc: &mut ScopeContext,
1569    _tooltipped: Tooltipped,
1570) {
1571    match bv {
1572        BV::Value(_token) => (), // TODO
1573        BV::Block(block) => {
1574            let mut vd = Validator::new(block, data);
1575            vd.set_case_sensitive(false);
1576            vd.req_field("view");
1577            vd.field_value("view"); // TODO
1578            vd.field_target("player", sc, Scopes::Character);
1579        }
1580    }
1581}
1582
1583pub fn validate_create_alliance(
1584    _key: &Token,
1585    bv: &BV,
1586    data: &Everything,
1587    sc: &mut ScopeContext,
1588    _tooltipped: Tooltipped,
1589) {
1590    match bv {
1591        BV::Value(token) => {
1592            validate_target(token, data, sc, Scopes::Character);
1593        }
1594        BV::Block(block) => {
1595            let mut vd = Validator::new(block, data);
1596            vd.set_case_sensitive(false);
1597            vd.req_field("target");
1598            vd.field_target("target", sc, Scopes::Character);
1599            vd.field_target_ok_this("allied_through_owner", sc, Scopes::Character);
1600            vd.field_target("allied_through_target", sc, Scopes::Character);
1601        }
1602    }
1603}
1604
1605pub fn validate_create_epidemic_outbreak(
1606    _key: &Token,
1607    _block: &Block,
1608    _data: &Everything,
1609    sc: &mut ScopeContext,
1610    mut vd: Validator,
1611    _tooltipped: Tooltipped,
1612) {
1613    vd.req_field("type");
1614    vd.req_field("intensity");
1615    vd.field_item("type", Item::EpidemicType);
1616    vd.field_choice("intensity", OUTBREAK_INTENSITIES);
1617    if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
1618        sc.define_name_token(name.as_str(), Scopes::Epidemic, name, Temporary::No);
1619    }
1620}
1621
1622pub fn validate_create_inspiration(
1623    _key: &Token,
1624    bv: &BV,
1625    data: &Everything,
1626    sc: &mut ScopeContext,
1627    _tooltipped: Tooltipped,
1628) {
1629    match bv {
1630        BV::Value(token) => data.verify_exists(Item::Inspiration, token),
1631        BV::Block(block) => {
1632            let mut vd = Validator::new(block, data);
1633            vd.set_case_sensitive(false);
1634            vd.req_field("type");
1635            vd.field_item("type", Item::Inspiration);
1636            vd.field_script_value("gold", sc);
1637        }
1638    }
1639}
1640
1641pub fn validate_create_story(
1642    _key: &Token,
1643    bv: &BV,
1644    data: &Everything,
1645    sc: &mut ScopeContext,
1646    _tooltipped: Tooltipped,
1647) {
1648    match bv {
1649        BV::Value(token) => data.verify_exists(Item::Story, token),
1650        BV::Block(block) => {
1651            let mut vd = Validator::new(block, data);
1652            vd.set_case_sensitive(false);
1653            vd.req_field("type");
1654            vd.field_item("type", Item::Story);
1655            if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
1656                sc.define_name_token(name.as_str(), Scopes::StoryCycle, name, Temporary::No);
1657            }
1658            if let Some(name) = vd.field_identifier("save_temporary_scope_as", "scope name") {
1659                sc.define_name_token(name.as_str(), Scopes::StoryCycle, name, Temporary::Yes);
1660            }
1661        }
1662    }
1663}
1664
1665pub fn validate_death(
1666    _key: &Token,
1667    bv: &BV,
1668    data: &Everything,
1669    sc: &mut ScopeContext,
1670    _tooltipped: Tooltipped,
1671) {
1672    match bv {
1673        BV::Value(token) => {
1674            if !token.is("natural") {
1675                let msg = "expected `death = natural`";
1676                warn(ErrorKey::Validation).msg(msg).loc(token).push();
1677            }
1678        }
1679        BV::Block(block) => {
1680            let mut vd = Validator::new(block, data);
1681            vd.set_case_sensitive(false);
1682            vd.req_field("death_reason");
1683            vd.field_item("death_reason", Item::DeathReason);
1684            vd.field_target("killer", sc, Scopes::Character);
1685            vd.field_target("artifact", sc, Scopes::Artifact);
1686        }
1687    }
1688}
1689
1690pub fn validate_open_view(
1691    key: &Token,
1692    bv: &BV,
1693    data: &Everything,
1694    sc: &mut ScopeContext,
1695    _tooltipped: Tooltipped,
1696) {
1697    match bv {
1698        BV::Value(_token) => (), // TODO
1699        BV::Block(block) => {
1700            let mut vd = Validator::new(block, data);
1701            vd.set_case_sensitive(false);
1702            vd.req_field("view");
1703            vd.field_value("view"); // TODO
1704            vd.field_value("view_message"); // TODO
1705            vd.field_target("player", sc, Scopes::Character);
1706            if key.is("open_view_data") {
1707                vd.field_target("secondary_actor", sc, Scopes::Character); // undocumented
1708                vd.field_target("data", sc, Scopes::all_but_none()); // undocumented
1709            }
1710        }
1711    }
1712}
1713
1714pub fn validate_remove_courtier_or_guest(
1715    _key: &Token,
1716    bv: &BV,
1717    data: &Everything,
1718    sc: &mut ScopeContext,
1719    _tooltipped: Tooltipped,
1720) {
1721    match bv {
1722        BV::Value(token) => {
1723            validate_target(token, data, sc, Scopes::Character);
1724        }
1725        BV::Block(block) => {
1726            let mut vd = Validator::new(block, data);
1727            vd.set_case_sensitive(false);
1728            vd.req_field("character");
1729            vd.field_target("character", sc, Scopes::Character);
1730            vd.field_target("new_location", sc, Scopes::Province);
1731        }
1732    }
1733}
1734
1735pub fn validate_set_dead_character_variable(
1736    _key: &Token,
1737    block: &Block,
1738    _data: &Everything,
1739    sc: &mut ScopeContext,
1740    mut vd: Validator,
1741    _tooltipped: Tooltipped,
1742) {
1743    vd.req_field("name");
1744    vd.field_identifier("name", "variable name");
1745    vd.field_validated("value", |bv, data| match bv {
1746        BV::Value(token) => {
1747            validate_target_ok_this(token, data, sc, Scopes::all_but_none());
1748        }
1749        BV::Block(_) => validate_script_value(bv, data, sc),
1750    });
1751    validate_mandatory_duration(block, &mut vd, sc);
1752}
1753
1754pub fn validate_set_location(
1755    _key: &Token,
1756    bv: &BV,
1757    data: &Everything,
1758    sc: &mut ScopeContext,
1759    _tooltipped: Tooltipped,
1760) {
1761    match bv {
1762        BV::Value(token) => {
1763            validate_target(token, data, sc, Scopes::Province);
1764        }
1765        BV::Block(block) => {
1766            let mut vd = Validator::new(block, data);
1767            vd.set_case_sensitive(false);
1768            vd.req_field("location");
1769            vd.field_target("location", sc, Scopes::Province);
1770            vd.field_bool("stick_to_location");
1771        }
1772    }
1773}
1774
1775pub fn validate_set_owner(
1776    _key: &Token,
1777    bv: &BV,
1778    data: &Everything,
1779    sc: &mut ScopeContext,
1780    _tooltipped: Tooltipped,
1781) {
1782    match bv {
1783        BV::Value(token) => {
1784            validate_target(token, data, sc, Scopes::Character);
1785        }
1786        BV::Block(block) => {
1787            let mut vd = Validator::new(block, data);
1788            vd.set_case_sensitive(false);
1789            vd.req_field("target");
1790            vd.field_target("target", sc, Scopes::Character);
1791            vd.multi_field_validated_block_sc("history", sc, validate_artifact_history);
1792            vd.field_bool("generate_history");
1793        }
1794    }
1795}
1796
1797pub fn validate_set_relation(
1798    _key: &Token,
1799    bv: &BV,
1800    data: &Everything,
1801    sc: &mut ScopeContext,
1802    _tooltipped: Tooltipped,
1803) {
1804    match bv {
1805        BV::Value(token) => {
1806            validate_target(token, data, sc, Scopes::Character);
1807        }
1808        BV::Block(block) => {
1809            let mut vd = Validator::new(block, data);
1810            vd.set_case_sensitive(false);
1811            vd.req_field("target");
1812            // Sometimes both are used and I don't know what that means. TODO: verify
1813            // vd.req_field_one_of(&["reason", "copy_reason"]);
1814            vd.field_target("target", sc, Scopes::Character);
1815            // TODO: {reason}_corresponding is also a loca
1816            vd.field_item("reason", Item::Localization);
1817            vd.field_item("copy_reason", Item::Relation);
1818            vd.field_target("province", sc, Scopes::Province);
1819            vd.field_target("involved_character", sc, Scopes::Character);
1820        }
1821    }
1822}
1823
1824fn validate_artifact_history(block: &Block, data: &Everything, sc: &mut ScopeContext) {
1825    let mut vd = Validator::new(block, data);
1826    vd.set_case_sensitive(false);
1827    vd.req_field("type");
1828    vd.field_item("type", Item::ArtifactHistory);
1829    vd.field_date("date");
1830    vd.field_target("actor", sc, Scopes::Character);
1831    vd.field_target("recipient", sc, Scopes::Character);
1832    vd.field_target("location", sc, Scopes::Province);
1833}
1834
1835pub fn validate_end_struggle(
1836    _value: &Token,
1837    mut vd: ValueValidator,
1838    _sc: &mut ScopeContext,
1839    _tooltipped: Tooltipped,
1840) {
1841    vd.maybe_is("yes");
1842    vd.item(Item::Localization); // undocumented
1843}
1844
1845pub fn validate_create_legend(
1846    key: &Token,
1847    _block: &Block,
1848    data: &Everything,
1849    sc: &mut ScopeContext,
1850    mut vd: Validator,
1851    _tooltipped: Tooltipped,
1852) {
1853    vd.req_field("type");
1854    vd.field_item("type", Item::LegendType);
1855    vd.req_field("quality");
1856    vd.field_choice("quality", LEGEND_QUALITY);
1857    vd.req_field("chronicle");
1858    vd.req_field("properties");
1859    vd.field_item("chronicle", Item::LegendChronicle);
1860    if let Some(chronicle_token) = vd.field_value("chronicle").cloned() {
1861        data.verify_exists(Item::LegendChronicle, &chronicle_token);
1862
1863        if let Some((_, _, chronicle)) =
1864            data.get_item::<LegendChronicle>(Item::LegendChronicle, chronicle_token.as_str())
1865        {
1866            vd.field_validated_key_block("properties", |key, block, data| {
1867                let mut found_properties = TigerHashSet::default();
1868                let mut vd = Validator::new(block, data);
1869                vd.unknown_value_fields(|key, value| {
1870                    if let Some(scopes) = chronicle.properties.get(key).copied() {
1871                        found_properties.insert(key.clone());
1872                        validate_target(value, data, sc, scopes);
1873                    } else {
1874                        let msg =
1875                            format!("property {key} not found in {chronicle_token} chronicle");
1876                        err(ErrorKey::Validation).msg(msg).loc(key).push();
1877                    }
1878                });
1879                for property in chronicle.properties.keys() {
1880                    if !found_properties.contains(property) {
1881                        let msg = format!("chronicle property {property} missing from properties");
1882                        err(ErrorKey::Validation)
1883                            .msg(msg)
1884                            .loc(key)
1885                            .loc_msg(property, "defined here")
1886                            .push();
1887                    }
1888                }
1889            });
1890        }
1891    }
1892    // This validation function is used for both create_legend and create_legend_seed
1893    if key.is("create_legend") {
1894        vd.field_target("protagonist", sc, Scopes::Character);
1895        if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
1896            sc.define_name_token(name.as_str(), Scopes::Legend, name, Temporary::No);
1897        }
1898    }
1899}
1900
1901pub fn validate_change_maa_regiment_size(
1902    _key: &Token,
1903    bv: &BV,
1904    data: &Everything,
1905    sc: &mut ScopeContext,
1906    _tooltipped: Tooltipped,
1907) {
1908    match bv {
1909        BV::Value(_) => validate_script_value(bv, data, sc),
1910        BV::Block(block) => {
1911            let mut vd = Validator::new(block, data);
1912            vd.set_case_sensitive(false);
1913            vd.req_field("size");
1914            vd.field_script_value("size", sc);
1915            vd.field_bool("reinforce");
1916        }
1917    }
1918}
1919
1920pub fn validate_create_adventurer_title(
1921    _key: &Token,
1922    _block: &Block,
1923    _data: &Everything,
1924    sc: &mut ScopeContext,
1925    mut vd: Validator,
1926    _tooltipped: Tooltipped,
1927) {
1928    vd.req_field("name");
1929    vd.req_field("holder");
1930    vd.field_validated_sc("name", sc, validate_desc);
1931    vd.field_target("holder", sc, Scopes::Character);
1932    vd.field_validated_sc("prefix", sc, validate_desc);
1933    vd.field_validated_sc("adjective", sc, validate_desc);
1934    vd.field_item("government", Item::GovernmentType);
1935    if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
1936        sc.define_name_token(name.as_str(), Scopes::LandedTitle, name, Temporary::No);
1937    }
1938
1939    // undocumented
1940
1941    vd.field_validated_sc("article", sc, validate_desc);
1942}
1943
1944pub fn validate_start_best_war(
1945    _key: &Token,
1946    _block: &Block,
1947    _data: &Everything,
1948    _sc: &mut ScopeContext,
1949    mut vd: Validator,
1950    _tooltipped: Tooltipped,
1951) {
1952    fn sc_builder(key: &Token) -> ScopeContext {
1953        let mut sc = ScopeContext::new(Scopes::Character, key);
1954        sc.define_name("target_character", Scopes::Character, key);
1955        sc.define_name("target_title", Scopes::LandedTitle, key);
1956        sc.define_list("target_titles", Scopes::LandedTitle, key);
1957        sc.define_name("claimant", Scopes::Character, key);
1958        sc.define_name("casus_belli_type", Scopes::CasusBelliType, key);
1959        sc.define_name("has_hostage", Scopes::Bool, key);
1960        sc.define_name("score", Scopes::Value, key);
1961        sc
1962    }
1963
1964    vd.field_list_items("cb", Item::CasusBelli);
1965    vd.field_bool("recalculate_cb_targets");
1966    vd.field_trigger_builder("is_valid", Tooltipped::No, sc_builder);
1967    vd.field_effect_builder("on_success", Tooltipped::No, sc_builder);
1968    vd.field_effect_builder("on_failure", Tooltipped::No, sc_builder);
1969}
1970
1971pub fn validate_create_maa_regiment(
1972    _key: &Token,
1973    _block: &Block,
1974    _data: &Everything,
1975    sc: &mut ScopeContext,
1976    mut vd: Validator,
1977    _tooltipped: Tooltipped,
1978) {
1979    vd.req_field_one_of(&["type", "type_of"]);
1980    vd.field_item("type", Item::MenAtArms);
1981    vd.field_target("type_of", sc, Scopes::Regiment);
1982    vd.field_bool("check_can_recruit");
1983    vd.field_target("title", sc, Scopes::LandedTitle);
1984    vd.field_integer("size");
1985}
1986
1987pub fn validate_create_task_contract(
1988    _key: &Token,
1989    _block: &Block,
1990    _data: &Everything,
1991    sc: &mut ScopeContext,
1992    mut vd: Validator,
1993    _tooltipped: Tooltipped,
1994) {
1995    vd.req_field("task_contract_type");
1996    vd.req_field("task_contract_tier");
1997    vd.req_field("location");
1998
1999    vd.field_item("task_contract_type", Item::TaskContractType);
2000    vd.advice_field(
2001        "task_task_contract_tier",
2002        "docs say `task_task_contract_tier` but it's `task_contract_tier`",
2003    );
2004    vd.field_script_value("task_contract_tier", sc);
2005    vd.field_target("location", sc, Scopes::Province);
2006
2007    vd.field_target("task_contract_employer", sc, Scopes::Character);
2008    vd.field_target("destination", sc, Scopes::Province);
2009    vd.field_target("target", sc, Scopes::Character);
2010    if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
2011        sc.define_name_token(name.as_str(), Scopes::TaskContract, name, Temporary::No);
2012    }
2013
2014    // undocumented
2015
2016    if let Some(name) = vd.field_identifier("save_temporary_scope_as", "scope name") {
2017        sc.define_name_token(name.as_str(), Scopes::TaskContract, name, Temporary::Yes);
2018    }
2019}
2020
2021pub fn validate_give_noble_family_title(
2022    _key: &Token,
2023    _block: &Block,
2024    _data: &Everything,
2025    sc: &mut ScopeContext,
2026    mut vd: Validator,
2027    _tooltipped: Tooltipped,
2028) {
2029    vd.field_validated_sc("name", sc, validate_desc);
2030    vd.field_choice("tier", &["county", "duchy"]);
2031    vd.field_validated_sc("article", sc, validate_desc);
2032    vd.field_item("government", Item::GovernmentType);
2033    if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
2034        sc.define_name_token(name.as_str(), Scopes::LandedTitle, name, Temporary::No);
2035    }
2036}
2037
2038pub fn validate_contracts_for_area(
2039    _key: &Token,
2040    _block: &Block,
2041    _data: &Everything,
2042    sc: &mut ScopeContext,
2043    mut vd: Validator,
2044    _tooltipped: Tooltipped,
2045) {
2046    vd.field_target("location", sc, Scopes::Province);
2047    vd.field_script_value("amount", sc);
2048    vd.field_list_items("group", Item::TaskContractGroup);
2049}
2050
2051pub fn validate_change_appointment_investment(
2052    _key: &Token,
2053    _block: &Block,
2054    _data: &Everything,
2055    sc: &mut ScopeContext,
2056    mut vd: Validator,
2057    _tooltipped: Tooltipped,
2058) {
2059    vd.req_field("target");
2060    vd.req_field("value");
2061    vd.field_target("target", sc, Scopes::Character);
2062    vd.field_target("investor", sc, Scopes::Character);
2063    vd.field_script_value("value", sc);
2064}
2065
2066pub fn validate_set_important_location(
2067    key: &Token,
2068    _block: &Block,
2069    _data: &Everything,
2070    sc: &mut ScopeContext,
2071    mut vd: Validator,
2072    _tooltipped: Tooltipped,
2073) {
2074    vd.field_target("title", sc, Scopes::LandedTitle);
2075    // TODO: set scopes for fired events and actions
2076    // "Events and onactions are fired with this scope:
2077    // root - title top liege
2078    // scope:county - important location
2079    // scope:title - higher tier title that is interested in the county
2080    // In enter realm:
2081    // scope:changed_top_liege - former top liege of the important location
2082    // In leave realm:
2083    // scope:changed_top_liege - new top liege of the important location"
2084    let mut sc = ScopeContext::new(Scopes::Character, key);
2085    sc.define_name("county", Scopes::LandedTitle, key);
2086    sc.define_name("title", Scopes::LandedTitle, key);
2087    sc.define_name("changed_top_liege", Scopes::Character, key);
2088
2089    vd.field_event("enter_realm_event", &mut sc);
2090    vd.field_action("enter_realm_on_action", &sc);
2091    vd.field_event("leave_realm_event", &mut sc);
2092    vd.field_action("leave_realm_on_action", &sc);
2093}
2094
2095pub fn validate_appoint_court_position(
2096    _key: &Token,
2097    _block: &Block,
2098    _data: &Everything,
2099    sc: &mut ScopeContext,
2100    mut vd: Validator,
2101    _tooltipped: Tooltipped,
2102) {
2103    vd.req_field("court_position");
2104    vd.req_field("recipient");
2105    vd.field_item_or_target("court_position", sc, Item::CourtPosition, Scopes::CourtPositionType);
2106    vd.field_target("recipient", sc, Scopes::Character);
2107}
2108
2109pub fn validate_add_takeover_phase_duration(
2110    _key: &Token,
2111    block: &Block,
2112    _data: &Everything,
2113    sc: &mut ScopeContext,
2114    mut vd: Validator,
2115    _tooltipped: Tooltipped,
2116) {
2117    vd.field_item("phase", Item::SituationPhase);
2118    validate_mandatory_duration(block, &mut vd, sc);
2119}
2120
2121pub fn validate_add_takeover_phase_points(
2122    _key: &Token,
2123    _block: &Block,
2124    _data: &Everything,
2125    sc: &mut ScopeContext,
2126    mut vd: Validator,
2127    _tooltipped: Tooltipped,
2128) {
2129    vd.field_item("phase", Item::SituationPhase);
2130    vd.field_script_value("points", sc);
2131}
2132
2133pub fn validate_change_phase(
2134    _key: &Token,
2135    bv: &BV,
2136    data: &Everything,
2137    _sc: &mut ScopeContext,
2138    _tooltipped: Tooltipped,
2139) {
2140    match bv {
2141        BV::Value(value) => {
2142            data.verify_exists(Item::SituationPhase, value);
2143        }
2144        BV::Block(block) => {
2145            let mut vd = Validator::new(block, data);
2146            vd.field_item("phase", Item::SituationPhase);
2147        }
2148    }
2149}
2150
2151pub fn validate_raze_county(
2152    _key: &Token,
2153    _block: &Block,
2154    _data: &Everything,
2155    _sc: &mut ScopeContext,
2156    mut vd: Validator,
2157    _tooltipped: Tooltipped,
2158) {
2159    vd.field_item("holding_type", Item::HoldingType);
2160    vd.field_bool("purge_secondary_holdings");
2161    vd.multi_field_item("excluded_holding_type", Item::HoldingType);
2162}
2163
2164pub fn validate_start_tributary(
2165    _key: &Token,
2166    _block: &Block,
2167    _data: &Everything,
2168    sc: &mut ScopeContext,
2169    mut vd: Validator,
2170    _tooltipped: Tooltipped,
2171) {
2172    vd.field_item("contract_group", Item::SubjectContractGroup);
2173    vd.field_target("suzerain", sc, Scopes::Character);
2174}
2175
2176pub fn validate_start_situation(
2177    _key: &Token,
2178    _block: &Block,
2179    _data: &Everything,
2180    sc: &mut ScopeContext,
2181    mut vd: Validator,
2182    _tooltipped: Tooltipped,
2183) {
2184    vd.req_field("type");
2185    vd.field_item("type", Item::Situation);
2186    vd.field_item("start_phase", Item::SituationPhase);
2187    if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
2188        sc.define_name_token(name.as_str(), Scopes::Situation, name, Temporary::No);
2189    }
2190    if let Some(name) = vd.field_identifier("save_temporary_scope_as", "scope name") {
2191        sc.define_name_token(name.as_str(), Scopes::Situation, name, Temporary::Yes);
2192    }
2193    vd.multi_field_validated_block("sub_region", |block, data| {
2194        let mut vd = Validator::new(block, data);
2195        vd.req_field("key");
2196        vd.field_item("key", Item::SituationSubRegion);
2197        vd.field_item("start_phase", Item::SituationPhase);
2198        vd.field_list_items("geographical_regions", Item::Region);
2199        vd.field_validated("map_color", validate_possibly_named_color);
2200    });
2201}
2202
2203pub fn validate_situation_catalyst(
2204    key: &Token,
2205    bv: &BV,
2206    data: &Everything,
2207    sc: &mut ScopeContext,
2208    _tooltipped: Tooltipped,
2209) {
2210    match bv {
2211        BV::Value(value) => {
2212            data.verify_exists(Item::SituationCatalyst, value);
2213        }
2214        BV::Block(block) => {
2215            let mut vd = Validator::new(block, data);
2216            vd.req_field("catalyst");
2217            vd.field_item("catalyst", Item::SituationCatalyst);
2218            vd.field_target("character", sc, Scopes::Character);
2219            if key.is("trigger_situation_catalyst") {
2220                // TODO: verify if "county" really takes a province
2221                vd.field_target("county", sc, Scopes::Province);
2222            }
2223        }
2224    }
2225}
2226
2227pub fn validate_house_relation_level(
2228    _key: &Token,
2229    _block: &Block,
2230    _data: &Everything,
2231    sc: &mut ScopeContext,
2232    mut vd: Validator,
2233    _tooltipped: Tooltipped,
2234) {
2235    vd.field_script_value_no_breakdown("steps", sc);
2236    vd.field_validated_sc("description", sc, validate_desc);
2237    vd.field_bool("notification");
2238}
2239
2240pub fn validate_create_cadet_branch(
2241    _key: &Token,
2242    _block: &Block,
2243    _data: &Everything,
2244    sc: &mut ScopeContext,
2245    mut vd: Validator,
2246    _tooltipped: Tooltipped,
2247) {
2248    vd.field_item("prefix", Item::Localization);
2249    vd.field_validated_sc("name", sc, validate_desc);
2250    vd.field_item("coat_of_arms", Item::Coa);
2251    vd.field_bool("spread_to_descendants");
2252    if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
2253        sc.define_name_token(name.as_str(), Scopes::DynastyHouse, name, Temporary::No);
2254    }
2255}
2256
2257pub fn validate_create_dynasty(
2258    _key: &Token,
2259    _block: &Block,
2260    _data: &Everything,
2261    sc: &mut ScopeContext,
2262    mut vd: Validator,
2263    _tooltipped: Tooltipped,
2264) {
2265    vd.field_item("prefix", Item::Localization);
2266    vd.field_validated_sc("name", sc, validate_desc);
2267    vd.field_item("coat_of_arms", Item::Coa);
2268    vd.field_bool("spread_to_descendants");
2269    if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
2270        sc.define_name_token(name.as_str(), Scopes::Dynasty, name, Temporary::No);
2271    }
2272}
2273
2274pub fn validate_contribution(
2275    _key: &Token,
2276    _block: &Block,
2277    _data: &Everything,
2278    sc: &mut ScopeContext,
2279    mut vd: Validator,
2280    _tooltipped: Tooltipped,
2281) {
2282    vd.req_field("contribution");
2283    vd.field_target("contribution", sc, Scopes::ProjectContribution);
2284    vd.field_bool("cost");
2285    vd.field_bool("check_can_contribute");
2286}
2287
2288pub fn validate_situation_special_event(
2289    _key: &Token,
2290    _block: &Block,
2291    _data: &Everything,
2292    sc: &mut ScopeContext,
2293    mut vd: Validator,
2294    _tooltipped: Tooltipped,
2295) {
2296    // TODO: "Only applies to situations that support full history"
2297    vd.field_target("actor", sc, Scopes::Character);
2298    vd.field_item("phase", Item::SituationPhase);
2299    let mut loca_sc = sc.clone();
2300    vd.field_validated_block("variables", |block, data| {
2301        let mut vd = Validator::new(block, data);
2302        vd.unknown_value_fields(|key, value| {
2303            let scopes = validate_target_ok_this(value, data, sc, Scopes::all());
2304            loca_sc.define_name(key.as_str(), scopes, value);
2305        });
2306    });
2307    vd.field_localization("key", &mut loca_sc);
2308    loca_sc.destroy();
2309}
2310
2311pub fn validate_appointment_timeout(
2312    _key: &Token,
2313    block: &Block,
2314    _data: &Everything,
2315    sc: &mut ScopeContext,
2316    mut vd: Validator,
2317    _tooltipped: Tooltipped,
2318) {
2319    vd.field_validated_sc("desc", sc, validate_desc);
2320    validate_mandatory_duration(block, &mut vd, sc);
2321}
2322
2323pub fn validate_house_relation(
2324    _key: &Token,
2325    _block: &Block,
2326    _data: &Everything,
2327    sc: &mut ScopeContext,
2328    mut vd: Validator,
2329    _tooltipped: Tooltipped,
2330) {
2331    vd.req_field("target");
2332    vd.field_target("target", sc, Scopes::DynastyHouse);
2333    vd.field_item("type", Item::HouseRelationType);
2334    // TODO: check that level is part of type
2335    vd.field_item("level", Item::HouseRelationLevel);
2336    vd.field_validated_sc("description", sc, validate_desc);
2337    if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
2338        sc.define_name_token(name.as_str(), Scopes::HouseRelation, name, Temporary::No);
2339    }
2340}
2341
2342pub fn validate_set_activity_intent(
2343    _key: &Token,
2344    bv: &BV,
2345    data: &Everything,
2346    sc: &mut ScopeContext,
2347    _tooltipped: Tooltipped,
2348) {
2349    match bv {
2350        BV::Value(value) => {
2351            data.verify_exists(Item::ActivityIntent, value);
2352        }
2353        BV::Block(block) => {
2354            let mut vd = Validator::new(block, data);
2355            vd.req_field("intent");
2356            vd.field_item("intent", Item::ActivityIntent);
2357            vd.field_target("target", sc, Scopes::Character);
2358        }
2359    }
2360}
2361
2362pub fn validate_plan_great_project(
2363    _key: &Token,
2364    _block: &Block,
2365    _data: &Everything,
2366    sc: &mut ScopeContext,
2367    mut vd: Validator,
2368    _tooltipped: Tooltipped,
2369) {
2370    vd.req_field("great_project_type");
2371    vd.req_field("founder");
2372    vd.field_item("great_project_type", Item::GreatProjectType);
2373    vd.field_target("founder", sc, Scopes::Character);
2374    if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
2375        sc.define_name_token(name.as_str(), Scopes::GreatProject, name, Temporary::No);
2376    }
2377}
2378
2379pub fn validate_phase_duration(
2380    _key: &Token,
2381    block: &Block,
2382    _data: &Everything,
2383    sc: &mut ScopeContext,
2384    mut vd: Validator,
2385    _tooltipped: Tooltipped,
2386) {
2387    validate_mandatory_duration(block, &mut vd, sc);
2388}
2389
2390pub fn validate_send_interface(
2391    key: &Token,
2392    block: &Block,
2393    data: &Everything,
2394    sc: &mut ScopeContext,
2395    mut vd: Validator,
2396    _tooltipped: Tooltipped,
2397    special_tokens: &mut SpecialTokens,
2398) -> bool {
2399    vd.field_item("type", Item::Message);
2400    if let Some(token) = vd.field_value("goto") {
2401        let msg = "`goto` was removed from interface messages in 1.9";
2402        warn(ErrorKey::Removed).msg(msg).loc(token).push();
2403    }
2404    // Mark all these as known fields. This exempts them from the unknown-fields loop in `validate_effect_internal`.
2405    // They must be validated after that loop, because the effects may have set named scopes for them to use.
2406    vd.field("title");
2407    vd.field("desc");
2408    vd.field("tooltip");
2409    vd.field("left_icon");
2410    vd.field("right_icon");
2411    vd.field("localization_values");
2412
2413    let random_is_problem = if let Some(message_type) = block.get_field_value("type") {
2414        data.item_has_property(Item::Message, message_type.as_str(), "displays_effect")
2415    } else {
2416        true
2417    };
2418    let mut st = SpecialTokens::empty();
2419    let caller = Lowercase::new(key.as_str());
2420    validate_effect_internal(
2421        &caller,
2422        ListType::None,
2423        block,
2424        data,
2425        sc,
2426        &mut vd,
2427        Tooltipped::Yes,
2428        &mut st,
2429    );
2430    for token in st.into_iter() {
2431        if token.is("random") || token.is("random_list") || token.is("duel") {
2432            if random_is_problem {
2433                warn(ErrorKey::Logic)
2434                    .msg(format!("running `{token}` inside `{key}` does not work right"))
2435                    .info("the message performs one roll and the execution performs another")
2436                    .loc(token)
2437                    .loc_msg(key, "executed inside this")
2438                    .push();
2439            }
2440        } else {
2441            special_tokens.insert(token);
2442        }
2443    }
2444
2445    // These are both scopes and $-parameters to set for the loca.
2446    // They are applied only to the following loca.
2447    let mut loca_sc = sc.clone();
2448    vd.field_validated_block("localization_values", |block, data| {
2449        let mut vd = Validator::new(block, data);
2450        vd.unknown_value_fields(|key, value| {
2451            let scopes = validate_target_ok_this(value, data, sc, Scopes::all());
2452            loca_sc.define_name(key.as_str(), scopes, value);
2453        });
2454    });
2455    vd.field_validated_sc("title", &mut loca_sc, validate_desc);
2456    vd.field_validated_sc("desc", &mut loca_sc, validate_desc);
2457    vd.field_validated_sc("tooltip", &mut loca_sc, validate_desc);
2458    loca_sc.destroy();
2459    let icon_scopes = Scopes::Character
2460        | Scopes::LandedTitle
2461        | Scopes::Artifact
2462        | Scopes::Faith
2463        | Scopes::Dynasty
2464        | Scopes::DynastyHouse
2465        | Scopes::Confederation;
2466    if let Some(token) = vd.field_value("left_icon") {
2467        validate_target_ok_this(token, data, sc, icon_scopes);
2468    }
2469    if let Some(token) = vd.field_value("right_icon") {
2470        validate_target_ok_this(token, data, sc, icon_scopes);
2471    }
2472    false
2473}