Skip to main content

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        && !data.item_exists(Item::Secret, token.as_str())
140    {
141        validate_target(token, data, sc, Scopes::Secret);
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.field_target("knight", sc, Scopes::Character);
523    vd.field_item("primary", Item::AccoladeType);
524    vd.advice_field("secondary", "removed in 1.19");
525    vd.field_item("name", Item::Localization);
526}
527
528pub fn validate_create_artifact(
529    key: &Token,
530    _block: &Block,
531    _data: &Everything,
532    sc: &mut ScopeContext,
533    mut vd: Validator,
534    _tooltipped: Tooltipped,
535) {
536    let caller = key.as_str().to_ascii_lowercase();
537    vd.field_validated_sc("name", sc, validate_desc);
538    vd.field_validated_sc("description", sc, validate_desc);
539    vd.field_item("rarity", Item::ArtifactRarity);
540    vd.field_item("type", Item::ArtifactType);
541    vd.multi_field_item("modifier", Item::Modifier);
542    vd.field_script_value("durability", sc);
543    vd.field_script_value("max_durability", sc);
544    vd.field_bool("decaying");
545    vd.multi_field_validated_block_sc("history", sc, validate_artifact_history);
546    vd.field_item("template", Item::ArtifactTemplate);
547    vd.field_item("visuals", Item::ArtifactVisual);
548    vd.field_bool("generate_history");
549    vd.field_script_value("quality", sc);
550    vd.field_script_value("wealth", sc);
551    vd.field_target("creator", sc, Scopes::Character);
552    vd.field_target(
553        "visuals_source",
554        sc,
555        Scopes::LandedTitle | Scopes::Dynasty | Scopes::DynastyHouse,
556    );
557
558    if caller == "create_artifact" {
559        if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
560            sc.define_name_token(name.as_str(), Scopes::Artifact, name, Temporary::No);
561        }
562        vd.field_target("title_history", sc, Scopes::LandedTitle);
563        vd.field_date("title_history_date");
564    } else {
565        vd.ban_field("save_scope_as", || "`create_artifact`");
566        vd.ban_field("title_history", || "`create_artifact`");
567        vd.ban_field("title_history_date", || "`create_artifact`");
568    }
569}
570
571pub fn validate_create_character(
572    _key: &Token,
573    block: &Block,
574    data: &Everything,
575    sc: &mut ScopeContext,
576    mut vd: Validator,
577    _tooltipped: Tooltipped,
578) {
579    // docs say save_event_target instead of save_scope
580    vd.replaced_field("save_event_target_as", "save_scope_as");
581    vd.replaced_field("save_temporary_event_target_as", "save_temporary_scope_as");
582    if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
583        sc.define_name_token(name.as_str(), Scopes::Character, name, Temporary::No);
584    }
585    if let Some(name) = vd.field_identifier("save_temporary_scope_as", "scope name") {
586        sc.define_name_token(name.as_str(), Scopes::Character, name, Temporary::Yes);
587    }
588
589    vd.field_validated_sc("name", sc, validate_desc);
590    vd.field_script_value("age", sc);
591    if let Some(token) = vd.field_value("gender")
592        && !token.is("male")
593        && !token.is("female")
594    {
595        validate_target_ok_this(token, data, sc, Scopes::Character);
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        && !token.is("generate")
627        && !token.is("inherit")
628        && !token.is("none")
629    {
630        validate_target(token, data, sc, Scopes::Dynasty);
631    }
632    vd.field_validated_value("ethnicity", |_, mut vd| {
633        vd.maybe_is("culture");
634        vd.maybe_is("mother");
635        vd.maybe_is("father");
636        vd.maybe_is("parents");
637        vd.item(Item::Ethnicity);
638    });
639    // TODO: Find out the syntax of this. Docs are unclear, no examples in vanilla.
640    vd.field_block("ethnicities");
641    vd.field_script_value("diplomacy", sc);
642    vd.field_script_value("intrigue", sc);
643    vd.field_script_value("martial", sc);
644    vd.field_script_value("learning", sc);
645    vd.field_script_value("prowess", sc);
646    vd.field_script_value("stewardship", sc);
647    vd.field_validated_key_block("after_creation", |key, block, data| {
648        sc.open_scope(Scopes::Character, key.clone());
649        validate_effect(block, data, sc, Tooltipped::No); // TODO: verify
650        sc.close();
651    });
652}
653
654pub fn validate_create_character_memory(
655    key: &Token,
656    block: &Block,
657    _data: &Everything,
658    sc: &mut ScopeContext,
659    mut vd: Validator,
660    _tooltipped: Tooltipped,
661) {
662    vd.req_field("type");
663    vd.field_item("type", Item::MemoryType);
664    // TODO: also check that all participants are specified
665    vd.field_validated_block("participants", |b, data| {
666        let mut vd = Validator::new(b, data);
667        let memtype = block.get_field_value("type");
668        vd.unknown_value_fields(|key, token| {
669            if let Some(memtype) = memtype
670                && !data.item_has_property(Item::MemoryType, memtype.as_str(), key.as_str())
671            {
672                let msg = format!("memory type `{memtype}` does not define participant `{key}`");
673                warn(ErrorKey::Validation).msg(msg).loc(key).push();
674            }
675            validate_target_ok_this(token, data, sc, Scopes::Character);
676        });
677    });
678    vd.field_validated_block_sc("duration", sc, validate_duration);
679    sc.define_name("new_memory", Scopes::CharacterMemory, key);
680}
681
682pub fn validate_create_confederation(
683    key: &Token,
684    _block: &Block,
685    _data: &Everything,
686    sc: &mut ScopeContext,
687    mut vd: Validator,
688    _tooltipped: Tooltipped,
689) {
690    vd.req_field("name");
691    vd.field_validated_sc("name", sc, validate_desc);
692    vd.field_target("type", sc, Scopes::ConfederationType);
693    vd.field_target("leader", sc, Scopes::DynastyHouse);
694    sc.define_name("new_confederation", Scopes::Confederation, key);
695}
696
697pub fn validate_create_dynamic_title(
698    key: &Token,
699    _block: &Block,
700    _data: &Everything,
701    sc: &mut ScopeContext,
702    mut vd: Validator,
703    _tooltipped: Tooltipped,
704) {
705    vd.req_field("tier");
706    vd.req_field("name");
707    vd.field_choice("tier", &["duchy", "kingdom", "empire", "hegemony"]);
708    vd.field_validated_sc("name", sc, validate_desc);
709    vd.advice_field("adjective", "changed to adj in 1.13");
710    vd.field_validated_sc("adj", sc, validate_desc);
711    vd.field_validated_sc("pre", sc, validate_desc);
712    vd.field_validated_sc("article", sc, validate_desc);
713    sc.define_name("new_title", Scopes::LandedTitle, key);
714}
715
716pub fn validate_create_nomad_title(
717    _key: &Token,
718    _block: &Block,
719    _data: &Everything,
720    sc: &mut ScopeContext,
721    mut vd: Validator,
722    _tooltipped: Tooltipped,
723) {
724    vd.field_validated_sc("name", sc, validate_desc);
725    vd.field_validated_sc("prefix", sc, validate_desc);
726    vd.field_validated_sc("adjective", sc, validate_desc);
727    vd.field_target("holder", sc, Scopes::Character);
728    vd.field_item("government", Item::GovernmentType);
729    if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
730        sc.define_name_token(name.as_str(), Scopes::LandedTitle, name, Temporary::No);
731    }
732}
733
734pub fn validate_create_holy_order(
735    _key: &Token,
736    _block: &Block,
737    _data: &Everything,
738    sc: &mut ScopeContext,
739    mut vd: Validator,
740    _tooltipped: Tooltipped,
741) {
742    vd.req_field("leader");
743    vd.req_field("capital");
744    vd.field_target("leader", sc, Scopes::Character);
745    vd.field_target("capital", sc, Scopes::LandedTitle);
746    vd.field_item("name", Item::Localization);
747    vd.field_item("coat_of_arms", Item::Coa);
748    if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
749        sc.define_name_token(name.as_str(), Scopes::HolyOrder, name, Temporary::No);
750    }
751    if let Some(name) = vd.field_identifier("save_temporary_scope_as", "scope name") {
752        sc.define_name_token(name.as_str(), Scopes::HolyOrder, name, Temporary::Yes);
753    }
754}
755
756pub fn validate_create_title_and_vassal_change(
757    _key: &Token,
758    _block: &Block,
759    _data: &Everything,
760    sc: &mut ScopeContext,
761    mut vd: Validator,
762    _tooltipped: Tooltipped,
763) {
764    vd.req_field("type");
765    vd.req_field("save_scope_as");
766    if let Some(history_type) = vd.field_value("type") {
767        let valid_types: Vec<_> = TITLE_HISTORY_TYPES
768            .iter()
769            .filter(|t| !BANNED_TITLE_HISTORY_TYPES.contains(t))
770            .copied()
771            .collect();
772        if BANNED_TITLE_HISTORY_TYPES.contains(&history_type.as_str()) {
773            let msg = format!(
774                "types {} cannot be used from script",
775                stringify_list(BANNED_TITLE_HISTORY_TYPES)
776            );
777            let info = format!("choose_from {}", stringify_choices(&valid_types));
778            err(ErrorKey::Choice).msg(msg).info(info).loc(history_type).push();
779        } else {
780            vd.field_choice("type", &valid_types);
781        }
782    }
783    if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
784        sc.define_name_token(name.as_str(), Scopes::TitleAndVassalChange, name, Temporary::No);
785    }
786    vd.field_bool("add_claim_on_loss");
787}
788
789pub fn validate_delay_travel_plan(
790    _key: &Token,
791    _block: &Block,
792    _data: &Everything,
793    sc: &mut ScopeContext,
794    mut vd: Validator,
795    _tooltipped: Tooltipped,
796) {
797    vd.field_bool("add");
798    validate_optional_duration(&mut vd, sc);
799}
800
801pub fn validate_divide_war_chest(
802    _key: &Token,
803    _block: &Block,
804    _data: &Everything,
805    sc: &mut ScopeContext,
806    mut vd: Validator,
807    _tooltipped: Tooltipped,
808) {
809    vd.field_bool("defenders");
810    vd.field_script_value("fraction", sc);
811    vd.field_bool("gold");
812    vd.field_bool("piety");
813    vd.field_bool("prestige");
814}
815
816pub fn validate_duel(
817    key: &Token,
818    block: &Block,
819    data: &Everything,
820    sc: &mut ScopeContext,
821    mut vd: Validator,
822    tooltipped: Tooltipped,
823    special_tokens: &mut SpecialTokens,
824) -> bool {
825    vd.field_item("skill", Item::Skill);
826    vd.field_list_items("skills", Item::Skill);
827    vd.field_target("target", sc, Scopes::Character);
828    vd.field_script_value("value", sc);
829    if let Some(value) = vd.field_value("localization") {
830        validate_effect_localization(value, data, tooltipped);
831    }
832    vd.field_validated_value("challenge_variable", |_, mut vd| {
833        vd.identifier("variable name");
834        let loca = format!("{}_name", vd.value());
835        data.verify_exists_implied(Item::Localization, &loca, vd.value());
836        vd.accept();
837    });
838    vd.field_validated_list("challenge_variables", |token, data| {
839        validate_identifier(token, "variable name", Severity::Error);
840        let loca = format!("{token}_name");
841        data.verify_exists_implied(Item::Localization, &loca, token);
842    });
843    sc.define_name("duel_value", Scopes::Value, key);
844    let has_tooltip = validate_random_list(key, block, data, sc, vd, tooltipped, special_tokens);
845    if has_tooltip {
846        special_tokens.insert(key);
847    }
848    has_tooltip
849}
850
851pub fn validate_faction_start_war(
852    _key: &Token,
853    _block: &Block,
854    _data: &Everything,
855    sc: &mut ScopeContext,
856    mut vd: Validator,
857    _tooltipped: Tooltipped,
858) {
859    vd.field_target("title", sc, Scopes::LandedTitle);
860}
861
862pub fn validate_force_add_to_agent_slot(
863    _key: &Token,
864    _block: &Block,
865    _data: &Everything,
866    sc: &mut ScopeContext,
867    mut vd: Validator,
868    _tooltipped: Tooltipped,
869) {
870    vd.field_target("agent_slot", sc, Scopes::AgentSlot);
871    validate_optional_duration(&mut vd, sc);
872}
873
874pub fn validate_force_vote_as(
875    _key: &Token,
876    _block: &Block,
877    _data: &Everything,
878    sc: &mut ScopeContext,
879    mut vd: Validator,
880    _tooltipped: Tooltipped,
881) {
882    vd.field_target("target", sc, Scopes::Character);
883    validate_optional_duration(&mut vd, sc);
884}
885
886pub fn validate_imprison(
887    _key: &Token,
888    _block: &Block,
889    _data: &Everything,
890    sc: &mut ScopeContext,
891    mut vd: Validator,
892    _tooltipped: Tooltipped,
893) {
894    vd.field_target("target", sc, Scopes::Character);
895    vd.field_item("type", Item::PrisonType);
896    // The docs also have a "reason" key, but no indication what it is
897}
898
899pub fn validate_join_faction_forced(
900    _key: &Token,
901    _block: &Block,
902    _data: &Everything,
903    sc: &mut ScopeContext,
904    mut vd: Validator,
905    _tooltipped: Tooltipped,
906) {
907    vd.field_target("faction", sc, Scopes::Faction);
908    vd.field_target("forced_by", sc, Scopes::Character);
909    validate_optional_duration(&mut vd, sc);
910}
911
912pub fn validate_make_pregnant(
913    _key: &Token,
914    _block: &Block,
915    _data: &Everything,
916    sc: &mut ScopeContext,
917    mut vd: Validator,
918    _tooltipped: Tooltipped,
919) {
920    vd.field_target("father", sc, Scopes::Character);
921    vd.field_integer("number_of_children");
922    vd.field_bool("known_bastard");
923}
924
925pub fn validate_move_budget(
926    key: &Token,
927    _block: &Block,
928    _data: &Everything,
929    sc: &mut ScopeContext,
930    mut vd: Validator,
931    _tooltipped: Tooltipped,
932) {
933    if key.is("move_budget_gold") {
934        vd.field_script_value_no_breakdown("gold", sc);
935    } else if key.is("move_budget_treasury") {
936        vd.field_script_value_no_breakdown("treasury", sc);
937    }
938    let choices = &["budget_war_chest", "budget_reserved", "budget_short_term", "budget_long_term"];
939    vd.field_choice("from", choices);
940    vd.field_choice("to", choices);
941}
942
943pub fn validate_open_interaction_window(
944    key: &Token,
945    _block: &Block,
946    _data: &Everything,
947    sc: &mut ScopeContext,
948    mut vd: Validator,
949    _tooltipped: Tooltipped,
950) {
951    let caller = key.as_str().to_ascii_lowercase();
952    vd.req_field("interaction");
953    vd.req_field("actor");
954    vd.req_field("recipient");
955    vd.field_value("interaction"); // TODO
956    vd.field_bool("redirect");
957    vd.field_target_ok_this("actor", sc, Scopes::Character);
958    vd.field_target_ok_this("recipient", sc, Scopes::Character);
959    vd.field_target_ok_this("secondary_actor", sc, Scopes::Character);
960    vd.field_target_ok_this("secondary_recipient", sc, Scopes::Character);
961    if caller == "open_interaction_window" {
962        vd.field_target("target_title", sc, Scopes::LandedTitle);
963    }
964    if caller == "run_interaction" {
965        vd.field_choice("execute_threshold", &["accept", "maybe", "decline"]);
966        vd.field_choice("send_threshold", &["accept", "maybe", "decline"]);
967    }
968}
969
970pub fn validate_pay_gold(
971    _key: &Token,
972    _block: &Block,
973    _data: &Everything,
974    sc: &mut ScopeContext,
975    mut vd: Validator,
976    _tooltipped: Tooltipped,
977) {
978    vd.req_field("target");
979    vd.field_target("target", sc, Scopes::Character);
980    vd.field_script_value("gold", sc);
981    // undocumented; it means multiply the gold amount by (whose?) yearly income
982    vd.field_bool("yearly_income");
983}
984
985pub fn validate_pay_income(
986    _key: &Token,
987    _block: &Block,
988    _data: &Everything,
989    sc: &mut ScopeContext,
990    mut vd: Validator,
991    _tooltipped: Tooltipped,
992) {
993    vd.req_field("target");
994    vd.field_target("target", sc, Scopes::Character);
995    validate_optional_duration(&mut vd, sc);
996}
997
998pub fn validate_current_phase_guest_subset(
999    _key: &Token,
1000    _block: &Block,
1001    _data: &Everything,
1002    sc: &mut ScopeContext,
1003    mut vd: Validator,
1004    _tooltipped: Tooltipped,
1005) {
1006    vd.req_field("name");
1007    vd.req_field("target");
1008    vd.field_item("name", Item::GuestSubset);
1009    vd.field_target("target", sc, Scopes::Character);
1010}
1011
1012pub fn validate_remove_opinion(
1013    _key: &Token,
1014    _block: &Block,
1015    _data: &Everything,
1016    sc: &mut ScopeContext,
1017    mut vd: Validator,
1018    _tooltipped: Tooltipped,
1019) {
1020    vd.req_field("target");
1021    vd.req_field("modifier");
1022    vd.field_target("target", sc, Scopes::Character);
1023    vd.field_item("modifier", Item::OpinionModifier);
1024    vd.field_bool("single");
1025}
1026
1027pub fn validate_replace_court_position(
1028    _key: &Token,
1029    _block: &Block,
1030    _data: &Everything,
1031    sc: &mut ScopeContext,
1032    mut vd: Validator,
1033    _tooltipped: Tooltipped,
1034) {
1035    vd.req_field("recipient");
1036    vd.req_field("court_position");
1037    vd.field_target("recipient", sc, Scopes::Character);
1038    vd.field_target("holder", sc, Scopes::Character);
1039    vd.field_item("court_position", Item::CourtPosition);
1040}
1041
1042pub fn validate_revoke_court_position(
1043    _key: &Token,
1044    _block: &Block,
1045    _data: &Everything,
1046    sc: &mut ScopeContext,
1047    mut vd: Validator,
1048    _tooltipped: Tooltipped,
1049) {
1050    vd.req_field("court_position");
1051    vd.field_item("court_position", Item::CourtPosition);
1052    vd.field_target("recipient", sc, Scopes::Character);
1053    vd.field_target("holder", sc, Scopes::Character);
1054}
1055
1056pub fn validate_save_opinion_value(
1057    key: &Token,
1058    _block: &Block,
1059    _data: &Everything,
1060    sc: &mut ScopeContext,
1061    mut vd: Validator,
1062    _tooltipped: Tooltipped,
1063) {
1064    vd.req_field("name");
1065    vd.req_field("target");
1066    let temp =
1067        if key.is("save_temporary_opinion_value_as") { Temporary::Yes } else { Temporary::No };
1068    if let Some(name) = vd.field_value("name") {
1069        sc.define_name_token(name.as_str(), Scopes::Value, name, temp);
1070    }
1071    vd.field_target("target", sc, Scopes::Character);
1072}
1073
1074pub fn validate_scheme_freeze(
1075    _key: &Token,
1076    _block: &Block,
1077    _data: &Everything,
1078    sc: &mut ScopeContext,
1079    mut vd: Validator,
1080    _tooltipped: Tooltipped,
1081) {
1082    vd.field_item("reason", Item::Localization);
1083    validate_optional_duration(&mut vd, sc);
1084}
1085
1086pub fn validate_set_council_task(
1087    _key: &Token,
1088    _block: &Block,
1089    _data: &Everything,
1090    sc: &mut ScopeContext,
1091    mut vd: Validator,
1092    _tooltipped: Tooltipped,
1093) {
1094    vd.req_field("task_type");
1095    // TODO: figure out for which task types `target` is required
1096    vd.field_item("task_type", Item::CouncilTask);
1097    // This has been verified as of 1.9.2, it does require a Province here and not a LandedTitle
1098    vd.field_target("target", sc, Scopes::Character | Scopes::Province);
1099}
1100
1101pub fn validate_set_culture_name(
1102    _key: &Token,
1103    _block: &Block,
1104    _data: &Everything,
1105    sc: &mut ScopeContext,
1106    mut vd: Validator,
1107    _tooltipped: Tooltipped,
1108) {
1109    vd.req_field("noun");
1110    vd.field_validated_sc("noun", sc, validate_desc);
1111    vd.field_validated_sc("collective_noun", sc, validate_desc);
1112    vd.field_validated_sc("prefix", sc, validate_desc);
1113}
1114
1115pub fn validate_set_death_reason(
1116    _key: &Token,
1117    _block: &Block,
1118    _data: &Everything,
1119    sc: &mut ScopeContext,
1120    mut vd: Validator,
1121    _tooltipped: Tooltipped,
1122) {
1123    vd.req_field("death_reason");
1124    vd.field_item("death_reason", Item::DeathReason);
1125    vd.field_target("killer", sc, Scopes::Character);
1126    vd.field_target("artifact", sc, Scopes::Artifact);
1127}
1128
1129pub fn validate_set_ghw_target(
1130    key: &Token,
1131    _block: &Block,
1132    _data: &Everything,
1133    sc: &mut ScopeContext,
1134    mut vd: Validator,
1135    _tooltipped: Tooltipped,
1136) {
1137    let caller = key.as_str().to_ascii_lowercase();
1138    vd.req_field("target_character");
1139    vd.req_field("target_title");
1140    vd.field_target("target_character", sc, Scopes::Character);
1141    vd.field_target("target_title", sc, Scopes::LandedTitle);
1142    if caller == "start_great_holy_war" {
1143        vd.field_script_value("delay", sc);
1144        vd.field_target("war", sc, Scopes::War);
1145    }
1146}
1147
1148pub fn validate_set_legend_chapter(
1149    _key: &Token,
1150    _block: &Block,
1151    _data: &Everything,
1152    _sc: &mut ScopeContext,
1153    mut vd: Validator,
1154    _tooltipped: Tooltipped,
1155) {
1156    vd.field_item("name", Item::LegendChapter);
1157    vd.field_item("localization_key", Item::Localization);
1158}
1159
1160pub fn validate_set_legend_property(
1161    _key: &Token,
1162    _block: &Block,
1163    _data: &Everything,
1164    sc: &mut ScopeContext,
1165    mut vd: Validator,
1166    _tooltipped: Tooltipped,
1167) {
1168    vd.field_item("name", Item::LegendProperty);
1169    // TODO: look up possible scope types from the legend properties
1170    vd.field_target("target", sc, Scopes::all());
1171}
1172
1173pub fn validate_setup_cb(
1174    key: &Token,
1175    _block: &Block,
1176    _data: &Everything,
1177    sc: &mut ScopeContext,
1178    mut vd: Validator,
1179    _tooltipped: Tooltipped,
1180) {
1181    let caller = key.as_str().to_ascii_lowercase();
1182    vd.req_field("attacker");
1183    vd.req_field("defender");
1184    // vd.req_field("change"); is optional if you just want it to set scope:cb_prestige_factor
1185    vd.field_target("attacker", sc, Scopes::Character);
1186    vd.field_target("defender", sc, Scopes::Character);
1187    vd.field_target("change", sc, Scopes::TitleAndVassalChange);
1188    vd.field_bool("victory");
1189    if caller == "setup_claim_cb" {
1190        vd.req_field("claimant");
1191        vd.field_target("claimant", sc, Scopes::Character);
1192        vd.field_bool("take_occupied");
1193        vd.field_bool("civil_war");
1194        vd.field_choice("titles", &["target_titles", "faction_titles"]);
1195    } else if caller == "setup_de_jure_cb" {
1196        vd.field_target("title", sc, Scopes::LandedTitle);
1197    } else if caller == "setup_invasion_cb" {
1198        vd.field_identifier("titles", "list name");
1199        vd.field_bool("take_occupied");
1200        vd.field_target("claimant", sc, Scopes::Character);
1201    }
1202    sc.define_name("cb_prestige_factor", Scopes::Value, key);
1203}
1204
1205pub fn validate_spawn_army(
1206    _key: &Token,
1207    _block: &Block,
1208    _data: &Everything,
1209    sc: &mut ScopeContext,
1210    mut vd: Validator,
1211    _tooltipped: Tooltipped,
1212) {
1213    // TODO: either levies or men_at_arms
1214    vd.req_field("location");
1215    vd.field_script_value("levies", sc);
1216    vd.multi_field_validated_block("men_at_arms", |b, data| {
1217        let mut vd = Validator::new(b, data);
1218        vd.req_field("type");
1219        vd.field_item("type", Item::MenAtArms);
1220        vd.field_script_value("men", sc);
1221        vd.field_script_value("stacks", sc);
1222        vd.field_bool("inheritable"); // undocumented
1223    });
1224    vd.field_target("location", sc, Scopes::Province);
1225    vd.field_target("origin", sc, Scopes::Province);
1226    vd.field_target("war", sc, Scopes::War);
1227    vd.field_bool("war_keep_on_attacker_victory");
1228    vd.field_bool("inheritable");
1229    vd.field_bool("uses_supply");
1230    vd.field_target("army", sc, Scopes::Army);
1231    if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
1232        sc.define_name_token(name.as_str(), Scopes::Army, name, Temporary::No);
1233    }
1234    if let Some(name) = vd.field_identifier("save_temporary_scope_as", "scope name") {
1235        sc.define_name_token(name.as_str(), Scopes::Army, name, Temporary::Yes);
1236    }
1237    vd.field_validated_sc("name", sc, validate_desc);
1238}
1239
1240pub fn validate_start_scheme(
1241    _key: &Token,
1242    _block: &Block,
1243    _data: &Everything,
1244    sc: &mut ScopeContext,
1245    mut vd: Validator,
1246    _tooltipped: Tooltipped,
1247) {
1248    vd.req_field("type");
1249    vd.req_field_one_of(&[
1250        "target_character",
1251        "target_title",
1252        "target_culture",
1253        "target_faith",
1254        "targets_nothing",
1255    ]);
1256    vd.field_item("type", Item::Scheme);
1257    vd.field_target("contract", sc, Scopes::TaskContract);
1258    vd.advice_field("target", "replaced with target_character in 1.13");
1259    vd.field_target("target_character", sc, Scopes::Character);
1260    vd.field_target("target_title", sc, Scopes::LandedTitle);
1261    vd.field_target("target_culture", sc, Scopes::Culture);
1262    vd.field_target("target_faith", sc, Scopes::Faith);
1263    vd.field_bool("targets_nothing");
1264    if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
1265        sc.define_name_token(name.as_str(), Scopes::Scheme, name, Temporary::No);
1266    }
1267
1268    // undocumented
1269
1270    // TODO: verify if still valid in 1.13
1271    vd.field_target("artifact", sc, Scopes::Artifact);
1272}
1273
1274pub fn validate_start_struggle(
1275    _key: &Token,
1276    _block: &Block,
1277    _data: &Everything,
1278    _sc: &mut ScopeContext,
1279    mut vd: Validator,
1280    _tooltipped: Tooltipped,
1281) {
1282    vd.req_field("struggle_type");
1283    vd.req_field("start_phase");
1284    vd.field_item("struggle_type", Item::Struggle);
1285    vd.field_item("start_phase", Item::StrugglePhase);
1286}
1287
1288pub fn validate_start_travel_plan(
1289    _key: &Token,
1290    _block: &Block,
1291    data: &Everything,
1292    sc: &mut ScopeContext,
1293    mut vd: Validator,
1294    _tooltipped: Tooltipped,
1295) {
1296    vd.req_field("destination");
1297    for token in vd.multi_field_value("destination") {
1298        validate_target(token, data, sc, Scopes::Province);
1299    }
1300    vd.field_target("travel_leader", sc, Scopes::Character);
1301    for token in vd.multi_field_value("companion") {
1302        validate_target(token, data, sc, Scopes::Character);
1303    }
1304    vd.field_bool("travel_with_domicile");
1305    vd.field_bool("can_cancel_planning");
1306    vd.field_bool("players_use_planner");
1307    vd.field_bool("return_trip");
1308    vd.field_event("on_arrival_event", sc);
1309    vd.field_action("on_arrival_on_action", sc);
1310    vd.field_event("on_start_event", sc);
1311    vd.field_action("on_start_on_action", sc);
1312    vd.field_event("on_travel_planner_cancel_event", sc);
1313    vd.field_action("on_travel_planner_cancel_on_action", sc);
1314    vd.field_choice("on_arrival_destinations", &["all", "first", "last", "all_but_last"]);
1315}
1316
1317pub fn validate_start_war(
1318    _key: &Token,
1319    _block: &Block,
1320    data: &Everything,
1321    sc: &mut ScopeContext,
1322    mut vd: Validator,
1323    _tooltipped: Tooltipped,
1324) {
1325    vd.field_item("casus_belli", Item::CasusBelli);
1326    vd.field_item("cb", Item::CasusBelli);
1327    vd.field_target("target", sc, Scopes::Character);
1328    vd.field_target_ok_this("claimant", sc, Scopes::Character);
1329    for token in vd.multi_field_value("target_title") {
1330        validate_target(token, data, sc, Scopes::LandedTitle);
1331    }
1332}
1333
1334pub fn validate_stress_impact(
1335    _key: &Token,
1336    _block: &Block,
1337    data: &Everything,
1338    sc: &mut ScopeContext,
1339    mut vd: Validator,
1340    _tooltipped: Tooltipped,
1341) {
1342    vd.field_script_value("base", sc);
1343    vd.unknown_fields(|token, bv| {
1344        data.verify_exists(Item::Trait, token);
1345        validate_non_dynamic_script_value(bv, data);
1346    });
1347}
1348
1349pub fn validate_try_create_important_action(
1350    _key: &Token,
1351    _block: &Block,
1352    data: &Everything,
1353    sc: &mut ScopeContext,
1354    mut vd: Validator,
1355    _tooltipped: Tooltipped,
1356) {
1357    vd.req_field("important_action_type");
1358    vd.field_item("important_action_type", Item::ImportantAction);
1359    vd.unknown_value_fields(|_, value| {
1360        validate_target_ok_this(value, data, sc, Scopes::all_but_none());
1361    });
1362}
1363
1364pub fn validate_try_create_suggestion(
1365    _key: &Token,
1366    _block: &Block,
1367    _data: &Everything,
1368    sc: &mut ScopeContext,
1369    mut vd: Validator,
1370    _tooltipped: Tooltipped,
1371) {
1372    vd.req_field("suggestion_type");
1373    vd.field_item("suggestion_type", Item::Suggestion);
1374    vd.field_target_ok_this("actor", sc, Scopes::Character);
1375    vd.field_target_ok_this("recipient", sc, Scopes::Character);
1376    vd.field_target_ok_this("secondary_actor", sc, Scopes::Character);
1377    vd.field_target_ok_this("secondary_recipient", sc, Scopes::Character);
1378    vd.field_target_ok_this("landed_title", sc, Scopes::LandedTitle);
1379}
1380
1381pub fn validate_contract_set_obligation_level(
1382    _key: &Token,
1383    _block: &Block,
1384    data: &Everything,
1385    sc: &mut ScopeContext,
1386    mut vd: Validator,
1387    _tooltipped: Tooltipped,
1388) {
1389    vd.req_field("type");
1390    vd.req_field("level");
1391    if let Some(token) = vd.field_value("type")
1392        && !data.item_exists(Item::SubjectContract, token.as_str())
1393    {
1394        validate_target(token, data, sc, Scopes::VassalContract);
1395    }
1396    if let Some(token) = vd.field_value("level")
1397        && !token.is_integer()
1398        && !data.item_exists(Item::SubjectContractObligationLevel, token.as_str())
1399    {
1400        validate_target(token, data, sc, Scopes::VassalObligationLevel);
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}
2474
2475pub fn validate_impact_house_relation(
2476    _key: &Token,
2477    _block: &Block,
2478    _data: &Everything,
2479    sc: &mut ScopeContext,
2480    mut vd: Validator,
2481    _tooltipped: Tooltipped,
2482) {
2483    vd.req_field("target");
2484    vd.field_target("target", sc, Scopes::DynastyHouse);
2485    vd.field_item("type", Item::HouseRelationType);
2486    vd.field_script_value("steps", sc);
2487    vd.field_validated_sc("description", sc, validate_desc);
2488    vd.field_bool("notification");
2489    if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
2490        sc.define_name_token(name.as_str(), Scopes::HouseRelation, name, Temporary::No);
2491    }
2492}
2493
2494pub fn validate_set_regnal_name(
2495    _key: &Token,
2496    bv: &BV,
2497    data: &Everything,
2498    sc: &mut ScopeContext,
2499    _tooltipped: Tooltipped,
2500) {
2501    match bv {
2502        BV::Value(value) => {
2503            data.validate_localization_sc(value.as_str(), sc);
2504        }
2505        BV::Block(block) => {
2506            let mut vd = Validator::new(block, data);
2507            vd.req_field("character");
2508            vd.field_target("character", sc, Scopes::Character);
2509        }
2510    }
2511}