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        if let Some(modifier) = data.scripted_modifiers.get(key.as_str()) {
212            if caller != "random" && caller != "random_list" && caller != "duel" {
213                let msg = "cannot use scripted modifier here";
214                err(ErrorKey::Validation).msg(msg).loc(key).push();
215                return false;
216            }
217            validate_scripted_modifier_call(key, bv, modifier, data, sc);
218            return false;
219        }
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                    if !token.is("yes") {
232                        let msg = format!("expected just `{key} = yes`");
233                        warn(ErrorKey::Validation).msg(msg).loc(token).push();
234                    }
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                    if let Some(number) = token.get_number() {
250                        if matches!(effect, Effect::NonNegativeValue) && number < 0.0 {
251                            if key.is("add_gold") {
252                                let msg = "add_gold does not take negative numbers";
253                                let info = "try remove_short_term_gold instead";
254                                warn(ErrorKey::Range).msg(msg).info(info).loc(token).push();
255                            } else {
256                                let msg = format!("{key} does not take negative numbers");
257                                warn(ErrorKey::Range).msg(msg).loc(token).push();
258                            }
259                        }
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                    if !data.item_exists(itype, token.as_str()) {
293                        validate_target(token, data, sc, outscopes);
294                    }
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(feature = "ck3")]
327            Effect::ItemValue(key, itype) => {
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("value");
333                    vd.field_item(key, itype);
334                    vd.field_script_value("value", sc);
335                }
336            }
337            Effect::Choice(choices) => {
338                if let Some(token) = bv.expect_value() {
339                    if !choices.contains(&token.as_str()) {
340                        let msg = format!("expected one of {}", choices.join(", "));
341                        err(ErrorKey::Choice).msg(msg).loc(token).push();
342                    }
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                }
426                sc.close();
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        if let Ok(ltype) = ListType::try_from(it_type.as_str()) {
456            if let Some((inscopes, outscope)) = scope_iterator(&it_name, data, sc) {
457                if ltype.is_for_triggers() {
458                    let msg = format!("cannot use `{it_type}_` lists in an effect");
459                    err(ErrorKey::Validation).msg(msg).loc(key).push();
460                    return false;
461                }
462                sc.expect(inscopes, &Reason::Token(key.clone()), data);
463                if let Some(b) = bv.expect_block() {
464                    precheck_iterator_fields(ltype, it_name.as_str(), b, data, sc);
465                    sc.open_scope(outscope, key.clone());
466                    let mut vd = Validator::new(b, data);
467                    has_tooltip |= validate_effect_internal(
468                        &Lowercase::new(it_name.as_str()),
469                        ltype,
470                        b,
471                        data,
472                        sc,
473                        &mut vd,
474                        tooltipped,
475                        special_tokens,
476                    );
477                }
478                sc.close();
479                return has_tooltip && tooltipped.is_tooltipped();
480            }
481        }
482    }
483
484    #[cfg(feature = "hoi4")]
485    if Game::is_hoi4() && key.starts_with("var:") {
486        validate_variable(key, data, sc, Severity::Error);
487        if let Some(block) = bv.expect_block() {
488            sc.open_scope(Scopes::all_but_none(), key.clone());
489            has_tooltip |= validate_effect(block, data, sc, tooltipped);
490            sc.close();
491        }
492        return has_tooltip;
493    }
494
495    // skip this check for imperator because it has too many dynamic triggers that get
496    // inappropriately matched.
497    if !Game::is_imperator() && scope_trigger(key, data).is_some() {
498        let msg = format!("`{key}` is a trigger and can't be used as an effect");
499        err(ErrorKey::WrongUse).msg(msg).loc(key).push();
500        return false;
501    }
502
503    // Check if it's a target = { target_scope } block.
504    sc.open_builder();
505    if validate_scope_chain(key, data, sc, matches!(cmp, Comparator::Equals(Question))) {
506        sc.finalize_builder();
507        if Game::is_ck3() && key.starts_with("flag:") {
508            let msg = "as of 1.9, flag literals cannot be used on the left-hand side";
509            err(ErrorKey::Scopes).msg(msg).loc(key).push();
510        }
511        if let Some(block) = bv.expect_block() {
512            has_tooltip |= validate_effect(block, data, sc, tooltipped);
513        }
514    }
515    sc.close();
516    has_tooltip && tooltipped.is_tooltipped()
517}
518
519/// Validate an effect that has other effects inside its block.
520pub fn validate_effect_control(
521    caller: &Lowercase,
522    block: &Block,
523    data: &Everything,
524    sc: &mut ScopeContext,
525    mut tooltipped: Tooltipped,
526    special_tokens: &mut SpecialTokens,
527) -> bool {
528    let mut vd = Validator::new(block, data);
529
530    if caller == "if" || caller == "else_if" {
531        vd.req_field_warn("limit");
532    }
533
534    #[cfg(feature = "jomini")]
535    if Game::is_jomini()
536        && (caller == "custom_description"
537            || caller == "custom_description_no_bullet"
538            || caller == "custom_tooltip"
539            || caller == "custom_label")
540    {
541        vd.req_field("text");
542        if caller == "custom_tooltip" || caller == "custom_label" {
543            vd.field_item("text", Item::Localization);
544            if let Some(value) = block.get_field_value("text") {
545                data.validate_localization_sc(value.as_str(), sc);
546            }
547        } else if let Some(token) = vd.field_value("text") {
548            validate_effect_localization(token, data, tooltipped);
549        }
550        vd.field_target_ok_this("subject", sc, Scopes::non_primitive());
551        tooltipped = Tooltipped::No;
552    } else {
553        vd.ban_field("text", || "`custom_description` or `custom_tooltip`");
554        vd.ban_field("subject", || "`custom_description` or `custom_tooltip`");
555    }
556
557    #[cfg(feature = "jomini")]
558    if Game::is_jomini() {
559        if caller == "custom_description" || caller == "custom_description_no_bullet" {
560            vd.field_target_ok_this("object", sc, Scopes::non_primitive());
561            vd.field_script_value("value", sc);
562        } else {
563            vd.ban_field("object", || "`custom_description`");
564        }
565    }
566
567    if caller == "hidden_effect" || caller == "hidden_effect_new_object" {
568        tooltipped = Tooltipped::No;
569    }
570
571    if caller == "random" {
572        vd.req_field("chance");
573        if Game::is_jomini() {
574            #[cfg(feature = "jomini")]
575            vd.field_script_value("chance", sc);
576        } else {
577            // TODO HOI4
578            vd.field_numeric("chance");
579        }
580    } else {
581        vd.ban_field("chance", || "`random`");
582    }
583
584    if caller == "while" {
585        // TODO HOI4
586        if !(block.has_key("limit") || block.has_key("count")) {
587            let msg = "`while` needs one of `limit` or `count`";
588            warn(ErrorKey::Validation).msg(msg).loc(block).push();
589        }
590
591        if Game::is_jomini() {
592            #[cfg(feature = "jomini")]
593            vd.field_script_value("count", sc);
594        }
595    } else {
596        vd.ban_field("count", || "`while` and `any_` lists");
597    }
598
599    if caller == "random" || caller == "random_list" || caller == "duel" {
600        #[cfg(feature = "vic3")]
601        if Game::is_vic3() {
602            // docs warn there should not be multiple modifier blocks
603            vd.field_script_value("modifier", sc);
604        }
605        #[cfg(any(feature = "imperator", feature = "ck3", feature = "hoi4"))]
606        if Game::is_imperator() || Game::is_ck3() || Game::is_hoi4() {
607            validate_modifiers(&mut vd, sc);
608        }
609    } else {
610        vd.ban_field("modifier", || "`random`, `random_list` or `duel`");
611        vd.ban_field("compare_modifier", || "`random`, `random_list` or `duel`");
612        vd.ban_field("opinion_modifier", || "`random`, `random_list` or `duel`");
613        vd.ban_field("ai_value_modifier", || "`random`, `random_list` or `duel`");
614        vd.ban_field("compatibility", || "`random`, `random_list` or `duel`");
615    }
616
617    if caller == "random_list" || caller == "duel" {
618        vd.field_trigger("trigger", Tooltipped::No, sc);
619        vd.field_bool("show_chance");
620        vd.field_validated_sc("desc", sc, validate_desc);
621        #[cfg(feature = "jomini")]
622        if Game::is_jomini() {
623            vd.field_script_value("min", sc); // used in vanilla
624            vd.field_script_value("max", sc); // used in vanilla
625        }
626    } else {
627        vd.ban_field("trigger", || "`random_list` or `duel`");
628        vd.ban_field("show_chance", || "`random_list` or `duel`");
629    }
630
631    validate_effect_internal(
632        caller,
633        ListType::None,
634        block,
635        data,
636        sc,
637        &mut vd,
638        tooltipped,
639        special_tokens,
640    )
641}
642
643/// This `enum` describes what arguments an effect takes, so that they can be validated.
644///
645/// Since effects are so varied, many of them end up as special cases described by the `VB`, `VBv`,
646/// and `VV` variants. Common patterns can be captured here though.
647///
648/// TODO: adding a "Block" syntax similar to that in triggers may be helpful. It could remove some
649/// of the variants that currently have very few users, and it could remove some of the special
650/// cases.
651#[derive(Copy, Clone)]
652#[allow(dead_code)] // TODO: remove when hoi4 is complete
653pub enum Effect {
654    /// No special value, just `effect = yes`.
655    Yes,
656    /// Yes and no are both meaningful. The difference between this and [`Effect::Yes`] can be hard
657    /// to distinguish.
658    /// TODO: needs testing.
659    Boolean,
660    /// The effect takes a literal integer. It's not clear whether effects of this type actually
661    /// exist or if they're all secrectly [`Effect::ScriptValue`].
662    /// TODO: needs testing.
663    Integer,
664    /// The effect takes a script value, which can be a literal number or a named script value or an
665    /// inline script value block.
666    ScriptValue,
667    /// Just like [`Effect::ScriptValue`], but warns if the argument is a negative literal number.
668    #[allow(dead_code)]
669    NonNegativeValue,
670    /// The effect takes a literal date.
671    #[cfg(feature = "vic3")]
672    Date,
673    /// The effect takes a target value that must evaluate to a scope type in the given [`Scopes`] value.
674    ///
675    /// * Example: `set_county_culture = root.culture`
676    Scope(Scopes),
677    /// Just like [`Effect::Scope`] but it doesn't warn if the target is a literal `this`. The
678    /// default behavior for targets is to warn about that, because it's usually a mistake.
679    ///
680    /// * Example: `destroy_artifact = this`
681    #[cfg(any(feature = "ck3", feature = "imperator"))]
682    ScopeOkThis(Scopes),
683    /// The effect takes a literal string that must exist in the item database for the given [`Item`] type.
684    ///
685    /// * Example: `add_perk = iron_constitution_perk`
686    Item(Item),
687    /// A combination of [`Effect::Scope`] and [`Effect::Item`]. The argument is first checked to see
688    /// if it's a literal [`Item`], and if not, it's evaluated as a target. This can sometimes
689    /// cause strange error messages if the argument was intended to be an item but just doesn't exist.
690    ///
691    /// * Example: `add_trait = cannibal`
692    /// * Example: `add_trait = scope:learned_trait`
693    ScopeOrItem(Scopes, Item),
694    /// The effect takes a block that contains a single field, named here, which is a target that
695    /// must evaluate to a scope type in the given [`Scopes`] value.
696    ///
697    /// * Only example: `becomes_independent = { change = scope:change }`
698    #[cfg(feature = "ck3")]
699    Target(&'static str, Scopes),
700    /// The effect takes a block with two fields, both named here, where one specifies a target of
701    /// the given [`Scopes`] type and the other specifies a script value.
702    ///
703    /// * Example: `change_de_jure_drift_progress = { target = root.primary_title value = 5 }`
704    #[cfg(any(feature = "ck3", feature = "vic3"))]
705    TargetValue(&'static str, Scopes, &'static str),
706    /// The effect takes a block with two fields, both named here, where one specifies a key for
707    /// the given [`Item`] type and the other specifies a target of the given [`Scopes`] type.
708    ///
709    /// * Example: `remove_hook = { type = indebted_hook target = scope:old_caliph }`
710    #[cfg(any(feature = "ck3", feature = "hoi4"))]
711    ItemTarget(&'static str, Item, &'static str, Scopes),
712    /// The effect takes a block with two fields, both named here, where one specifies a key for
713    /// the given [`Item`] type and the other specifies a script value.
714    ///
715    /// * Example: `set_amenity_level = { type = court_food_quality value = 3 }`
716    #[cfg(feature = "ck3")]
717    ItemValue(&'static str, Item),
718    /// The effect takes either a localization key or a description block with `first_valid` etc.
719    ///
720    /// * Example: `set_artifact_name = relic_weapon_name`
721    #[cfg(feature = "ck3")]
722    Desc,
723    /// The effect takes a duration, with a `days`, `weeks`, `months`, or `years` script value.
724    ///
725    /// * Example: `add_destination_progress = { days = 5 }`
726    #[cfg(any(feature = "ck3", feature = "vic3"))]
727    Timespan,
728    /// The effect takes a block that contains other effects.
729    ///
730    /// * Examples: `if`, `while`, `custom_description`
731    Control,
732    /// The effect takes either a localization key, or a block that contains other effects.
733    /// This variant is used by `custom_tooltip`.
734    ControlOrLabel,
735    /// The effect is an iterator that does not fit the regular pattern
736    #[cfg(feature = "hoi4")]
737    Iterator(ListType, Scopes),
738    /// This variant is for effects that can take any argument and it's not validated.
739    /// The effect is too unusual, or not worth checking, or really any argument is fine.
740    ///
741    /// * Examples: `assert_if`, `debug_log`, `remove_variable`
742    Unchecked,
743    /// This variant is for effects that we haven't gotten around to validating yet.
744    #[cfg(any(feature = "ck3", feature = "vic3", feature = "eu5", feature = "hoi4"))]
745    UncheckedTodo,
746    /// The effect takes a literal string that is one of the options given here.
747    ///
748    /// * Example: `end_war = white_peace`
749    Choice(&'static [&'static str]),
750    /// The effect is no longer valid; warn if it's still being used.
751    /// The first string is the game version number where it was removed and the second string is
752    /// an explanation that suggests a different effect to try. The second string may be empty.
753    Removed(&'static str, &'static str),
754    /// The effect takes a block that will be validated by this function
755    Vb(fn(&Token, &Block, &Everything, &mut ScopeContext, Validator, Tooltipped)),
756    /// The effect is a control effect (containing other effects) and it takes a block that will be validated by this function
757    Vbc(
758        #[allow(clippy::type_complexity)]
759        fn(
760            &Token,
761            &Block,
762            &Everything,
763            &mut ScopeContext,
764            Validator,
765            Tooltipped,
766            &mut SpecialTokens,
767        ) -> bool,
768    ),
769    /// The effect takes a block or value that will be validated by this function
770    Vbv(fn(&Token, &BV, &Everything, &mut ScopeContext, Tooltipped)),
771    /// The effect takes a value that will be validated by this function
772    Vv(fn(&Token, ValueValidator, &mut ScopeContext, Tooltipped)),
773    /// The effect takes a single word.
774    /// The parameter is a description of what kind of identifier is expected.
775    Identifier(&'static str),
776    /// The effect takes a number or an expression that produces a value.
777    /// This is for Hoi4 which doesn't have script values.
778    #[cfg(feature = "hoi4")]
779    Value,
780    /// The effect takes a possibly named color value
781    #[cfg(feature = "ck3")]
782    Color,
783}