1use crate::block::Block;
2use crate::ck3::modif::ModifKinds;
3use crate::context::ScopeContext;
4use crate::db::{Db, DbKind};
5use crate::everything::Everything;
6use crate::game::GameFlags;
7use crate::item::{Item, ItemLoader, LoadAsFile, Recursive};
8use crate::modif::validate_modifs;
9use crate::pdxfile::PdxEncoding;
10use crate::report::{ErrorKey, err, warn};
11use crate::scopes::Scopes;
12use crate::script_value::validate_non_dynamic_script_value;
13use crate::token::Token;
14use crate::tooltipped::Tooltipped;
15use crate::validate::{validate_duration, validate_possibly_named_color};
16use crate::validator::Validator;
17
18#[derive(Clone, Debug)]
19pub struct Situation {}
20
21#[derive(Clone, Debug)]
22pub struct SituationCatalyst {}
23
24#[derive(Clone, Debug)]
25pub struct SituationHistory {}
26
27#[derive(Clone, Debug)]
28pub struct SituationGroupType {}
29
30inventory::submit! {
31 ItemLoader::Normal(GameFlags::Ck3, Item::Situation, Situation::add)
32}
33
34inventory::submit! {
35 ItemLoader::Normal(GameFlags::Ck3, Item::SituationCatalyst, SituationCatalyst::add)
36}
37
38inventory::submit! {
39 ItemLoader::Full(GameFlags::Ck3, Item::SituationHistory, PdxEncoding::Utf8Bom, ".txt", LoadAsFile::Yes, Recursive::No, SituationHistory::add)
40}
41
42inventory::submit! {
43 ItemLoader::Normal(GameFlags::Ck3, Item::SituationGroupType, SituationGroupType::add)
44}
45
46impl Situation {
47 pub fn add(db: &mut Db, key: Token, block: Block) {
48 db.add(Item::Situation, key, block, Box::new(Self {}));
49 }
50}
51
52impl SituationCatalyst {
53 pub fn add(db: &mut Db, key: Token, block: Block) {
54 db.add(Item::SituationCatalyst, key, block, Box::new(Self {}));
55 }
56}
57
58impl SituationHistory {
59 pub fn add(db: &mut Db, file: Token, block: Block) {
60 db.add(Item::SituationHistory, file, block, Box::new(Self {}));
61 }
62}
63
64impl SituationGroupType {
65 pub fn add(db: &mut Db, file: Token, block: Block) {
66 db.add(Item::SituationGroupType, file, block, Box::new(Self {}));
67 }
68}
69
70impl DbKind for Situation {
71 fn add_subitems(&self, _key: &Token, block: &Block, db: &mut Db) {
72 if let Some(block) = block.get_field_block("sub_regions") {
73 for (key, _) in block.iter_definitions() {
74 db.add_flag(Item::SituationSubRegion, key.clone());
75 }
76 }
77 if let Some(block) = block.get_field_block("participant_groups") {
78 for (key, _) in block.iter_definitions() {
79 db.add_flag(Item::SituationParticipantGroup, key.clone());
80 }
81 }
82 if let Some(block) = block.get_field_block("phases") {
83 for (key, block) in block.iter_definitions() {
84 if let Some(block) = block.get_field_block("parameters") {
85 for (key, _) in block.iter_assignments() {
86 db.add_flag(Item::SituationPhaseParameter, key.clone());
87 }
88 }
89 if let Some(block) = block.get_field_block("modifier_sets") {
90 for (_, block) in block.iter_definitions() {
91 for (_, block) in block.iter_definitions() {
92 if let Some(block) = block.get_field_block("parameters") {
93 for (key, _) in block.iter_assignments() {
94 db.add_flag(
95 Item::SituationParticipantGroupParameter,
96 key.clone(),
97 );
98 }
99 }
100 }
101 }
102 }
103 db.add_flag(Item::SituationPhase, key.clone());
104 }
105 }
106 }
107
108 fn validate(&self, key: &Token, block: &Block, data: &Everything) {
109 let mut vd = Validator::new(block, data);
110
111 let loca = format!("situation_{key}");
112 data.verify_exists_implied(Item::Localization, &loca, key);
113 let loca = format!("situation_{key}_desc");
114 data.verify_exists_implied(Item::Localization, &loca, key);
115 let loca = format!("situation_type_{key}");
116 data.verify_exists_implied(Item::Localization, &loca, key);
117 let loca = format!("situation_type_{key}_desc");
118 data.verify_exists_implied(Item::Localization, &loca, key);
119
120 vd.field_choice(
121 "window",
122 &["situation", "the_great_steppe", "silk_road", "dynastic_cycle"],
123 );
124 if let Some(token) = vd.field_value("gui_window_name") {
125 let pathname = format!("gui/{token}.gui");
126 data.verify_exists_implied(Item::File, &pathname, token);
127 }
128 if let Some(token) = vd.field_value("gui_participation_window_name") {
129 let pathname = format!("gui/{token}.gui");
130 data.verify_exists_implied(Item::File, &pathname, token);
131 }
132
133 vd.field_bool("gui_tooltip_group_focused");
134 vd.field_item("illustration", Item::File);
135 vd.multi_field_validated_block("icon", |block, data| {
136 let mut vd = Validator::new(block, data);
137 vd.field_trigger_rooted("trigger", Tooltipped::No, Scopes::Situation);
138 vd.field_item("reference", Item::File);
139 });
140 vd.field_item("situation_group_type", Item::SituationGroupType);
141 vd.field_integer("sort_order");
142
143 vd.field_validated_value("map_mode", |_, mut vvd| {
144 vvd.maybe_is("participant_groups");
145 vvd.maybe_is("sub_regions");
146 vvd.item(Item::MapMode);
147 });
148
149 vd.req_field("sub_regions");
151 vd.field_validated_block("sub_regions", |block, data| {
152 let mut vd = Validator::new(block, data);
153 let mut count = 0;
154 vd.unknown_block_fields(|sub_region_key, block| {
155 count += 1;
156 validate_sub_region(sub_region_key, block, data, key);
157 });
158 if count == 0 {
159 let msg = "situation needs at least one sub-region";
160 err(ErrorKey::FieldMissing).msg(msg).loc(key).push();
161 }
162 });
163
164 vd.req_field("participant_groups");
166 vd.field_validated_block("participant_groups", |block, data| {
167 let mut vd = Validator::new(block, data);
168 let mut count = 0;
169 vd.unknown_block_fields(|pg_key, block| {
170 count += 1;
171 validate_participant_group(pg_key, block, data, key);
172 });
173 if count == 0 {
174 let msg = "situation needs at least one participant group";
175 err(ErrorKey::FieldMissing).msg(msg).loc(key).push();
176 }
177 });
178
179 vd.req_field("phases");
180 vd.field_validated_block("phases", |block, data| {
181 let mut vd = Validator::new(block, data);
182 let mut count = 0;
183 vd.unknown_block_fields(|phase_key, block| {
184 count += 1;
185 validate_phase(phase_key, block, data, key);
186 });
187 if count == 0 {
188 let msg = "situation needs at least one phase";
189 err(ErrorKey::FieldMissing).msg(msg).loc(key).push();
190 }
191 });
192
193 vd.field_effect_rooted("on_start", Tooltipped::No, Scopes::Situation);
194 vd.field_effect_rooted("on_end", Tooltipped::No, Scopes::Situation);
195 vd.field_effect_rooted("on_monthly", Tooltipped::No, Scopes::Situation);
196 vd.field_effect_rooted("on_yearly", Tooltipped::No, Scopes::Situation);
197 vd.field_effect_rooted("on_join", Tooltipped::Yes, Scopes::Situation);
198 vd.field_effect_rooted("on_leave", Tooltipped::Yes, Scopes::Situation);
199
200 vd.field_bool("is_unique");
201 vd.field_bool("keep_full_history");
202 vd.field_bool("migration");
203 vd.field_item("start_phase", Item::SituationPhase);
205 vd.field_bool("use_situation_phase_flat_icons");
206 }
207}
208
209impl DbKind for SituationCatalyst {
210 fn validate(&self, _key: &Token, block: &Block, data: &Everything) {
211 let _vd = Validator::new(block, data);
213 }
214}
215
216impl DbKind for SituationHistory {
217 fn validate(&self, _file: &Token, block: &Block, data: &Everything) {
218 let mut vd = Validator::new(block, data);
219 vd.validate_history_blocks(|_, _, block, data| {
220 let mut vd = Validator::new(block, data);
221 vd.field_effect_rooted("effect", Tooltipped::No, Scopes::None);
222 });
223 }
224}
225
226impl DbKind for SituationGroupType {
227 fn validate(&self, _key: &Token, block: &Block, data: &Everything) {
228 let mut vd = Validator::new(block, data);
229
230 vd.field_integer("sort_order");
231 vd.field_list("gui_tags");
232 }
233}
234
235fn validate_participant_group(key: &Token, block: &Block, data: &Everything, situation: &Token) {
236 fn sc_builder(key: &Token) -> ScopeContext {
237 let mut sc = ScopeContext::new(Scopes::Character, key);
238 sc.define_name("situation", Scopes::Situation, key);
239 sc.define_name("situation_sub_region", Scopes::SituationSubRegion, key);
240 sc
241 }
242 fn sc_with_group(key: &Token) -> ScopeContext {
243 let mut sc = sc_builder(key);
244 sc.define_name("situation_participant_group", Scopes::SituationParticipantGroup, key);
245 sc
246 }
247
248 let mut vd = Validator::new(block, data);
249
250 let loca = format!("{situation}_participant_group_{key}");
251 data.verify_exists_implied(Item::Localization, &loca, key);
252 let loca = format!("{situation}_participant_group_{key}_desc");
253 data.verify_exists_implied(Item::Localization, &loca, key);
254
255 vd.field_item("icon", Item::File);
256 vd.field_bool("auto_add_rulers");
257 vd.field_bool("auto_add_landless_rulers");
258 vd.field_validated("map_color", validate_possibly_named_color);
259 vd.field_bool("require_capital_in_sub_region");
260 vd.field_bool("require_domain_in_sub_region");
261 vd.field_bool("require_realm_in_sub_region");
262 vd.field_bool("require_domicile_in_sub_region");
263
264 vd.field_trigger_builder("is_character_valid", Tooltipped::Yes, sc_builder);
265 vd.field_effect_builder("on_join", Tooltipped::Yes, sc_with_group);
266 vd.field_effect_builder("on_leave", Tooltipped::Yes, sc_with_group);
267}
268
269fn validate_phase(key: &Token, block: &Block, data: &Everything, situation: &Token) {
270 fn sc_builder(key: &Token) -> ScopeContext {
271 let mut sc = ScopeContext::new(Scopes::Situation, key);
272 sc.define_name("situation", Scopes::Situation, key);
273 sc.define_name("situation_sub_region", Scopes::SituationSubRegion, key);
274 sc
275 }
276 fn sc_builder2(key: &Token) -> ScopeContext {
277 let mut sc = ScopeContext::new(Scopes::Situation, key);
278 sc.define_name("situation_sub_region", Scopes::SituationSubRegion, key);
279 sc
280 }
281
282 let mut vd = Validator::new(block, data);
283
284 let loca = format!("{situation}_{key}_situation_phase");
285 data.verify_exists_implied(Item::Localization, &loca, key);
286 vd.field_validated_block("parameters", validate_parameters);
289
290 vd.field_effect_builder("on_start", Tooltipped::No, sc_builder);
291 vd.field_effect_builder("on_end", Tooltipped::No, sc_builder);
292 vd.field_item("illustration", Item::File);
293 vd.field_item("icon", Item::File);
294 vd.field_item("map_province_effect", Item::ProvinceEffect);
295 vd.field_numeric_range("map_province_effect_intensity", 0.0..=1.0);
296 vd.field_validated_block_sc("max_duration", &mut sc_builder2(key), validate_duration);
297 vd.field_choice(
298 "max_duration_next_phase",
299 &[
300 "highest_points",
301 "weighted_random_points",
302 "random_non_takeover",
303 "weighted_non_takeover",
304 ],
305 );
306
307 vd.field_validated_block("future_phases", |block, data| {
308 let mut vd = Validator::new(block, data);
309
310 vd.validate_item_key_blocks(Item::SituationPhase, |_, block, data| {
311 let mut vd = Validator::new(block, data);
312
313 vd.field_choice("takeover_type", &["none", "points", "duration"]);
314 vd.field_script_value_no_breakdown_builder("takeover_points", sc_builder2);
315 vd.field_script_value_no_breakdown_builder("weight", sc_builder2);
316 vd.field_validated_block_sc(
317 "takeover_duration",
318 &mut sc_builder2(key),
319 validate_duration,
320 );
321 vd.field_validated_block("catalysts", |block, data| {
322 let mut vd = Validator::new(block, data);
323
324 vd.unknown_fields(|key, bv| {
325 data.verify_exists(Item::SituationCatalyst, key);
326 validate_non_dynamic_script_value(bv, data);
327 });
328 });
329 });
330 });
331
332 vd.advice_field("modifier_named_sets", "docs say modifier_named_sets but it's modifier_sets");
333 vd.field_validated_block("modifier_sets", |block, data| {
334 let mut vd = Validator::new(block, data);
335
336 vd.unknown_block_fields(|key, block| {
337 let mut vd = Validator::new(block, data);
338 data.verify_exists(Item::Localization, key);
339
340 vd.field_item("icon", Item::File);
341 vd.field_validated_block("all", validate_modifier_set);
342 vd.validate_item_key_blocks(Item::SituationParticipantGroup, |_, block, data| {
344 validate_modifier_set(block, data);
345 });
346 });
347 });
348}
349
350fn validate_sub_region(key: &Token, block: &Block, data: &Everything, situation: &Token) {
351 let mut vd = Validator::new(block, data);
352
353 let loca = format!("{situation}_sub_region_{key}");
354 data.verify_exists_implied(Item::Localization, &loca, key);
355
356 vd.field_item("illustration", Item::File);
357 vd.field_item("icon", Item::File);
358 vd.field_validated("map_color", validate_possibly_named_color);
359 vd.field_list_items("geographical_regions", Item::Region);
360
361 vd.field_item("capital_province", Item::Province);
364}
365
366fn validate_modifier_set(block: &Block, data: &Everything) {
367 let mut vd = Validator::new(block, data);
368
369 vd.field_validated_block("county_modifier", |block, data| {
370 let vd = Validator::new(block, data);
371 validate_modifs(block, data, ModifKinds::County, vd);
372 });
373 vd.field_validated_block("character_modifier", |block, data| {
374 let vd = Validator::new(block, data);
375 validate_modifs(block, data, ModifKinds::Character, vd);
376 });
377 vd.multi_field_validated_block("doctrine_character_modifier", |block, data| {
378 let mut vd = Validator::new(block, data);
379 vd.field_item("name", Item::Localization);
380 vd.field_item("doctrine", Item::Doctrine);
381 validate_modifs(block, data, ModifKinds::Character, vd);
382 });
383 vd.field_validated_block("parameters", validate_parameters);
384}
385
386fn validate_parameters(block: &Block, data: &Everything) {
387 let mut vd = Validator::new(block, data);
388 vd.unknown_value_fields(|key, value| {
389 let loca = format!("situation_parameter_{key}");
390 data.verify_exists_implied(Item::Localization, &loca, key);
391 if !value.is("yes") {
394 let msg = "only `yes` makes sense here";
395 warn(ErrorKey::Validation).msg(msg).loc(value).push();
396 }
397 });
398}