tiger_lib/
modif.rs

1//! Validator for `modifs` which is our name for the basic things that modifiers modify.
2//!
3//! The main entry points are the [`validate_modifs`] function and the [`ModifKinds`] type.
4
5use std::fmt::Display;
6
7use bitflags::Flags;
8
9use crate::block::Block;
10use crate::everything::Everything;
11use crate::game::Game;
12#[cfg(feature = "hoi4")]
13use crate::hoi4::tables::modifs::modif_loc_hoi4;
14#[cfg(any(feature = "ck3", feature = "vic3", feature = "hoi4"))]
15use crate::item::Item;
16use crate::report::{ErrorKey, Severity, err};
17#[cfg(feature = "jomini")]
18use crate::script_value::validate_non_dynamic_script_value;
19use crate::token::Token;
20use crate::validator::Validator;
21#[cfg(feature = "vic3")]
22use crate::vic3::tables::modifs::modif_loc_vic3;
23
24/// All the things a modif can apply to.
25/// Many modifs are for multiple things, so this is a bitflags type.
26///
27/// This trait is used to warn when a modif is used inappropriately.
28/// For Imperator it is not yet known how important this is.
29pub trait ModifKinds: Display + Flags + Copy {
30    fn require(self, other: Self, token: &Token) {
31        if !self.intersects(other) {
32            let msg = format!("`{token}` is a modifier for {other} but expected {self}");
33            err(ErrorKey::Modifiers).msg(msg).loc(token).push();
34        }
35    }
36
37    /// Returns Some(kinds) if the token is a valid modif or *could* be a valid modif if the appropriate item existed.
38    /// Returns None otherwise.
39    fn lookup_modif(name: &Token, data: &Everything, warn: Option<Severity>) -> Option<Self>;
40}
41
42pub fn validate_modifs<'a, MK: ModifKinds>(
43    _block: &Block,
44    data: &'a Everything,
45    kinds: MK,
46    mut vd: Validator<'a>,
47) {
48    #[cfg(feature = "hoi4")]
49    vd.field_validated_block("hidden_modifier", |block, data| {
50        let mut vd = Validator::new(block, data);
51        // Same as below, but with no loca check
52        vd.unknown_fields(|key, bv| {
53            if let Some(mk) = MK::lookup_modif(key, data, Some(Severity::Error)) {
54                kinds.require(mk, key);
55
56                // TODO HOI4
57                let _ = &bv;
58            } else {
59                let msg = format!("unknown modifier `{key}`");
60                err(ErrorKey::UnknownField).msg(msg).loc(key).push();
61            }
62        });
63    });
64
65    #[cfg(feature = "hoi4")]
66    vd.field_item("custom_modifier_tooltip", Item::Localization);
67
68    vd.unknown_fields(|key, bv| {
69        #[cfg(feature = "hoi4")]
70        if Game::is_hoi4()
71            && (key.is("fort") || key.is("river") || data.item_exists(Item::Terrain, key.as_str()))
72        {
73            if let Some(block) = bv.expect_block() {
74                let mut vd = Validator::new(block, data);
75                vd.field_numeric("attack");
76                vd.field_numeric("movement");
77                vd.field_numeric("defence");
78            }
79            return;
80        }
81
82        if let Some(mk) = MK::lookup_modif(key, data, Some(Severity::Error)) {
83            kinds.require(mk, key);
84            if Game::is_jomini() {
85                #[cfg(feature = "jomini")]
86                validate_non_dynamic_script_value(bv, data);
87            } else {
88                // TODO HOI4
89                let _ = &bv;
90            }
91            #[cfg(feature = "ck3")]
92            if Game::is_ck3()
93                && !key.is("health")
94                && !key.is("elderly_health")
95                && !key.is("child_health")
96                && !key.is("negate_health_penalty_add")
97            {
98                data.verify_exists(Item::ModifierFormat, key);
99            }
100            #[cfg(feature = "vic3")]
101            if Game::is_vic3() {
102                // The loca should exist.
103                let (loca_key, loca_desc_key) = modif_loc_vic3(key, data);
104                data.verify_exists_implied(Item::Localization, &loca_key, key);
105                data.verify_exists_implied(Item::Localization, &loca_desc_key, key);
106            }
107            #[cfg(feature = "hoi4")]
108            if Game::is_hoi4() {
109                let loca_key = modif_loc_hoi4(key, data);
110                data.verify_exists_implied(Item::Localization, &loca_key, key);
111            }
112        }
113        // All modifiers are potentially valid in vic3
114        else if !Game::is_vic3() {
115            let msg = format!("unknown modifier `{key}`");
116            err(ErrorKey::UnknownField).msg(msg).loc(key).push();
117        }
118    });
119}
120
121pub fn verify_modif_exists<MK: ModifKinds>(
122    key: &Token,
123    data: &Everything,
124    kinds: MK,
125    sev: Severity,
126) {
127    if let Some(mk) = MK::lookup_modif(key, data, Some(sev)) {
128        kinds.require(mk, key);
129    }
130    // All modifiers are potentially valid in vic3
131    else if !Game::is_vic3() {
132        let msg = format!("unknown modifier `{key}`");
133        err(ErrorKey::UnknownField).msg(msg).loc(key).push();
134    }
135}