tiger_lib/ck3/data/
maa.rs

1use std::path::PathBuf;
2
3use crate::block::Block;
4use crate::ck3::validate::{validate_cost, validate_maa_stats};
5use crate::context::ScopeContext;
6use crate::everything::Everything;
7use crate::fileset::{FileEntry, FileHandler};
8use crate::helpers::{TigerHashMap, TigerHashSet, dup_error};
9use crate::item::Item;
10use crate::lowercase::Lowercase;
11use crate::parse::ParserMemory;
12use crate::pdxfile::PdxFile;
13use crate::report::{ErrorKey, Severity, warn};
14use crate::scopes::Scopes;
15use crate::token::Token;
16use crate::tooltipped::Tooltipped;
17use crate::validator::Validator;
18use crate::variables::Variables;
19
20#[derive(Clone, Debug, Default)]
21pub struct MenAtArmsTypes {
22    menatarmsbasetypes: TigerHashSet<Token>,
23    menatarmstypes: TigerHashMap<&'static str, MenAtArmsType>,
24    menatarmsbasetypes_lc: TigerHashSet<Lowercase<'static>>,
25}
26
27impl MenAtArmsTypes {
28    pub fn load_item(&mut self, key: Token, block: Block) {
29        if let Some(other) = self.menatarmstypes.get(key.as_str()) {
30            if other.key.loc.kind == key.loc.kind {
31                dup_error(&key, &other.key, "men-at-arms type");
32            }
33        }
34
35        self.menatarmstypes.insert(key.as_str(), MenAtArmsType::new(key, block));
36    }
37
38    pub fn scan_variables(&self, registry: &mut Variables) {
39        for item in self.menatarmstypes.values() {
40            registry.scan(&item.block);
41        }
42    }
43
44    pub fn base_exists(&self, key: &str) -> bool {
45        self.menatarmsbasetypes.contains(key)
46    }
47
48    pub fn base_exists_lc(&self, key: &Lowercase) -> bool {
49        self.menatarmsbasetypes_lc.contains(key)
50    }
51
52    pub fn iter_base_keys(&self) -> impl Iterator<Item = &Token> {
53        self.menatarmsbasetypes.iter()
54    }
55
56    pub fn exists(&self, key: &str) -> bool {
57        self.menatarmstypes.contains_key(key)
58    }
59
60    pub fn iter_keys(&self) -> impl Iterator<Item = &Token> {
61        self.menatarmstypes.values().map(|item| &item.key)
62    }
63
64    pub fn validate(&self, data: &Everything) {
65        for item in self.menatarmstypes.values() {
66            item.validate(data);
67        }
68    }
69}
70
71impl FileHandler<Block> for MenAtArmsTypes {
72    fn subpath(&self) -> PathBuf {
73        PathBuf::from("common/men_at_arms_types")
74    }
75
76    fn load_file(&self, entry: &FileEntry, parser: &ParserMemory) -> Option<Block> {
77        if !entry.filename().to_string_lossy().ends_with(".txt") {
78            return None;
79        }
80
81        PdxFile::read(entry, parser)
82    }
83
84    fn handle_file(&mut self, _entry: &FileEntry, mut block: Block) {
85        for (key, block) in block.drain_definitions_warn() {
86            self.load_item(key, block);
87        }
88    }
89
90    fn finalize(&mut self) {
91        for maa in self.menatarmstypes.values() {
92            if let Some(base) = maa.block.get_field_value("type") {
93                self.menatarmsbasetypes.insert(base.clone());
94                self.menatarmsbasetypes_lc.insert(Lowercase::new(base.as_str()));
95            }
96        }
97    }
98}
99
100#[derive(Clone, Debug)]
101pub struct MenAtArmsType {
102    key: Token,
103    block: Block,
104}
105
106impl MenAtArmsType {
107    pub fn new(key: Token, block: Block) -> Self {
108        MenAtArmsType { key, block }
109    }
110
111    pub fn validate(&self, data: &Everything) {
112        let mut vd = Validator::new(&self.block, data);
113
114        vd.req_field("type");
115        vd.field_item("type", Item::MenAtArmsBase);
116        if let Some(base) = self.block.get_field_value("type") {
117            let modif = format!("stationed_{base}_damage_add");
118            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
119            let modif = format!("stationed_{base}_damage_mult");
120            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
121            let modif = format!("stationed_{base}_pursuit_add");
122            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
123            let modif = format!("stationed_{base}_pursuit_mult");
124            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
125            let modif = format!("stationed_{base}_screen_add");
126            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
127            let modif = format!("stationed_{base}_screen_mult");
128            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
129            let modif = format!("stationed_{base}_toughness_add");
130            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
131            let modif = format!("stationed_{base}_toughness_mult");
132            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
133            let modif = format!("stationed_{base}_siege_value_add");
134            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
135            let modif = format!("stationed_{base}_siege_value_mult");
136            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
137
138            let modif = format!("{base}_damage_add");
139            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
140            let modif = format!("{base}_damage_mult");
141            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
142            let modif = format!("{base}_pursuit_add");
143            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
144            let modif = format!("{base}_pursuit_mult");
145            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
146            let modif = format!("{base}_screen_add");
147            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
148            let modif = format!("{base}_screen_mult");
149            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
150            let modif = format!("{base}_toughness_add");
151            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
152            let modif = format!("{base}_toughness_mult");
153            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
154            let modif = format!("{base}_recruitment_cost_mult");
155            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
156            let modif = format!("{base}_siege_value_add");
157            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
158            let modif = format!("{base}_siege_value_mult");
159            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
160            let modif = format!("{base}_maintenance_mult");
161            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
162            let modif = format!("{base}_max_size_add");
163            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
164            let modif = format!("{base}_max_size_mult");
165            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
166        }
167
168        data.verify_exists(Item::Localization, &self.key);
169        let loca = format!("{}_flavor", &self.key);
170        data.verify_exists_implied(Item::Localization, &loca, &self.key);
171
172        vd.multi_field_validated_block("illustration", |block, data| {
173            let mut vd = Validator::new(block, data);
174            vd.field_trigger_rooted("trigger", Tooltipped::No, Scopes::Culture);
175            vd.field_icon("reference", "NGameIcons|REGIMENTYPE_HORIZONTAL_IMAGE_PATH", ".dds");
176            vd.field_icon("reference", "NGameIcons|REGIMENTYPE_VERTICAL_IMAGE_PATH", ".dds");
177        });
178
179        if let Some(icon) = vd.field_value("icon") {
180            data.verify_icon("NGameIcons|REGIMENTYPE_ICON_PATH", icon, ".dds");
181            data.verify_icon("NGameIcons|REGIMENTYPE_HORIZONTAL_IMAGE_PATH", icon, ".dds");
182            data.verify_icon("NGameIcons|REGIMENTYPE_VERTICAL_IMAGE_PATH", icon, ".dds");
183        } else if let Some(base) = self.block.get_field_value("type") {
184            for define in &[
185                "NGameIcons|REGIMENTYPE_ICON_PATH",
186                "NGameIcons|REGIMENTYPE_HORIZONTAL_IMAGE_PATH",
187                "NGameIcons|REGIMENTYPE_VERTICAL_IMAGE_PATH",
188            ] {
189                if let Some(icon_path) = data.get_defined_string_warn(&self.key, define) {
190                    let base_path = format!("{icon_path}/{base}.dds");
191                    let path = format!("{icon_path}/{}.dds", self.key);
192                    data.mark_used(Item::File, &base_path);
193                    if !data.fileset.exists(&base_path) {
194                        data.verify_exists_implied_max_sev(
195                            Item::File,
196                            &path,
197                            &self.key,
198                            Severity::Warning,
199                        );
200                    }
201                }
202            }
203        }
204
205        // TODO: "use this instead of `can_recruit = { always = no }`"
206        vd.field_bool("special_recruit_only");
207        // TODO: "Mutually exclusive with being unlocked by innovation"
208        vd.field_trigger_builder("can_recruit", Tooltipped::Yes, |key| {
209            let mut sc = ScopeContext::new(Scopes::Character, key);
210            sc.define_name("title", Scopes::LandedTitle, key);
211            sc
212        });
213
214        vd.field_trigger_rooted("should_show_when_unavailable", Tooltipped::No, Scopes::Character);
215        vd.field_trigger_rooted("access_through_subject", Tooltipped::No, Scopes::Character);
216
217        vd.field_integer("max");
218        validate_maa_stats(&mut vd);
219        vd.field_integer("siege_tier");
220        vd.field_bool("fights_in_main_phase");
221
222        for field in &["buy_cost", "low_maintenance_cost", "high_maintenance_cost"] {
223            vd.field_validated_key_block(field, |key, block, data| {
224                let mut sc = ScopeContext::new(Scopes::Character, key);
225                validate_cost(block, data, &mut sc);
226            });
227        }
228
229        vd.field_validated_block("terrain_bonus", validate_terrain_bonus);
230        vd.field_validated_block("holding_bonus", validate_holding_bonus);
231        vd.field_validated_block("winter_bonus", validate_winter_bonus);
232        vd.field_validated_block("era_bonus", validate_era_bonus);
233        vd.field_validated_block("counters", validate_counters);
234
235        vd.field_numeric("stack");
236        vd.field_numeric("hired_stack_size");
237        vd.field_integer("max_sub_regiments");
238        vd.field_numeric("provision_cost");
239
240        vd.field_script_value_rooted("ai_quality", Scopes::Character);
241        vd.field_bool("allowed_in_hired_troops");
242        vd.field_bool("fallback_in_hired_troops_if_unlocked");
243        vd.field_bool("mercenary_fallback");
244        vd.field_bool("holy_order_fallback");
245
246        // undocumented
247
248        vd.field_integer("max_regiments");
249    }
250}
251
252pub fn validate_terrain_bonus(block: &Block, data: &Everything) {
253    let mut vd = Validator::new(block, data);
254    vd.unknown_block_fields(|key, block| {
255        data.verify_exists(Item::Terrain, key);
256        let mut vd = Validator::new(block, data);
257        validate_maa_stats(&mut vd);
258    });
259}
260
261pub fn validate_holding_bonus(block: &Block, data: &Everything) {
262    let mut vd = Validator::new(block, data);
263    vd.unknown_block_fields(|key, block| {
264        data.verify_exists(Item::HoldingType, key);
265        let mut vd = Validator::new(block, data);
266        validate_maa_stats(&mut vd);
267    });
268}
269
270pub fn validate_winter_bonus(block: &Block, data: &Everything) {
271    let mut vd = Validator::new(block, data);
272    vd.unknown_block_fields(|key, block| {
273        if !(key.is("harsh_winter") || key.is("normal_winter")) {
274            warn(ErrorKey::Validation).msg("unknown winter type").loc(key).push();
275        }
276        let mut vd = Validator::new(block, data);
277        validate_maa_stats(&mut vd);
278    });
279}
280
281fn validate_era_bonus(block: &Block, data: &Everything) {
282    let mut vd = Validator::new(block, data);
283    vd.unknown_block_fields(|key, block| {
284        data.verify_exists(Item::CultureEra, key);
285        let mut vd = Validator::new(block, data);
286        validate_maa_stats(&mut vd);
287    });
288}
289
290fn validate_counters(block: &Block, data: &Everything) {
291    let mut vd = Validator::new(block, data);
292    for key in &data.menatarmstypes.menatarmsbasetypes {
293        vd.field_numeric(key.as_str());
294    }
295}