1use crate::block::{BV, Block};
2use crate::context::{ScopeContext, Temporary};
3use crate::desc::validate_desc;
4use crate::everything::Everything;
5use crate::helpers::TigerHashSet;
6use crate::item::Item;
7use crate::report::{ErrorKey, ErrorLoc, err, warn};
8use crate::scopes::Scopes;
9use crate::script_value::validate_script_value;
10use crate::token::Token;
11use crate::tooltipped::Tooltipped;
12use crate::trigger::validate_target;
13use crate::validate::{validate_color, validate_optional_duration, validate_possibly_named_color};
14use crate::validator::{Validator, ValueValidator};
15use crate::vic3::data::buildings::BuildingType;
16use crate::vic3::tables::misc::{LOBBY_FORMATION_REASON, STATE_TYPES, STRATA, TARIFF_LEVELS};
17use crate::vic3::validate::validate_treaty_article;
18
19pub fn validate_activate_production_method(
20 _key: &Token,
21 _block: &Block,
22 _data: &Everything,
23 _sc: &mut ScopeContext,
24 mut vd: Validator,
25 _tooltipped: Tooltipped,
26) {
27 vd.req_field("building_type");
28 vd.req_field("production_method");
29 vd.field_item("building_type", Item::BuildingType);
30 vd.field_item("production_method", Item::ProductionMethod);
32}
33
34pub fn validate_add_culture_modifier(
35 _key: &Token,
36 _block: &Block,
37 _data: &Everything,
38 sc: &mut ScopeContext,
39 mut vd: Validator,
40 _tooltipped: Tooltipped,
41) {
42 vd.req_field("culture");
43 vd.field_target("culture", sc, Scopes::Culture);
44 validate_optional_duration(&mut vd, sc);
45 vd.field_script_value("multiplier", sc); }
47
48pub fn validate_add_religion_sol_modifier(
49 _key: &Token,
50 _block: &Block,
51 _data: &Everything,
52 sc: &mut ScopeContext,
53 mut vd: Validator,
54 _tooltipped: Tooltipped,
55) {
56 vd.req_field("religion");
57 vd.field_target("religion", sc, Scopes::Religion);
58 validate_optional_duration(&mut vd, sc);
59 vd.field_script_value("multiplier", sc); }
61
62pub fn validate_add_enactment_modifier(
63 _key: &Token,
64 _block: &Block,
65 data: &Everything,
66 sc: &mut ScopeContext,
67 mut vd: Validator,
68 _tooltipped: Tooltipped,
69) {
70 vd.req_field("name");
71 vd.field_validated_value("name", |_key, mut vd| {
72 vd.item(Item::Modifier);
73 let value = vd.value();
74 data.validate_call(Item::Modifier, value, &Block::new(value.loc), sc);
75 });
76 vd.field_script_value_builder("multiplier", |key| sc.get_multiplier_context(key));
78}
79
80pub fn validate_add_modifier(
81 _key: &Token,
82 bv: &BV,
83 data: &Everything,
84 sc: &mut ScopeContext,
85 _tooltipped: Tooltipped,
86) {
87 match bv {
88 BV::Value(value) => {
89 data.verify_exists(Item::Modifier, value);
90 data.validate_call(Item::Modifier, value, &Block::new(value.loc), sc);
91 }
92 BV::Block(block) => {
93 let mut vd = Validator::new(block, data);
94 vd.set_case_sensitive(false);
95 vd.req_field("name");
96 vd.field_validated_value("name", |_key, mut vd| {
97 vd.item(Item::Modifier);
98 let value = vd.value();
99 data.validate_call(Item::Modifier, value, &Block::new(value.loc), sc);
100 });
101 vd.field_script_value_builder("multiplier", |key| sc.get_multiplier_context(key));
103 validate_optional_duration(&mut vd, sc);
104 vd.field_bool("is_decaying");
105 }
106 }
107}
108
109pub fn validate_add_journalentry(
110 _key: &Token,
111 _block: &Block,
112 _data: &Everything,
113 sc: &mut ScopeContext,
114 mut vd: Validator,
115 _tooltipped: Tooltipped,
116) {
117 vd.req_field("type");
118 vd.field_item("type", Item::JournalEntry);
119 vd.field_item("objective_subgoal", Item::ObjectiveSubgoal); vd.field_target("target", sc, Scopes::all());
121}
122
123pub fn validate_add_loyalists(
124 _key: &Token,
125 _block: &Block,
126 _data: &Everything,
127 sc: &mut ScopeContext,
128 mut vd: Validator,
129 _tooltipped: Tooltipped,
130) {
131 vd.req_field("value");
132 vd.field_script_value("value", sc);
133 vd.field_item_or_target("interest_group", sc, Item::InterestGroup, Scopes::InterestGroup);
134 vd.field_item_or_target("pop_type", sc, Item::PopType, Scopes::PopType);
135 vd.field_choice("strata", STRATA);
136 vd.field_item_or_target("culture", sc, Item::Culture, Scopes::Culture);
137 vd.field_item_or_target("religion", sc, Item::Religion, Scopes::Religion);
138}
139
140pub fn validate_add_technology_progress(
141 _key: &Token,
142 _block: &Block,
143 _data: &Everything,
144 _sc: &mut ScopeContext,
145 mut vd: Validator,
146 _tooltipped: Tooltipped,
147) {
148 vd.req_field("progress");
149 vd.field_numeric("progress");
150 vd.req_field("technology");
151 vd.field_item("technology", Item::Technology);
152}
153
154pub fn validate_add_war_goal(
155 _key: &Token,
156 block: &Block,
157 _data: &Everything,
158 sc: &mut ScopeContext,
159 mut vd: Validator,
160 _tooltipped: Tooltipped,
161) {
162 vd.req_field("holder");
163 vd.field_item_or_target("holder", sc, Item::Country, Scopes::Country);
164 vd.req_field("type");
165 vd.field_item("type", Item::WarGoalType);
166 vd.field_target("state", sc, Scopes::State);
167 vd.advice_field("country", "docs say `country` but it's `target_country`");
169 vd.field_target("target_country", sc, Scopes::Country);
170 vd.field_target("target_state", sc, Scopes::State);
171 vd.field_target("region", sc, Scopes::StateRegion);
172 vd.field_bool("primary_demand");
173 if let Some(goal_type) = block.get_field_value("type")
174 && goal_type.is("enforce_treaty_article")
175 {
176 vd.multi_field_validated_block_sc("article", sc, validate_treaty_article);
177 }
178}
179
180pub fn validate_remove_war_goal(
181 _key: &Token,
182 _block: &Block,
183 _data: &Everything,
184 sc: &mut ScopeContext,
185 mut vd: Validator,
186 _tooltipped: Tooltipped,
187) {
188 vd.req_field("who");
189 vd.field_item_or_target("who", sc, Item::Country, Scopes::Country);
190 vd.req_field("type");
191 vd.field_item("type", Item::WarGoalType);
192}
193
194pub fn validate_addremove_backers(
195 _key: &Token,
196 _block: &Block,
197 data: &Everything,
198 sc: &mut ScopeContext,
199 mut vd: Validator,
200 _tooltipped: Tooltipped,
201) {
202 for value in vd.values() {
203 if !data.item_exists(Item::Country, value.as_str()) {
204 validate_target(value, data, sc, Scopes::Country);
205 }
206 }
207}
208
209pub fn validate_call_election(
210 _key: &Token,
211 _block: &Block,
212 _data: &Everything,
213 sc: &mut ScopeContext,
214 mut vd: Validator,
215 _tooltipped: Tooltipped,
216) {
217 vd.req_field("months");
218 vd.field_script_value("months", sc);
219}
220
221pub fn validate_change_institution_investment_level(
222 _key: &Token,
223 _block: &Block,
224 _data: &Everything,
225 _sc: &mut ScopeContext,
226 mut vd: Validator,
227 _tooltipped: Tooltipped,
228) {
229 vd.req_field("institution");
230 vd.field_item("institution", Item::Institution);
231 vd.req_field("investment");
232 vd.field_integer("investment");
233}
234
235pub fn validate_set_institution_investment_level(
236 _key: &Token,
237 _block: &Block,
238 _data: &Everything,
239 _sc: &mut ScopeContext,
240 mut vd: Validator,
241 _tooltipped: Tooltipped,
242) {
243 vd.req_field("institution");
244 vd.field_item("institution", Item::Institution);
245 vd.req_field("level");
246 vd.field_integer("level");
247}
248
249pub fn validate_diplomatic_pact(
250 _key: &Token,
251 _block: &Block,
252 _data: &Everything,
253 sc: &mut ScopeContext,
254 mut vd: Validator,
255 _tooltipped: Tooltipped,
256) {
257 vd.req_field("country");
258 vd.req_field("type");
259 vd.advice_field("tcountry", "documentation says tcountry but it's just country");
260 vd.field_item_or_target("country", sc, Item::Country, Scopes::Country);
261 vd.field_item_or_target("first_state", sc, Item::StateRegion, Scopes::State);
262 vd.field_item_or_target("second_state", sc, Item::StateRegion, Scopes::State);
263 vd.field_item("type", Item::DiplomaticAction);
264}
265
266pub fn validate_country_value(
267 _key: &Token,
268 _block: &Block,
269 _data: &Everything,
270 sc: &mut ScopeContext,
271 mut vd: Validator,
272 _tooltipped: Tooltipped,
273) {
274 vd.req_field("country");
275 vd.advice_field("tcountry", "documentation says tcountry but it's just country");
276 vd.req_field("value");
277 vd.field_item_or_target("country", sc, Item::Country, Scopes::Country);
278 vd.field_script_value("value", sc);
279}
280
281pub fn validate_create_building(
282 _key: &Token,
283 block: &Block,
284 _data: &Everything,
285 sc: &mut ScopeContext,
286 mut vd: Validator,
287 _tooltipped: Tooltipped,
288) {
289 vd.req_field("building");
290 vd.field_item("building", Item::BuildingType);
291 let building = block.get_field_value("building");
292 vd.field_validated_list("activate_production_methods", |token, data| {
293 data.verify_exists(Item::ProductionMethod, token);
294 if let Some(building) = building
295 && let Some((_, block, building_item)) =
296 data.get_item::<BuildingType>(Item::BuildingType, building.as_str())
297 {
298 building_item.validate_production_method(token, building, block, data);
299 }
300 });
301 vd.field_bool("subsidized");
302 vd.field_numeric_range("reserves", 0.0..=1.0);
303 vd.field_validated_sc("level", sc, |bv, data, sc| {
304 if let Some(token) = bv.get_value()
305 && token.is("arable_land")
306 {
307 return;
308 }
309 validate_script_value(bv, data, sc);
310 });
311 vd.field_validated_block("add_ownership", |block, data| {
312 let mut vd = Validator::new(block, data);
313 vd.multi_field_validated_block("country", |block, data| {
314 let mut vd = Validator::new(block, data);
315 vd.req_field("country");
316 vd.field_target("country", sc, Scopes::Country);
317 vd.req_field("levels");
318 vd.field_integer("levels");
319 });
320 vd.multi_field_validated_block("building", |block, data| {
322 let mut vd = Validator::new(block, data);
323 vd.req_field("country");
324 vd.field_target("country", sc, Scopes::Country);
325 vd.req_field("levels");
326 vd.field_integer("levels");
327 vd.req_field("type");
328 vd.field_item("type", Item::BuildingType);
329 vd.req_field("region");
330 vd.field_item("region", Item::StateRegion);
331 });
332 vd.multi_field_validated_block("company", |block, data| {
334 let mut vd = Validator::new(block, data);
335 vd.req_field("country");
336 vd.field_target("country", sc, Scopes::Country);
337 vd.req_field("type");
338 vd.field_item("type", Item::CompanyType);
339 vd.req_field("levels");
340 vd.field_integer("levels");
341 });
342 });
343}
344
345pub fn validate_create_ship(
346 _key: &Token,
347 _block: &Block,
348 _data: &Everything,
349 sc: &mut ScopeContext,
350 mut vd: Validator,
351 _tooltipped: Tooltipped,
352) {
353 vd.req_field("type");
354 vd.field_item("type", Item::ShipType);
355 vd.field_target("fleet", sc, Scopes::MilitaryFormation);
356 vd.field_item("name", Item::Localization);
357 if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
358 sc.define_name_token(name.as_str(), Scopes::Ship, name, Temporary::No);
359 }
360}
361
362pub fn validate_create_character(
363 key: &Token,
364 block: &Block,
365 _data: &Everything,
366 sc: &mut ScopeContext,
367 mut vd: Validator,
368 _tooltipped: Tooltipped,
369) {
370 vd.field_localization("name", sc);
371 vd.field_localization("first_name", sc);
372 vd.field_localization("last_name", sc);
373 if block.has_key("name") {
374 vd.ban_field("first_name", || "characters without `name`");
375 vd.ban_field("last_name", || "characters without `name`");
376 } else if block.has_key("first_name") {
377 if !block.has_key("last_name") {
378 let msg = "character has `first_name` but no `last_name`";
379 warn(ErrorKey::Validation).msg(msg).loc(key).push();
380 }
381 } else if block.has_key("last_name") {
382 let msg = "character has `last_name` but no `first_name`";
383 warn(ErrorKey::Validation).msg(msg).loc(key).push();
384 }
385 vd.field_validated_value("culture", |_, mut vd| {
386 vd.maybe_is("primary_culture");
387 vd.item_or_target(sc, Item::Culture, Scopes::Culture);
388 });
389 vd.field_item_or_target("religion", sc, Item::Religion, Scopes::Religion);
390 vd.field_validated_value("female", |_, mut vd| {
391 vd.maybe_bool();
392 vd.target(sc, Scopes::Character);
393 });
394 vd.field_validated_value("noble", |_, mut vd| {
395 vd.maybe_bool();
396 vd.target(sc, Scopes::Character);
397 });
398 vd.field_bool("ruler");
399 vd.field_bool("heir");
400 vd.field_bool("historical");
401 vd.field_validated("age", |bv, data| {
402 match bv {
403 BV::Value(value) => {
404 let mut vd = ValueValidator::new(value, data);
406 vd.maybe_is("default");
407 vd.maybe_integer();
408 vd.target(sc, Scopes::Character);
409 }
410 BV::Block(block) => {
411 let mut vd = Validator::new(block, data);
413 vd.req_tokens_integers_exactly(2);
414 }
415 }
416 });
417 vd.field_item_or_target("ideology", sc, Item::Ideology, Scopes::Ideology);
418 vd.field_item_or_target("interest_group", sc, Item::InterestGroup, Scopes::InterestGroup);
419 vd.field_item("template", Item::CharacterTemplate);
420 vd.field_effect_rooted("on_created", Tooltipped::No, Scopes::Character);
421 if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
422 sc.define_name_token(name.as_str(), Scopes::Character, name, Temporary::No);
423 }
424 vd.field_effect_rooted("trait_generation", Tooltipped::No, Scopes::Character);
425 vd.field_item_or_target("hq", sc, Item::StrategicRegion, Scopes::Hq | Scopes::StrategicRegion);
427
428 vd.field_date("birth_date");
432 vd.field_list_items("traits", Item::CharacterTrait);
433 vd.field_item("dna", Item::Dna);
434 vd.field_bool("is_general");
435 vd.field_bool("is_admiral");
436 vd.field_bool("is_agitator");
437 vd.field_bool("ig_leader");
438 vd.field_item("commander_rank", Item::CommanderRank);
439}
440
441pub fn validate_create_country(
442 _key: &Token,
443 _block: &Block,
444 _data: &Everything,
445 sc: &mut ScopeContext,
446 mut vd: Validator,
447 _tooltipped: Tooltipped,
448) {
449 vd.field_item("tag", Item::Country);
450 vd.field_target_ok_this("origin", sc, Scopes::Country);
451 vd.multi_field_target("state", sc, Scopes::State);
452 vd.multi_field_target("province", sc, Scopes::Province);
453 vd.field_effect_rooted("on_created", Tooltipped::No, Scopes::Country);
454}
455
456pub fn validate_create_dynamic_country(
457 _key: &Token,
458 block: &Block,
459 _data: &Everything,
460 sc: &mut ScopeContext,
461 mut vd: Validator,
462 _tooltipped: Tooltipped,
463) {
464 vd.field_target_ok_this("origin", sc, Scopes::Country);
465 if !block.has_key("origin") {
466 vd.req_field("country_type");
467 vd.req_field("tier");
468 vd.req_field("culture");
469 vd.req_field("religion");
470 vd.req_field("capital");
471 vd.req_field("color");
472 vd.req_field("primary_unit_color");
473 vd.req_field("secondary_unit_color");
474 vd.req_field("tertiary_unit_color");
475 }
476 vd.field_item("country_type", Item::CountryType);
477 vd.field_item("tier", Item::CountryTier);
478 vd.multi_field_target("culture", sc, Scopes::Culture);
479 vd.field_target("religion", sc, Scopes::Religion);
480 vd.field_target("capital", sc, Scopes::State);
481 vd.field_item("social_hierarchy", Item::SocialHierarchy);
482 vd.field_trigger_rooted("cede_state_trigger", Tooltipped::No, Scopes::State);
483 vd.field_validated("color", validate_possibly_named_color);
484 vd.field_validated("primary_unit_color", validate_possibly_named_color);
485 vd.field_validated("secondary_unit_color", validate_possibly_named_color);
486 vd.field_validated("tertiary_unit_color", validate_possibly_named_color);
487 vd.field_effect_rooted("on_created", Tooltipped::No, Scopes::Country);
488}
489
490pub fn validate_create_diplomatic_play(
491 _key: &Token,
492 _block: &Block,
493 _data: &Everything,
494 sc: &mut ScopeContext,
495 mut vd: Validator,
496 _tooltipped: Tooltipped,
497) {
498 vd.field_localization("name", sc);
499 vd.field_integer_range("escalation", 0..=100);
500 vd.field_bool("war");
501 vd.field_item_or_target_ok_this("initiator", sc, Item::Country, Scopes::Country);
502 vd.field_item("type", Item::DiplomaticPlay);
503 vd.advice_field(
504 "handle_annexation_as_civil_war",
505 "docs say `handle_annexation_as_civil_war` but it's `annex_as_civil_war`",
506 );
507 vd.field_bool("annex_as_civil_war");
508 for field in &["add_initiator_backers", "add_target_backers"] {
509 vd.field_validated_list(field, |token, data| {
510 let mut vd = ValueValidator::new(token, data);
511 vd.maybe_item(Item::Country);
512 vd.target(sc, Scopes::Country);
513 });
514 }
515 vd.multi_field_validated_block_sc("add_war_goal", sc, validate_war_goal);
516
517 vd.field_target("target_state", sc, Scopes::State);
520 vd.field_target("target_country", sc, Scopes::Country);
521 vd.field_target("target_region", sc, Scopes::StrategicRegion);
522}
523
524fn validate_war_goal(block: &Block, data: &Everything, sc: &mut ScopeContext) {
525 let mut vd = Validator::new(block, data);
526 vd.set_case_sensitive(false);
527 vd.field_item_or_target_ok_this("holder", sc, Item::Country, Scopes::Country);
528 vd.field_item("type", Item::WarGoalType);
529 vd.advice_field("state", "docs say `state` but it's `target_state`");
530 vd.field_target("target_state", sc, Scopes::State);
531 vd.advice_field("country", "docs say `country` but it's `target_country`");
532 vd.field_target("target_country", sc, Scopes::Country);
533 vd.advice_field("region", "docs say `region` but it's `target_region`");
534 vd.field_target("target_region", sc, Scopes::StrategicRegion);
535 vd.field_bool("primary_demand");
536 if let Some(goal_type) = block.get_field_value("type")
537 && goal_type.is("enforce_treaty_article")
538 {
539 vd.multi_field_validated_block_sc("article", sc, validate_treaty_article);
540 }
541}
542
543pub fn validate_create_mass_migration(
544 _key: &Token,
545 _block: &Block,
546 _data: &Everything,
547 sc: &mut ScopeContext,
548 mut vd: Validator,
549 _tooltipped: Tooltipped,
550) {
551 vd.req_field("origin");
552 vd.field_target("origin", sc, Scopes::Country);
553 vd.req_field("culture");
554 vd.field_target("culture", sc, Scopes::Culture);
555}
556
557pub fn validate_create_military_formation(
558 _key: &Token,
559 block: &Block,
560 _data: &Everything,
561 sc: &mut ScopeContext,
562 mut vd: Validator,
563 _tooltipped: Tooltipped,
564) {
565 vd.field_localization("name", sc);
566 vd.field_choice("type", &["army", "fleet"]);
567 let is_fleet = block.field_value_is("type", "fleet");
568 vd.field_target("hq_region", sc, Scopes::StrategicRegion);
569 vd.multi_field_validated_block("combat_unit", |block, data| {
570 let mut vd = Validator::new(block, data);
571 vd.field_target("type", sc, Scopes::CombatUnitType);
572 vd.field_choice("service_type", &["regular", "conscript"]);
573 if let Some(token) = vd.field_value("service_type")
574 && is_fleet
575 && token.is("conscript")
576 {
577 let msg = "conscript is not applicable to fleets";
578 err(ErrorKey::Choice).msg(msg).loc(token).push();
579 }
580 vd.field_target("state_region", sc, Scopes::StateRegion);
581 vd.field_integer("count");
582 });
583 if is_fleet {
584 vd.ban_field("mobilization_options", || "armies");
585 }
586 vd.field_validated_list("mobilization_options", |token, data| {
587 let mut vd = ValueValidator::new(token, data);
588 vd.target(sc, Scopes::MobilizationOption);
589 });
590
591 if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
594 sc.define_name_token(name.as_str(), Scopes::MilitaryFormation, name, Temporary::No);
595 }
596}
597
598pub fn validate_create_pop(
599 _key: &Token,
600 block: &Block,
601 _data: &Everything,
602 _sc: &mut ScopeContext,
603 mut vd: Validator,
604 _tooltipped: Tooltipped,
605) {
606 #[allow(clippy::integer_division)]
609 fn sum_fractions_warner<E: ErrorLoc>(sum_fractions: i64, loc: E) {
610 if sum_fractions != 100_000 {
611 let msg = format!(
612 "fractions should add to exactly 1, currently {}.{:05}",
613 sum_fractions / 100_000,
614 sum_fractions % 100_000,
615 );
616 warn(ErrorKey::Validation).msg(msg.trim_end_matches('0')).loc(loc).push();
617 }
618 }
619
620 vd.field_item("pop_type", Item::PopType);
621 vd.field_integer("size");
622
623 let mut available_cultures = TigerHashSet::default();
624
625 if let Some(token) = vd.field_value("culture") {
626 available_cultures.insert(token.clone());
627
628 vd.ban_field("cultures", || "pops with several cultures");
629 vd.field_item("culture", Item::Culture);
630 } else {
631 vd.field_validated_block("cultures", |block, data| {
632 let mut vd = Validator::new(block, data);
633 let mut sum_fractions = 0_i64;
634
635 vd.validate_item_key_values(Item::Culture, |key, mut vd| {
636 available_cultures.insert(key.clone());
637 vd.numeric_range(0.0..=1.0);
638
639 sum_fractions += vd.value().get_fixed_number().unwrap_or(0);
640 });
641
642 sum_fractions_warner(sum_fractions, block);
643 });
644 }
645
646 if block.has_key("religion") {
647 vd.ban_field("split_religion", || "pops without a `religion` field");
648 vd.field_item("religion", Item::Religion);
649 } else if block.has_key("split_religion") {
650 let mut used_cultures = TigerHashSet::default();
651
652 vd.multi_field_validated_block("split_religion", |block, data| {
653 let mut vd = Validator::new(block, data);
654 let mut only_one_culture = false;
655
656 vd.validate_item_key_blocks(Item::Culture, |key, block, data| {
657 if only_one_culture {
658 let msg = "split_religion should contain only one culture block";
659 err(ErrorKey::DuplicateItem).msg(msg).loc(key).push();
660 }
661 only_one_culture = true;
662
663 if !available_cultures.contains(key.as_str()) {
664 let msg = "culture being split does not appear in pop";
665 err(ErrorKey::FieldMissing).msg(msg).loc(key).push();
666 }
667
668 match used_cultures.get(key) {
669 Some(duplicate) => {
670 let msg =
671 format!("trying to split religion of culture {key} multiple times");
672 let msg_other = "first split here";
673 err(ErrorKey::DuplicateField)
674 .msg(msg)
675 .loc(key)
676 .loc_msg(duplicate, msg_other)
677 .push();
678 }
679 None => {
680 used_cultures.insert(key.clone());
681 }
682 }
683
684 let mut vd = Validator::new(block, data);
685 let mut sum_fractions = 0_i64;
686
687 vd.validate_item_key_values(Item::Religion, |_, mut vd| {
688 vd.numeric_range(0.0..=1.0);
689
690 sum_fractions += vd.value().get_fixed_number().unwrap_or(0);
691 });
692
693 sum_fractions_warner(sum_fractions, block);
694 });
695
696 if !only_one_culture {
697 let msg = "split_religion must contain one culture block";
698 err(ErrorKey::DuplicateItem).msg(msg).loc(block).push();
699 }
700 });
701 }
702}
703
704pub fn validate_create_state(
705 _key: &Token,
706 _block: &Block,
707 _data: &Everything,
708 sc: &mut ScopeContext,
709 mut vd: Validator,
710 _tooltipped: Tooltipped,
711) {
712 vd.field_target("country", sc, Scopes::Country);
715 vd.field_list_items("owned_provinces", Item::Province);
716 vd.field_choice("state_type", STATE_TYPES);
717}
718
719pub fn validate_form_government(
720 _key: &Token,
721 _block: &Block,
722 _data: &Everything,
723 sc: &mut ScopeContext,
724 mut vd: Validator,
725 _tooltipped: Tooltipped,
726) {
727 vd.field_script_value("value", sc);
728 vd.multi_field_item("interest_group_type", Item::InterestGroup);
729}
730
731pub fn validate_set_secret_goal(
732 _key: &Token,
733 _block: &Block,
734 _data: &Everything,
735 sc: &mut ScopeContext,
736 mut vd: Validator,
737 _tooltipped: Tooltipped,
738) {
739 vd.req_field("country");
740 vd.advice_field("tcountry", "documentation says tcountry but it's just country");
741 vd.req_field("secret_goal");
742 vd.field_item_or_target("country", sc, Item::Country, Scopes::Country);
743 vd.field_item("secret_goal", Item::SecretGoal);
744}
745
746pub fn validate_post_notification(
747 _key: &Token,
748 mut vd: ValueValidator,
749 sc: &mut ScopeContext,
750 _tooltipped: Tooltipped,
751) {
752 vd.item(Item::Message);
753 vd.implied_localization_sc("notification_", "_name", sc);
754 vd.implied_localization_sc("notification_", "_desc", sc);
755 vd.implied_localization_sc("notification_", "_tooltip", sc);
756}
757
758pub fn validate_progress(
759 _key: &Token,
760 _block: &Block,
761 _data: &Everything,
762 sc: &mut ScopeContext,
763 mut vd: Validator,
764 _tooltipped: Tooltipped,
765) {
766 vd.req_field("value");
767 vd.req_field("name");
768 vd.field_script_value("value", sc);
769 vd.field_item("name", Item::ScriptedProgressBar);
770}
771
772pub fn validate_join_war(
773 _key: &Token,
774 _block: &Block,
775 _data: &Everything,
776 sc: &mut ScopeContext,
777 mut vd: Validator,
778 _tooltipped: Tooltipped,
779) {
780 vd.req_field("target");
781 vd.req_field("side");
782 vd.field_target("target", sc, Scopes::Country);
783 vd.field_target("side", sc, Scopes::Country);
784}
785
786pub fn validate_create_truce(
787 _key: &Token,
788 _block: &Block,
789 _data: &Everything,
790 sc: &mut ScopeContext,
791 mut vd: Validator,
792 _tooltipped: Tooltipped,
793) {
794 vd.req_field("country");
795 vd.req_field("months");
796 vd.advice_field("tcountry", "documentation says tcountry but it's just country");
797 vd.field_target("country", sc, Scopes::Country);
798 vd.field_integer("months");
800}
801
802pub fn validate_create_power_bloc(
803 _key: &Token,
804 _block: &Block,
805 _data: &Everything,
806 sc: &mut ScopeContext,
807 mut vd: Validator,
808 _tooltipped: Tooltipped,
809) {
810 vd.req_field("name");
811 vd.req_field("map_color");
812 vd.req_field("identity");
813 vd.field_validated_sc("name", sc, validate_desc);
815 vd.field_validated_block("map_color", validate_color);
817 vd.field_item("identity", Item::PowerBlocIdentity);
818 vd.multi_field_item("principle", Item::Principle);
819 vd.multi_field_target("member", sc, Scopes::Country);
820
821 vd.field_date("founding_date");
824}
825
826pub fn validate_create_lobby(
827 _key: &Token,
828 _block: &Block,
829 _data: &Everything,
830 sc: &mut ScopeContext,
831 mut vd: Validator,
832 _tooltipped: Tooltipped,
833) {
834 vd.req_field("type");
835 vd.req_field("target");
836 vd.field_item("type", Item::PoliticalLobby);
837 vd.field_target("target", sc, Scopes::Country);
838 vd.multi_field_target("add_interest_group", sc, Scopes::InterestGroup);
839 vd.field_choice("lobby_formation_reason", LOBBY_FORMATION_REASON);
841}
842
843pub fn validate_create_movement(
844 _key: &Token,
845 _block: &Block,
846 _data: &Everything,
847 sc: &mut ScopeContext,
848 mut vd: Validator,
849 _tooltipped: Tooltipped,
850) {
851 vd.req_field("type");
852 vd.field_item("type", Item::PoliticalMovement);
853 vd.advice_field("movement_type", "docs say movement_type but it's just type");
854 vd.field_target("religion", sc, Scopes::Religion);
855 vd.field_target("culture", sc, Scopes::Culture);
856}
857
858pub fn validate_create_catalyst(
859 _key: &Token,
860 _block: &Block,
861 _data: &Everything,
862 sc: &mut ScopeContext,
863 mut vd: Validator,
864 _tooltipped: Tooltipped,
865) {
866 vd.req_field("type");
867 vd.req_field("target");
868 vd.field_item("type", Item::DiplomaticCatalyst);
869 vd.field_target("target", sc, Scopes::Country);
870}
871
872pub fn validate_change_appeasement(
873 _key: &Token,
874 _block: &Block,
875 _data: &Everything,
876 sc: &mut ScopeContext,
877 mut vd: Validator,
878 _tooltipped: Tooltipped,
879) {
880 vd.req_field("amount");
881 vd.req_field("factor");
882 vd.field_item("factor", Item::PoliticalLobbyAppeasement);
883 vd.field_script_value("amount", sc);
884}
885
886pub fn validate_pop_wealth(
888 _key: &Token,
889 _block: &Block,
890 _data: &Everything,
891 sc: &mut ScopeContext,
892 mut vd: Validator,
893 _tooltipped: Tooltipped,
894) {
895 vd.req_field("wealth_distribution");
896 vd.field_script_value("wealth_distribution", sc);
897 vd.field_bool("update_loyalties");
898}
899
900pub fn validate_kill_character(
901 _key: &Token,
902 bv: &BV,
903 data: &Everything,
904 _sc: &mut ScopeContext,
905 _tooltipped: Tooltipped,
906) {
907 match bv {
908 BV::Value(value) => {
909 let mut vd = ValueValidator::new(value, data);
911 vd.bool();
912 }
913 BV::Block(block) => {
914 let mut vd = Validator::new(block, data);
916 vd.set_case_sensitive(false);
917 vd.field_bool("value");
918 vd.field_bool("hidden");
919 }
920 }
921}
922
923pub fn validate_kill_population(
926 key: &Token,
927 _block: &Block,
928 _data: &Everything,
929 sc: &mut ScopeContext,
930 mut vd: Validator,
931 _tooltipped: Tooltipped,
932) {
933 let percent = key.is("kill_population_percent") || key.is("kill_population_percent_in_state");
934 if percent {
935 vd.field_numeric_range("percent", 0.0..=1.0);
936 } else {
937 vd.field_integer("value");
938 }
939 vd.field_target("culture", sc, Scopes::Culture);
940 vd.field_target("religion", sc, Scopes::Religion);
941 vd.field_target("interest_group", sc, Scopes::InterestGroup);
942 vd.field_item("pop_type", Item::PopType);
943 vd.field_choice("strata", STRATA);
944}
945
946pub fn validate_pop_literacy(
947 _key: &Token,
948 _block: &Block,
949 _data: &Everything,
950 sc: &mut ScopeContext,
951 mut vd: Validator,
952 _tooltipped: Tooltipped,
953) {
954 vd.field_script_value("literacy_rate", sc);
955}
956
957pub fn validate_move_partial_pop(
958 _key: &Token,
959 _block: &Block,
960 _data: &Everything,
961 sc: &mut ScopeContext,
962 mut vd: Validator,
963 _tooltipped: Tooltipped,
964) {
965 vd.req_field("state");
966 vd.field_target("state", sc, Scopes::State);
967 vd.field_script_value("population", sc);
969 vd.field_script_value("population_ratio", sc);
970}
971
972pub fn validate_set_hub_name(
973 _key: &Token,
974 _block: &Block,
975 _data: &Everything,
976 _sc: &mut ScopeContext,
977 mut vd: Validator,
978 _tooltipped: Tooltipped,
979) {
980 vd.req_field("type");
981 vd.req_field("name");
982 vd.field_choice("type", &["city", "farm", "mine", "port", "wood"]);
983 vd.field_item("name", Item::Localization);
984}
985
986pub fn validate_sort(
987 _key: &Token,
988 _block: &Block,
989 _data: &Everything,
990 sc: &mut ScopeContext,
991 mut vd: Validator,
992 _tooltipped: Tooltipped,
993) {
994 vd.req_field("name");
995 vd.req_field("order");
996 if let Some(name) = vd.field_identifier("name", "list name") {
997 sc.open_scope(Scopes::all(), name.clone());
999 vd.field_script_value("order", sc);
1000 sc.close();
1001 }
1002}
1003
1004pub fn validate_owes_obligation(
1005 _key: &Token,
1006 _block: &Block,
1007 _data: &Everything,
1008 sc: &mut ScopeContext,
1009 mut vd: Validator,
1010 _tooltipped: Tooltipped,
1011) {
1012 vd.req_field("country");
1013 vd.req_field("setting");
1014 vd.field_target("country", sc, Scopes::Country);
1015 vd.field_bool("setting");
1016}
1017
1018pub fn validate_owner_of_provinces(
1019 _key: &Token,
1020 _block: &Block,
1021 _data: &Everything,
1022 sc: &mut ScopeContext,
1023 mut vd: Validator,
1024 _tooltipped: Tooltipped,
1025) {
1026 vd.req_field("country");
1027 vd.req_field("provinces");
1028 vd.field_target("country", sc, Scopes::Country);
1029 vd.field_list_items("provinces", Item::Province);
1030}
1031
1032pub fn validate_violate_sovereignty_join(
1033 _key: &Token,
1034 _block: &Block,
1035 _data: &Everything,
1036 sc: &mut ScopeContext,
1037 mut vd: Validator,
1038 _tooltipped: Tooltipped,
1039) {
1040 vd.req_field("violator");
1043 vd.req_field("target");
1044 vd.req_field("join_violator");
1045 vd.field_target("violator", sc, Scopes::Country);
1046 vd.field_target("target", sc, Scopes::Country);
1047 vd.field_bool("join_violator");
1048}
1049
1050pub fn validate_ruling_ig(
1051 _key: &Token,
1052 _block: &Block,
1053 data: &Everything,
1054 _sc: &mut ScopeContext,
1055 mut vd: Validator,
1056 _tooltipped: Tooltipped,
1057) {
1058 for token in vd.values() {
1059 data.verify_exists(Item::InterestGroup, token);
1060 }
1061}
1062
1063pub fn validate_start_tutorial(
1064 _key: &Token,
1065 _block: &Block,
1066 _data: &Everything,
1067 sc: &mut ScopeContext,
1068 mut vd: Validator,
1069 _tooltipped: Tooltipped,
1070) {
1071 vd.req_field("tutorial_lesson");
1074 vd.field_item("tutorial_lesson", Item::TutorialLesson);
1075 vd.field_target("journal_entry", sc, Scopes::JournalEntry);
1076}
1077
1078pub fn validate_audio_event(
1079 _key: &Token,
1080 _block: &Block,
1081 _data: &Everything,
1082 _sc: &mut ScopeContext,
1083 mut vd: Validator,
1084 _tooltipped: Tooltipped,
1085) {
1086 vd.field_value("persistent_object"); vd.field_value("event"); }
1089
1090pub fn validate_withdraw(
1091 _key: &Token,
1092 _block: &Block,
1093 _data: &Everything,
1094 sc: &mut ScopeContext,
1095 mut vd: Validator,
1096 _tooltipped: Tooltipped,
1097) {
1098 vd.req_field("country");
1099 vd.field_target("country", sc, Scopes::Country);
1100 vd.field_bool("apply_break_penalties");
1101}
1102
1103pub fn validate_create_treaty(
1104 _key: &Token,
1105 block: &Block,
1106 _data: &Everything,
1107 sc: &mut ScopeContext,
1108 mut vd: Validator,
1109 _tooltipped: Tooltipped,
1110) {
1111 vd.field_localization("name", sc);
1112 vd.req_field_one_of(&["first_country", "amends"]);
1113 vd.req_field_one_of(&["second_country", "amends"]);
1114 vd.field_target_ok_this("first_country", sc, Scopes::Country);
1115 vd.field_target_ok_this("second_country", sc, Scopes::Country);
1116 vd.field_target_ok_this("amends", sc, Scopes::Treaty);
1117
1118 vd.field_bool("is_draft");
1119 if block.has_key("amends") && !block.get_field_bool("is_draft").unwrap_or(true) {
1120 let msg = "treaties that are amendments must be drafts";
1121 let loc = block.get_key("draft").unwrap();
1123 err(ErrorKey::Validation).msg(msg).loc(loc).push();
1124 }
1125
1126 vd.field_date("entered_into_force_on");
1127 vd.field_validated_block("binding_period", |block, data| {
1128 let mut vd = Validator::new(block, data);
1129 validate_optional_duration(&mut vd, sc);
1130 });
1131
1132 if !block.has_key("amends") && !block.has_key("articles_to_create") {
1133 let msg = "treaties that are not amendments must create at least one article";
1134 err(ErrorKey::Validation).msg(msg).loc(block).push();
1135 }
1136 vd.field_validated_block("articles_to_create", |block, data| {
1137 let mut vd = Validator::new(block, data);
1138 let mut count = 0;
1139 for block in vd.blocks() {
1140 count += 1;
1141 validate_treaty_article(block, data, sc);
1142 }
1143 if count == 0 {
1144 let msg = "treaties that are not amendments must create at least one article";
1145 err(ErrorKey::Validation).msg(msg).loc(block).push();
1146 }
1147 });
1148}
1149
1150pub fn validate_tariff_level(
1151 _key: &Token,
1152 _block: &Block,
1153 _data: &Everything,
1154 sc: &mut ScopeContext,
1155 mut vd: Validator,
1156 _tooltipped: Tooltipped,
1157) {
1158 vd.field_target("goods", sc, Scopes::Goods);
1159 vd.field_choice("level", TARIFF_LEVELS);
1160}
1161
1162pub fn validate_activate_building(
1163 _key: &Token,
1164 _block: &Block,
1165 _data: &Everything,
1166 _sc: &mut ScopeContext,
1167 mut vd: Validator,
1168 _tooltipped: Tooltipped,
1169) {
1170 vd.multi_field_item("building", Item::BuildingType);
1171}
1172
1173pub fn validate_execute_event_option(
1174 _key: &Token,
1175 _block: &Block,
1176 _data: &Everything,
1177 _sc: &mut ScopeContext,
1178 mut vd: Validator,
1179 _tooltipped: Tooltipped,
1180) {
1181 vd.req_field("event");
1182 vd.req_field("option");
1183 vd.field_item("event", Item::Event);
1184 vd.field_integer_range("option", 0..);
1186}
1187
1188pub fn validate_national_awakening(
1189 _key: &Token,
1190 _block: &Block,
1191 _data: &Everything,
1192 sc: &mut ScopeContext,
1193 mut vd: Validator,
1194 _tooltipped: Tooltipped,
1195) {
1196 vd.req_field("culture");
1197 vd.req_field("months");
1198 vd.field_target("culture", sc, Scopes::Culture);
1199 vd.field_script_value("months", sc);
1200 vd.field_target("state_region", sc, Scopes::StateRegion);
1201}
1202
1203pub fn validate_add_amendment(
1204 _key: &Token,
1205 _block: &Block,
1206 _data: &Everything,
1207 sc: &mut ScopeContext,
1208 mut vd: Validator,
1209 _tooltipped: Tooltipped,
1210) {
1211 vd.req_field("type");
1212 vd.req_field("sponsor");
1213 vd.field_item("type", Item::Amendment);
1214 vd.field_target("sponsor", sc, Scopes::InterestGroup);
1215 vd.field_script_value("cooldown", sc);
1216 vd.field_script_value("timeout", sc);
1217}
1218
1219pub fn validate_spawn_entity_effect(
1220 _key: &Token,
1221 _block: &Block,
1222 _data: &Everything,
1223 sc: &mut ScopeContext,
1224 mut vd: Validator,
1225 _tooltipped: Tooltipped,
1226) {
1227 vd.req_field("name");
1228 vd.req_field("duration");
1229 vd.field_item("name", Item::Entity);
1230 vd.field_script_value("duration", sc);
1231}
1232
1233pub fn validate_teleport_to_front(
1234 _key: &Token,
1235 bv: &BV,
1236 data: &Everything,
1237 sc: &mut ScopeContext,
1238 _tooltipped: Tooltipped,
1239) {
1240 match bv {
1241 BV::Value(token) => {
1242 let mut vd = ValueValidator::new(token, data);
1243 vd.target(sc, Scopes::Front);
1244 }
1245 BV::Block(block) => {
1246 let mut vd = Validator::new(block, data);
1247 vd.field_target("front", sc, Scopes::Front);
1248 vd.field_target("base_camp", sc, Scopes::Province);
1249 }
1250 }
1251}
1252
1253pub fn validate_career_length(
1254 _key: &Token,
1255 _block: &Block,
1256 data: &Everything,
1257 sc: &mut ScopeContext,
1258 mut vd: Validator,
1259 _tooltipped: Tooltipped,
1260) {
1261 vd.req_field("role");
1262 #[allow(clippy::collapsible_if)]
1263 if let Some(role) = vd.field_value("role") {
1264 if !data.item_exists(Item::CharacterRole, role.as_str())
1265 && !data.item_exists(Item::CharacterArchetype, role.as_str())
1266 {
1267 let msg = "`{role}` not found as character role or character archetype";
1268 err(ErrorKey::MissingItem).msg(msg).loc(role).push();
1269 }
1270 }
1271 validate_optional_duration(&mut vd, sc);
1272 vd.field_list_numeric_exactly("random_range", 2);
1274}
1275
1276pub fn validate_add_character_role(
1277 _key: &Token,
1278 mut vvd: ValueValidator,
1279 _sc: &mut ScopeContext,
1280 _tooltipped: Tooltipped,
1281) {
1282 let role = vvd.value();
1283 if !vvd.data().item_exists(Item::CharacterRole, role.as_str())
1284 && !vvd.data().item_exists(Item::CharacterArchetype, role.as_str())
1285 {
1286 let msg = "`{role}` not found as character role or character archetype";
1287 err(ErrorKey::MissingItem).msg(msg).loc(role).push();
1288 }
1289 vvd.accept();
1290}
1291
1292pub fn validate_temporary_hostilities(
1293 key: &Token,
1294 _block: &Block,
1295 _data: &Everything,
1296 sc: &mut ScopeContext,
1297 mut vd: Validator,
1298 _tooltipped: Tooltipped,
1299) {
1300 vd.req_field("country");
1301 vd.field_target("country", sc, Scopes::Country);
1302 vd.field_choice("type", &["naval", "war", "limited_war"]);
1303 if key.as_str().starts_with("enable_") {
1304 validate_optional_duration(&mut vd, sc);
1305 }
1306}
1307
1308pub fn validate_replace_character_roles(
1309 _key: &Token,
1310 _block: &Block,
1311 _data: &Everything,
1312 _sc: &mut ScopeContext,
1313 mut vd: Validator,
1314 _tooltipped: Tooltipped,
1315) {
1316 for field in &["add", "remove"] {
1317 vd.field_validated_list(field, |role, data| {
1318 if !data.item_exists(Item::CharacterRole, role.as_str())
1319 && !data.item_exists(Item::CharacterArchetype, role.as_str())
1320 {
1321 let msg = "`{role}` not found as character role or character archetype";
1322 err(ErrorKey::MissingItem).msg(msg).loc(role).push();
1323 }
1324 });
1325 }
1326 vd.field_bool("remove_all");
1327}
1328
1329pub fn validate_start_harvest_condition(
1330 _key: &Token,
1331 _block: &Block,
1332 _data: &Everything,
1333 sc: &mut ScopeContext,
1334 mut vd: Validator,
1335 _tooltipped: Tooltipped,
1336) {
1337 vd.req_field("type");
1338 vd.field_item("type", Item::HarvestConditionType);
1339 vd.field_script_value("intensity", sc);
1340 vd.field_script_value("duration", sc);
1341}