Skip to main content

tiger_lib/
effect.rs

1//! Validate effects, which are parts of the script that change the game state.
2
3use crate::block::{BV, Block, Comparator, Eq::*};
4use crate::context::{Reason, ScopeContext};
5#[cfg(feature = "jomini")]
6use crate::data::effect_localization::validate_effect_localization;
7use crate::desc::validate_desc;
8use crate::everything::Everything;
9use crate::game::Game;
10#[cfg(feature = "hoi4")]
11use crate::hoi4::variables::validate_variable;
12use crate::item::Item;
13use crate::lowercase::Lowercase;
14use crate::report::{ErrorKey, Severity, err, fatal, tips, warn};
15use crate::scopes::{Scopes, scope_iterator};
16#[cfg(feature = "jomini")]
17use crate::script_value::validate_script_value;
18use crate::special_tokens::SpecialTokens;
19use crate::token::Token;
20use crate::tooltipped::Tooltipped;
21use crate::trigger::scope_trigger;
22#[cfg(any(feature = "ck3", feature = "imperator"))]
23use crate::trigger::validate_target_ok_this;
24use crate::trigger::{validate_target, validate_trigger};
25#[cfg(any(feature = "ck3", feature = "vic3"))]
26use crate::validate::validate_compare_duration;
27#[cfg(any(feature = "ck3", feature = "imperator", feature = "hoi4"))]
28use crate::validate::validate_modifiers;
29#[cfg(feature = "ck3")]
30use crate::validate::validate_possibly_named_color;
31#[cfg(feature = "jomini")]
32use crate::validate::validate_scripted_modifier_call;
33use crate::validate::{
34    ListType, precheck_iterator_fields, validate_identifier, validate_ifelse_sequence,
35    validate_inside_iterator, validate_iterator_fields, validate_scope_chain,
36};
37use crate::validator::{Validator, ValueValidator};
38
39/// Look up an effect name token in the effects table.
40/// `name` is the token. `data` is used in special cases to verify the name dynamically.
41pub fn scope_effect(name: &Token, data: &Everything) -> Option<(Scopes, Effect)> {
42    let scope_effect = match Game::game() {
43        #[cfg(feature = "ck3")]
44        Game::Ck3 => crate::ck3::tables::effects::scope_effect,
45        #[cfg(feature = "vic3")]
46        Game::Vic3 => crate::vic3::tables::effects::scope_effect,
47        #[cfg(feature = "imperator")]
48        Game::Imperator => crate::imperator::tables::effects::scope_effect,
49        #[cfg(feature = "eu5")]
50        Game::Eu5 => crate::eu5::tables::effects::scope_effect,
51        #[cfg(feature = "hoi4")]
52        Game::Hoi4 => crate::hoi4::tables::effects::scope_effect,
53    };
54    scope_effect(name, data)
55}
56
57/// The standard interface to effect validation. Validates an effect in the given [`ScopeContext`].
58///
59/// `tooltipped` determines what warnings are emitted related to tooltippability of the effects
60/// inside the block.
61pub fn validate_effect(
62    block: &Block,
63    data: &Everything,
64    sc: &mut ScopeContext,
65    tooltipped: Tooltipped,
66) -> bool {
67    let mut vd = Validator::new(block, data);
68    validate_effect_internal(
69        Lowercase::empty(),
70        ListType::None,
71        block,
72        data,
73        sc,
74        &mut vd,
75        tooltipped,
76        &mut SpecialTokens::none(),
77    )
78}
79
80/// The interface to effect validation when [`validate_effect`] is too limited.
81///
82/// `caller` is the key that opened this effect. It is used to determine which special cases apply.
83/// For example, if `caller` is `if` then a `limit` block is expected.
84///
85/// `list_type` specifies whether this effect is directly in an iterator of the specified
86/// [`ListType`]. It is also used to determine which special cases apply. The `list_type` must not
87/// be `ListType::Any` because that is only used for triggers.
88///
89/// `vd` is a `Validator` that may have already been checked for some fields by the caller. This is
90/// because some effects are in contexts where other keys than just the effects are valid.
91#[allow(clippy::too_many_arguments)]
92pub fn validate_effect_internal(
93    caller: &Lowercase,
94    list_type: ListType,
95    block: &Block,
96    data: &Everything,
97    sc: &mut ScopeContext,
98    vd: &mut Validator,
99    mut tooltipped: Tooltipped,
100    special_tokens: &mut SpecialTokens,
101) -> bool {
102    vd.set_case_sensitive(false);
103    if caller == "if"
104        || caller == "else_if"
105        || caller == "else"
106        || caller == "while"
107        || list_type != ListType::None
108    {
109        vd.field_validated_key_block("limit", |key, block, data| {
110            if caller == "else" {
111                let msg = "`else` with a `limit` does work, but may indicate a mistake";
112                let info = "normally you would use `else_if` instead.";
113                tips(ErrorKey::IfElse).msg(msg).info(info).loc(key).push();
114            }
115            validate_trigger(block, data, sc, Tooltipped::No);
116        });
117    } else {
118        vd.ban_field("limit", || "if/else_if or lists");
119    }
120
121    #[allow(clippy::if_not_else)] // for symmetry
122    if list_type != ListType::None {
123        vd.field_trigger("filter", Tooltipped::No, sc);
124    } else {
125        vd.ban_field("filter", || "lists");
126    }
127
128    validate_iterator_fields(caller, list_type, data, sc, vd, &mut tooltipped, false);
129
130    if list_type != ListType::None {
131        validate_inside_iterator(caller, list_type, block, data, sc, vd, tooltipped);
132    }
133
134    validate_ifelse_sequence(block, "if", "else_if", "else");
135
136    vd.set_allow_questionmark_equals(true);
137    let mut has_tooltip = false;
138    vd.unknown_fields_cmp(|key, cmp, bv| {
139        has_tooltip |=
140            validate_effect_field(caller, key, cmp, bv, data, sc, tooltipped, special_tokens);
141    });
142    has_tooltip
143}
144
145/// Validate a single effect field
146#[allow(unused_variables)] // hoi4 does not use `caller`
147#[allow(clippy::too_many_arguments)]
148pub fn validate_effect_field(
149    caller: &Lowercase,
150    key: &Token,
151    cmp: Comparator,
152    bv: &BV,
153    data: &Everything,
154    sc: &mut ScopeContext,
155    tooltipped: Tooltipped,
156    special_tokens: &mut SpecialTokens,
157) -> bool {
158    let mut has_tooltip = false;
159    if let Some(effect) = data.get_effect(key) {
160        match bv {
161            BV::Value(token) => {
162                if !effect.macro_parms().is_empty() {
163                    fatal(ErrorKey::Macro).msg("expected macro arguments").loc(token).push();
164                } else if !token.is("yes") {
165                    warn(ErrorKey::Validation).msg("expected just effect = yes").loc(token).push();
166                }
167                has_tooltip |= effect.validate_call(key, data, sc, tooltipped, special_tokens);
168            }
169            BV::Block(block) => {
170                let parms = effect.macro_parms();
171                if parms.is_empty() {
172                    err(ErrorKey::Macro)
173                        .msg("this scripted effect does not need macro arguments")
174                        .info("you can just use it as effect = yes")
175                        .loc(block)
176                        .push();
177                } else {
178                    let mut vec = Vec::new();
179                    let mut vd = Validator::new(block, data);
180                    for parm in &parms {
181                        if let Some(token) = vd.field_value(parm) {
182                            vec.push(token.clone());
183                        } else {
184                            let msg = format!("this scripted effect needs parameter {parm}");
185                            err(ErrorKey::Macro).msg(msg).loc(block).push();
186                            return false;
187                        }
188                    }
189                    vd.unknown_value_fields(|key, _value| {
190                        let msg = format!("this scripted effect does not need parameter {key}");
191                        let info = "supplying an unneeded parameter often causes a crash";
192                        fatal(ErrorKey::Macro).msg(msg).info(info).loc(key).push();
193                    });
194                    let args: Vec<_> = parms.into_iter().zip(vec).collect();
195                    has_tooltip |= effect.validate_macro_expansion(
196                        key,
197                        &args,
198                        data,
199                        sc,
200                        tooltipped,
201                        special_tokens,
202                    );
203                }
204            }
205        }
206        return has_tooltip && tooltipped.is_tooltipped();
207    }
208
209    #[cfg(feature = "jomini")]
210    if Game::is_jomini()
211        && let Some(modifier) = data.scripted_modifiers.get(key.as_str())
212    {
213        if caller != "random" && caller != "random_list" && caller != "duel" {
214            let msg = "cannot use scripted modifier here";
215            err(ErrorKey::Validation).msg(msg).loc(key).push();
216            return false;
217        }
218        validate_scripted_modifier_call(key, bv, modifier, data, sc);
219        return false;
220    }
221
222    if let Some((inscopes, effect)) = scope_effect(key, data) {
223        sc.expect(inscopes, &Reason::Token(key.clone()), data);
224        #[cfg(feature = "jomini")]
225        if tooltipped.is_tooltipped() {
226            has_tooltip |= data.item_exists(Item::EffectLocalization, key.as_str());
227        }
228        match effect {
229            Effect::Yes => {
230                if let Some(token) = bv.expect_value()
231                    && !token.is("yes")
232                {
233                    let msg = format!("expected just `{key} = yes`");
234                    warn(ErrorKey::Validation).msg(msg).loc(token).push();
235                }
236            }
237            Effect::Boolean => {
238                if let Some(token) = bv.expect_value() {
239                    validate_target(token, data, sc, Scopes::Bool);
240                }
241            }
242            Effect::Integer => {
243                if let Some(token) = bv.expect_value() {
244                    token.expect_integer();
245                }
246            }
247            Effect::ScriptValue | Effect::NonNegativeValue => {
248                if let Some(token) = bv.get_value()
249                    && let Some(number) = token.get_number()
250                    && matches!(effect, Effect::NonNegativeValue)
251                    && number < 0.0
252                {
253                    if key.is("add_gold") {
254                        let msg = "add_gold does not take negative numbers";
255                        let info = "try remove_short_term_gold instead";
256                        warn(ErrorKey::Range).msg(msg).info(info).loc(token).push();
257                    } else {
258                        let msg = format!("{key} does not take negative numbers");
259                        warn(ErrorKey::Range).msg(msg).loc(token).push();
260                    }
261                }
262                #[cfg(feature = "jomini")]
263                if Game::is_jomini() {
264                    validate_script_value(bv, data, sc);
265                }
266                // TODO HOI4
267            }
268            #[cfg(feature = "vic3")]
269            Effect::Date => {
270                if let Some(token) = bv.expect_value() {
271                    token.expect_date();
272                }
273            }
274            Effect::Scope(outscopes) => {
275                if let Some(token) = bv.expect_value() {
276                    validate_target(token, data, sc, outscopes);
277                }
278            }
279            #[cfg(any(feature = "ck3", feature = "imperator"))]
280            Effect::ScopeOkThis(outscopes) => {
281                if let Some(token) = bv.expect_value() {
282                    validate_target_ok_this(token, data, sc, outscopes);
283                }
284            }
285            Effect::Item(itype) => {
286                if let Some(token) = bv.expect_value() {
287                    data.verify_exists(itype, token);
288                }
289            }
290            Effect::ScopeOrItem(outscopes, itype) => {
291                if let Some(token) = bv.expect_value()
292                    && !data.item_exists(itype, token.as_str())
293                {
294                    validate_target(token, data, sc, outscopes);
295                }
296            }
297            #[cfg(feature = "ck3")]
298            Effect::Target(key, outscopes) => {
299                if let Some(block) = bv.expect_block() {
300                    let mut vd = Validator::new(block, data);
301                    vd.set_case_sensitive(false);
302                    vd.req_field(key);
303                    vd.field_target(key, sc, outscopes);
304                }
305            }
306            #[cfg(any(feature = "ck3", feature = "vic3"))]
307            Effect::TargetValue(key, outscopes, valuekey) => {
308                if let Some(block) = bv.expect_block() {
309                    let mut vd = Validator::new(block, data);
310                    vd.set_case_sensitive(false);
311                    vd.req_field(key);
312                    vd.req_field(valuekey);
313                    vd.field_target(key, sc, outscopes);
314                    vd.field_script_value(valuekey, sc);
315                }
316            }
317            #[cfg(any(feature = "ck3", feature = "hoi4"))]
318            Effect::ItemTarget(ikey, itype, tkey, outscopes) => {
319                if let Some(block) = bv.expect_block() {
320                    let mut vd = Validator::new(block, data);
321                    vd.set_case_sensitive(false);
322                    vd.field_item(ikey, itype);
323                    vd.field_target(tkey, sc, outscopes);
324                }
325            }
326            #[cfg(any(feature = "ck3", feature = "vic3"))]
327            Effect::ItemValue(key, itype, valuekey) => {
328                if let Some(block) = bv.expect_block() {
329                    let mut vd = Validator::new(block, data);
330                    vd.set_case_sensitive(false);
331                    vd.req_field(key);
332                    vd.req_field(valuekey);
333                    vd.field_item(key, itype);
334                    vd.field_script_value(valuekey, sc);
335                }
336            }
337            Effect::Choice(choices) => {
338                if let Some(token) = bv.expect_value()
339                    && !choices.contains(&token.as_str())
340                {
341                    let msg = format!("expected one of {}", choices.join(", "));
342                    err(ErrorKey::Choice).msg(msg).loc(token).push();
343                }
344            }
345            #[cfg(feature = "ck3")]
346            Effect::Desc => validate_desc(bv, data, sc),
347            #[cfg(any(feature = "ck3", feature = "vic3"))]
348            Effect::Timespan => {
349                if let Some(block) = bv.expect_block() {
350                    validate_compare_duration(block, data, sc);
351                }
352            }
353            Effect::Vb(f) => {
354                if let Some(block) = bv.expect_block() {
355                    let mut vd = Validator::new(block, data);
356                    vd.set_case_sensitive(false);
357                    f(key, block, data, sc, vd, tooltipped);
358                }
359            }
360            Effect::Vbc(f) => {
361                if let Some(block) = bv.expect_block() {
362                    let mut vd = Validator::new(block, data);
363                    vd.set_case_sensitive(false);
364                    f(key, block, data, sc, vd, tooltipped, special_tokens);
365                }
366            }
367            Effect::Vv(f) => {
368                if let Some(token) = bv.expect_value() {
369                    let vd = ValueValidator::new(token, data);
370                    f(key, vd, sc, tooltipped);
371                }
372            }
373            Effect::Vbv(f) => {
374                f(key, bv, data, sc, tooltipped);
375            }
376            Effect::ControlOrLabel => match bv {
377                BV::Value(t) => {
378                    data.verify_exists(Item::Localization, t);
379                    data.validate_localization_sc(t.as_str(), sc);
380                }
381                BV::Block(b) => {
382                    has_tooltip |= validate_effect_control(
383                        &Lowercase::new(key.as_str()),
384                        b,
385                        data,
386                        sc,
387                        tooltipped,
388                        special_tokens,
389                    );
390                }
391            },
392            Effect::Control => {
393                if let Some(block) = bv.expect_block() {
394                    let local_has_tooltip = validate_effect_control(
395                        &Lowercase::new(key.as_str()),
396                        block,
397                        data,
398                        sc,
399                        tooltipped,
400                        special_tokens,
401                    );
402                    if local_has_tooltip && key.is("random") {
403                        special_tokens.insert(key);
404                    }
405                    has_tooltip |= local_has_tooltip;
406                }
407            }
408            #[cfg(feature = "hoi4")]
409            Effect::Iterator(ltype, outscope) => {
410                let it_name = key.split_once('_').unwrap().1;
411                if let Some(block) = bv.expect_block() {
412                    precheck_iterator_fields(ltype, it_name.as_str(), block, data, sc);
413                    sc.open_scope(outscope, key.clone());
414                    let mut vd = Validator::new(block, data);
415                    has_tooltip |= validate_effect_internal(
416                        &Lowercase::new(it_name.as_str()),
417                        ltype,
418                        block,
419                        data,
420                        sc,
421                        &mut vd,
422                        tooltipped,
423                        special_tokens,
424                    );
425                    sc.close();
426                }
427            }
428            Effect::Identifier(kind) => {
429                if let Some(token) = bv.expect_value() {
430                    validate_identifier(token, kind, Severity::Error);
431                }
432            }
433            #[cfg(feature = "hoi4")]
434            Effect::Value => {
435                if let Some(token) = bv.expect_value() {
436                    validate_target(token, data, sc, Scopes::Value);
437                }
438            }
439            #[cfg(feature = "ck3")]
440            Effect::Color => {
441                validate_possibly_named_color(bv, data);
442            }
443            Effect::Removed(version, explanation) => {
444                let msg = format!("`{key}` was removed in {version}");
445                warn(ErrorKey::Removed).msg(msg).info(explanation).loc(key).push();
446            }
447            Effect::Unchecked => (),
448            #[cfg(any(feature = "ck3", feature = "vic3", feature = "eu5", feature = "hoi4"))]
449            Effect::UncheckedTodo => (),
450        }
451        return has_tooltip && tooltipped.is_tooltipped();
452    }
453
454    if let Some((it_type, it_name)) = key.split_once('_')
455        && let Ok(ltype) = ListType::try_from(it_type.as_str())
456        && let Some((inscopes, outscope)) = scope_iterator(&it_name, data, sc)
457    {
458        if ltype.is_for_triggers() {
459            let msg = format!("cannot use `{it_type}_` lists in an effect");
460            err(ErrorKey::Validation).msg(msg).loc(key).push();
461            return false;
462        }
463        sc.expect(inscopes, &Reason::Token(key.clone()), data);
464        if let Some(b) = bv.expect_block() {
465            precheck_iterator_fields(ltype, it_name.as_str(), b, data, sc);
466            sc.open_scope(outscope, key.clone());
467            let mut vd = Validator::new(b, data);
468            has_tooltip |= validate_effect_internal(
469                &Lowercase::new(it_name.as_str()),
470                ltype,
471                b,
472                data,
473                sc,
474                &mut vd,
475                tooltipped,
476                special_tokens,
477            );
478            sc.close();
479        }
480        return has_tooltip && tooltipped.is_tooltipped();
481    }
482
483    #[cfg(feature = "hoi4")]
484    if Game::is_hoi4() && key.starts_with("var:") {
485        validate_variable(key, data, sc, Severity::Error);
486        if let Some(block) = bv.expect_block() {
487            sc.open_scope(Scopes::all_but_none(), key.clone());
488            has_tooltip |= validate_effect(block, data, sc, tooltipped);
489            sc.close();
490        }
491        return has_tooltip;
492    }
493
494    // skip this check for imperator because it has too many dynamic triggers that get
495    // inappropriately matched.
496    if !Game::is_imperator() && scope_trigger(key, data).is_some() {
497        let msg = format!("`{key}` is a trigger and can't be used as an effect");
498        err(ErrorKey::WrongUse).msg(msg).loc(key).push();
499        return false;
500    }
501
502    // Check if it's a target = { target_scope } block.
503    sc.open_builder();
504    if validate_scope_chain(key, data, sc, matches!(cmp, Comparator::Equals(Question))) {
505        sc.finalize_builder();
506        if Game::is_ck3() && key.starts_with("flag:") {
507            let msg = "as of 1.9, flag literals cannot be used on the left-hand side";
508            err(ErrorKey::Scopes).msg(msg).loc(key).push();
509        }
510        if let Some(block) = bv.expect_block() {
511            has_tooltip |= validate_effect(block, data, sc, tooltipped);
512        }
513    }
514    sc.close();
515    has_tooltip && tooltipped.is_tooltipped()
516}
517
518/// Validate an effect that has other effects inside its block.
519pub fn validate_effect_control(
520    caller: &Lowercase,
521    block: &Block,
522    data: &Everything,
523    sc: &mut ScopeContext,
524    mut tooltipped: Tooltipped,
525    special_tokens: &mut SpecialTokens,
526) -> bool {
527    let mut vd = Validator::new(block, data);
528
529    if caller == "if" || caller == "else_if" {
530        vd.req_field_warn("limit");
531    }
532
533    #[cfg(feature = "jomini")]
534    if Game::is_jomini()
535        && (caller == "custom_description"
536            || caller == "custom_description_no_bullet"
537            || caller == "custom_tooltip"
538            || caller == "custom_tooltip_no_bullet"
539            || caller == "custom_label"
540            || caller == "custom_label_no_bullet")
541    {
542        vd.req_field("text");
543        if caller == "custom_tooltip"
544            || caller == "custom_tooltip_no_bullet"
545            || caller == "custom_label"
546            || caller == "custom_label_no_bullet"
547        {
548            vd.field_item("text", Item::Localization);
549            if let Some(value) = block.get_field_value("text") {
550                data.validate_localization_sc(value.as_str(), sc);
551            }
552        } else if let Some(token) = vd.field_value("text") {
553            validate_effect_localization(token, data, tooltipped);
554        }
555        vd.field_target_ok_this("subject", sc, Scopes::non_primitive());
556        tooltipped = Tooltipped::No;
557    } else {
558        vd.ban_field("text", || "`custom_description` or `custom_tooltip`");
559        vd.ban_field("subject", || "`custom_description` or `custom_tooltip`");
560    }
561
562    #[cfg(feature = "jomini")]
563    if Game::is_jomini() {
564        if caller == "custom_description" || caller == "custom_description_no_bullet" {
565            vd.field_target_ok_this("object", sc, Scopes::non_primitive());
566            vd.field_script_value("value", sc);
567        } else {
568            vd.ban_field("object", || "`custom_description`");
569        }
570    }
571
572    if caller == "hidden_effect" || caller == "hidden_effect_new_object" {
573        tooltipped = Tooltipped::No;
574    }
575
576    if caller == "random" {
577        vd.req_field("chance");
578        if Game::is_jomini() {
579            #[cfg(feature = "jomini")]
580            vd.field_script_value("chance", sc);
581        } else {
582            // TODO HOI4
583            vd.field_numeric("chance");
584        }
585    } else {
586        vd.ban_field("chance", || "`random`");
587    }
588
589    if caller == "while" {
590        // TODO HOI4
591        if !(block.has_key("limit") || block.has_key("count")) {
592            let msg = "`while` needs one of `limit` or `count`";
593            warn(ErrorKey::Validation).msg(msg).loc(block).push();
594        }
595
596        if Game::is_jomini() {
597            #[cfg(feature = "jomini")]
598            vd.field_script_value("count", sc);
599        }
600    } else {
601        vd.ban_field("count", || "`while` and `any_` lists");
602    }
603
604    if caller == "random" || caller == "random_list" || caller == "duel" {
605        #[cfg(feature = "vic3")]
606        if Game::is_vic3() {
607            // docs warn there should not be multiple modifier blocks
608            vd.field_script_value("modifier", sc);
609        }
610        #[cfg(any(feature = "imperator", feature = "ck3", feature = "hoi4"))]
611        if Game::is_imperator() || Game::is_ck3() || Game::is_hoi4() {
612            validate_modifiers(&mut vd, sc);
613        }
614    } else {
615        vd.ban_field("modifier", || "`random`, `random_list` or `duel`");
616        vd.ban_field("compare_modifier", || "`random`, `random_list` or `duel`");
617        vd.ban_field("opinion_modifier", || "`random`, `random_list` or `duel`");
618        vd.ban_field("ai_value_modifier", || "`random`, `random_list` or `duel`");
619        vd.ban_field("compatibility", || "`random`, `random_list` or `duel`");
620    }
621
622    if caller == "random_list" || caller == "duel" {
623        vd.field_trigger("trigger", Tooltipped::No, sc);
624        vd.field_bool("show_chance");
625        vd.field_validated_sc("desc", sc, validate_desc);
626        #[cfg(feature = "jomini")]
627        if Game::is_jomini() {
628            vd.field_script_value("min", sc); // used in vanilla
629            vd.field_script_value("max", sc); // used in vanilla
630        }
631    } else {
632        vd.ban_field("trigger", || "`random_list` or `duel`");
633        vd.ban_field("show_chance", || "`random_list` or `duel`");
634    }
635
636    validate_effect_internal(
637        caller,
638        ListType::None,
639        block,
640        data,
641        sc,
642        &mut vd,
643        tooltipped,
644        special_tokens,
645    )
646}
647
648/// This `enum` describes what arguments an effect takes, so that they can be validated.
649///
650/// Since effects are so varied, many of them end up as special cases described by the `VB`, `VBv`,
651/// and `VV` variants. Common patterns can be captured here though.
652///
653/// TODO: adding a "Block" syntax similar to that in triggers may be helpful. It could remove some
654/// of the variants that currently have very few users, and it could remove some of the special
655/// cases.
656#[derive(Copy, Clone)]
657#[allow(dead_code)] // TODO: remove when hoi4 is complete
658pub enum Effect {
659    /// No special value, just `effect = yes`.
660    Yes,
661    /// Yes and no are both meaningful. The difference between this and [`Effect::Yes`] can be hard
662    /// to distinguish.
663    /// TODO: needs testing.
664    Boolean,
665    /// The effect takes a literal integer. It's not clear whether effects of this type actually
666    /// exist or if they're all secrectly [`Effect::ScriptValue`].
667    /// TODO: needs testing.
668    Integer,
669    /// The effect takes a script value, which can be a literal number or a named script value or an
670    /// inline script value block.
671    ScriptValue,
672    /// Just like [`Effect::ScriptValue`], but warns if the argument is a negative literal number.
673    #[allow(dead_code)]
674    NonNegativeValue,
675    /// The effect takes a literal date.
676    #[cfg(feature = "vic3")]
677    Date,
678    /// The effect takes a target value that must evaluate to a scope type in the given [`Scopes`] value.
679    ///
680    /// * Example: `set_county_culture = root.culture`
681    Scope(Scopes),
682    /// Just like [`Effect::Scope`] but it doesn't warn if the target is a literal `this`. The
683    /// default behavior for targets is to warn about that, because it's usually a mistake.
684    ///
685    /// * Example: `destroy_artifact = this`
686    #[cfg(any(feature = "ck3", feature = "imperator"))]
687    ScopeOkThis(Scopes),
688    /// The effect takes a literal string that must exist in the item database for the given [`Item`] type.
689    ///
690    /// * Example: `add_perk = iron_constitution_perk`
691    Item(Item),
692    /// A combination of [`Effect::Scope`] and [`Effect::Item`]. The argument is first checked to see
693    /// if it's a literal [`Item`], and if not, it's evaluated as a target. This can sometimes
694    /// cause strange error messages if the argument was intended to be an item but just doesn't exist.
695    ///
696    /// * Example: `add_trait = cannibal`
697    /// * Example: `add_trait = scope:learned_trait`
698    ScopeOrItem(Scopes, Item),
699    /// The effect takes a block that contains a single field, named here, which is a target that
700    /// must evaluate to a scope type in the given [`Scopes`] value.
701    ///
702    /// * Only example: `becomes_independent = { change = scope:change }`
703    #[cfg(feature = "ck3")]
704    Target(&'static str, Scopes),
705    /// The effect takes a block with two fields, both named here, where one specifies a target of
706    /// the given [`Scopes`] type and the other specifies a script value.
707    ///
708    /// * Example: `change_de_jure_drift_progress = { target = root.primary_title value = 5 }`
709    #[cfg(any(feature = "ck3", feature = "vic3"))]
710    TargetValue(&'static str, Scopes, &'static str),
711    /// The effect takes a block with two fields, both named here, where one specifies a key for
712    /// the given [`Item`] type and the other specifies a target of the given [`Scopes`] type.
713    ///
714    /// * Example: `remove_hook = { type = indebted_hook target = scope:old_caliph }`
715    #[cfg(any(feature = "ck3", feature = "hoi4"))]
716    ItemTarget(&'static str, Item, &'static str, Scopes),
717    /// The effect takes a block with two fields, both named here, where one specifies a key for
718    /// the given [`Item`] type and the other specifies a script value.
719    ///
720    /// * Example: `set_amenity_level = { type = court_food_quality value = 3 }`
721    #[cfg(any(feature = "ck3", feature = "vic3"))]
722    ItemValue(&'static str, Item, &'static str),
723    /// The effect takes either a localization key or a description block with `first_valid` etc.
724    ///
725    /// * Example: `set_artifact_name = relic_weapon_name`
726    #[cfg(feature = "ck3")]
727    Desc,
728    /// The effect takes a duration, with a `days`, `weeks`, `months`, or `years` script value.
729    ///
730    /// * Example: `add_destination_progress = { days = 5 }`
731    #[cfg(any(feature = "ck3", feature = "vic3"))]
732    Timespan,
733    /// The effect takes a block that contains other effects.
734    ///
735    /// * Examples: `if`, `while`, `custom_description`
736    Control,
737    /// The effect takes either a localization key, or a block that contains other effects.
738    /// This variant is used by `custom_tooltip`.
739    ControlOrLabel,
740    /// The effect is an iterator that does not fit the regular pattern
741    #[cfg(feature = "hoi4")]
742    Iterator(ListType, Scopes),
743    /// This variant is for effects that can take any argument and it's not validated.
744    /// The effect is too unusual, or not worth checking, or really any argument is fine.
745    ///
746    /// * Examples: `assert_if`, `debug_log`, `remove_variable`
747    Unchecked,
748    /// This variant is for effects that we haven't gotten around to validating yet.
749    #[cfg(any(feature = "ck3", feature = "vic3", feature = "eu5", feature = "hoi4"))]
750    UncheckedTodo,
751    /// The effect takes a literal string that is one of the options given here.
752    ///
753    /// * Example: `end_war = white_peace`
754    Choice(&'static [&'static str]),
755    /// The effect is no longer valid; warn if it's still being used.
756    /// The first string is the game version number where it was removed and the second string is
757    /// an explanation that suggests a different effect to try. The second string may be empty.
758    Removed(&'static str, &'static str),
759    /// The effect takes a block that will be validated by this function
760    Vb(fn(&Token, &Block, &Everything, &mut ScopeContext, Validator, Tooltipped)),
761    /// The effect is a control effect (containing other effects) and it takes a block that will be validated by this function
762    Vbc(
763        #[allow(clippy::type_complexity)]
764        fn(
765            &Token,
766            &Block,
767            &Everything,
768            &mut ScopeContext,
769            Validator,
770            Tooltipped,
771            &mut SpecialTokens,
772        ) -> bool,
773    ),
774    /// The effect takes a block or value that will be validated by this function
775    Vbv(fn(&Token, &BV, &Everything, &mut ScopeContext, Tooltipped)),
776    /// The effect takes a value that will be validated by this function
777    Vv(fn(&Token, ValueValidator, &mut ScopeContext, Tooltipped)),
778    /// The effect takes a single word.
779    /// The parameter is a description of what kind of identifier is expected.
780    Identifier(&'static str),
781    /// The effect takes a number or an expression that produces a value.
782    /// This is for Hoi4 which doesn't have script values.
783    #[cfg(feature = "hoi4")]
784    Value,
785    /// The effect takes a possibly named color value
786    #[cfg(feature = "ck3")]
787    Color,
788}