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 vd.field_bool("special_recruit_only");
207 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 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}