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_target("type", sc, Scopes::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 if let Some(role) = vd.field_value("role") {
391 if !data.item_exists(Item::CharacterRole, role.as_str())
392 && !data.item_exists(Item::CharacterArchetype, role.as_str())
393 {
394 let msg = "`{role}` not found as character role or character archetype";
395 err(ErrorKey::MissingItem).msg(msg).loc(role).push();
396 }
397 }
398 vd.field_validated_value("female", |_, mut vd| {
399 vd.maybe_bool();
400 vd.target(sc, Scopes::Character);
401 });
402 vd.field_validated_value("noble", |_, mut vd| {
403 vd.maybe_bool();
404 vd.target(sc, Scopes::Character);
405 });
406 vd.field_bool("ruler");
407 vd.field_bool("heir");
408 vd.field_bool("historical");
409 vd.field_validated("age", |bv, data| {
410 match bv {
411 BV::Value(value) => {
412 let mut vd = ValueValidator::new(value, data);
414 vd.maybe_is("default");
415 vd.maybe_integer();
416 vd.target(sc, Scopes::Character);
417 }
418 BV::Block(block) => {
419 let mut vd = Validator::new(block, data);
421 vd.req_tokens_integers_exactly(2);
422 }
423 }
424 });
425 vd.field_item_or_target("ideology", sc, Item::Ideology, Scopes::Ideology);
426 vd.field_item_or_target("holding_type", sc, Item::BuildingType, Scopes::BuildingType);
427 vd.field_item_or_target("interest_group", sc, Item::InterestGroup, Scopes::InterestGroup);
428 vd.field_item("home_region", Item::StateRegion);
429 vd.field_item("template", Item::CharacterTemplate);
430 vd.field_effect_rooted("on_created", Tooltipped::No, Scopes::Character);
431 if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
432 sc.define_name_token(name.as_str(), Scopes::Character, name, Temporary::No);
433 }
434 if let Some(name) = vd.field_identifier("save_temporary_scope_as", "scope name") {
435 sc.define_name_token(name.as_str(), Scopes::Character, name, Temporary::Yes);
436 }
437 vd.field_effect_rooted("trait_generation", Tooltipped::No, Scopes::Character);
438 vd.field_item_or_target("hq", sc, Item::StrategicRegion, Scopes::Hq | Scopes::StrategicRegion);
440
441 vd.field_date("birth_date");
445 vd.field_list_items("traits", Item::CharacterTrait);
446 vd.field_item("dna", Item::Dna);
447 vd.field_bool("is_general");
448 vd.field_bool("is_admiral");
449 vd.field_bool("is_agitator");
450 vd.field_bool("ig_leader");
451 vd.field_item("commander_rank", Item::CommanderRank);
452}
453
454pub fn validate_create_country(
455 _key: &Token,
456 _block: &Block,
457 _data: &Everything,
458 sc: &mut ScopeContext,
459 mut vd: Validator,
460 _tooltipped: Tooltipped,
461) {
462 vd.field_item("tag", Item::Country);
463 vd.field_target_ok_this("origin", sc, Scopes::Country);
464 vd.multi_field_target("state", sc, Scopes::State);
465 vd.multi_field_target("province", sc, Scopes::Province);
466 vd.field_effect_rooted("on_created", Tooltipped::No, Scopes::Country);
467}
468
469pub fn validate_create_dynamic_country(
470 _key: &Token,
471 block: &Block,
472 _data: &Everything,
473 sc: &mut ScopeContext,
474 mut vd: Validator,
475 _tooltipped: Tooltipped,
476) {
477 vd.field_target_ok_this("origin", sc, Scopes::Country);
478 if !block.has_key("origin") {
479 vd.req_field("country_type");
480 vd.req_field("tier");
481 vd.req_field("culture");
482 vd.req_field("religion");
483 vd.req_field("capital");
484 vd.req_field("color");
485 vd.req_field("primary_unit_color");
486 vd.req_field("secondary_unit_color");
487 vd.req_field("tertiary_unit_color");
488 }
489 vd.field_item("country_type", Item::CountryType);
490 vd.field_item("tier", Item::CountryTier);
491 vd.multi_field_target("culture", sc, Scopes::Culture);
492 vd.field_target("religion", sc, Scopes::Religion);
493 vd.field_target("capital", sc, Scopes::State);
494 vd.field_item("social_hierarchy", Item::SocialHierarchy);
495 vd.field_trigger_rooted("cede_state_trigger", Tooltipped::No, Scopes::State);
496 vd.field_validated("color", validate_possibly_named_color);
497 vd.field_validated("primary_unit_color", validate_possibly_named_color);
498 vd.field_validated("secondary_unit_color", validate_possibly_named_color);
499 vd.field_validated("tertiary_unit_color", validate_possibly_named_color);
500 vd.field_effect_rooted("on_created", Tooltipped::No, Scopes::Country);
501}
502
503pub fn validate_create_diplomatic_play(
504 _key: &Token,
505 _block: &Block,
506 _data: &Everything,
507 sc: &mut ScopeContext,
508 mut vd: Validator,
509 _tooltipped: Tooltipped,
510) {
511 vd.field_localization("name", sc);
512 vd.field_integer_range("escalation", 0..=100);
513 vd.field_bool("war");
514 vd.field_item_or_target_ok_this("initiator", sc, Item::Country, Scopes::Country);
515 vd.field_item("type", Item::DiplomaticPlay);
516 vd.advice_field(
517 "handle_annexation_as_civil_war",
518 "docs say `handle_annexation_as_civil_war` but it's `annex_as_civil_war`",
519 );
520 vd.field_bool("annex_as_civil_war");
521 for field in &["add_initiator_backers", "add_target_backers"] {
522 vd.field_validated_list(field, |token, data| {
523 let mut vd = ValueValidator::new(token, data);
524 vd.maybe_item(Item::Country);
525 vd.target(sc, Scopes::Country);
526 });
527 }
528 vd.multi_field_validated_block_sc("add_war_goal", sc, validate_war_goal);
529
530 vd.field_target("target_state", sc, Scopes::State);
533 vd.field_target("target_country", sc, Scopes::Country);
534 vd.field_target("target_region", sc, Scopes::StrategicRegion);
535}
536
537fn validate_war_goal(block: &Block, data: &Everything, sc: &mut ScopeContext) {
538 let mut vd = Validator::new(block, data);
539 vd.set_case_sensitive(false);
540 vd.field_item_or_target_ok_this("holder", sc, Item::Country, Scopes::Country);
541 vd.field_item("type", Item::WarGoalType);
542 vd.advice_field("state", "docs say `state` but it's `target_state`");
543 vd.field_target("target_state", sc, Scopes::State);
544 vd.advice_field("country", "docs say `country` but it's `target_country`");
545 vd.field_target("target_country", sc, Scopes::Country);
546 vd.advice_field("region", "docs say `region` but it's `target_region`");
547 vd.field_target("target_region", sc, Scopes::StrategicRegion);
548 vd.field_bool("primary_demand");
549 if let Some(goal_type) = block.get_field_value("type")
550 && goal_type.is("enforce_treaty_article")
551 {
552 vd.multi_field_validated_block_sc("article", sc, validate_treaty_article);
553 }
554}
555
556pub fn validate_create_mass_migration(
557 _key: &Token,
558 _block: &Block,
559 _data: &Everything,
560 sc: &mut ScopeContext,
561 mut vd: Validator,
562 _tooltipped: Tooltipped,
563) {
564 vd.req_field("origin");
565 vd.field_target("origin", sc, Scopes::Country);
566 vd.req_field("culture");
567 vd.field_target("culture", sc, Scopes::Culture);
568}
569
570pub fn validate_create_military_formation(
571 _key: &Token,
572 block: &Block,
573 _data: &Everything,
574 sc: &mut ScopeContext,
575 mut vd: Validator,
576 _tooltipped: Tooltipped,
577) {
578 vd.field_localization("name", sc);
579 vd.field_choice("type", &["army", "fleet"]);
580 let is_fleet = block.field_value_is("type", "fleet");
581 vd.field_target("hq_region", sc, Scopes::StrategicRegion);
582 vd.field_target("supply_hub", sc, Scopes::State);
583 vd.multi_field_validated_block("combat_unit", |block, data| {
584 let mut vd = Validator::new(block, data);
585 vd.field_target("type", sc, Scopes::CombatUnitType);
586 vd.field_choice("service_type", &["regular", "conscript"]);
587 if let Some(token) = vd.field_value("service_type")
588 && is_fleet
589 && token.is("conscript")
590 {
591 let msg = "conscript is not applicable to fleets";
592 err(ErrorKey::Choice).msg(msg).loc(token).push();
593 }
594 vd.field_target("state_region", sc, Scopes::StateRegion);
595 vd.field_integer("count");
596 });
597 if is_fleet {
598 vd.ban_field("mobilization_options", || "armies");
599 }
600 vd.field_validated_list("mobilization_options", |token, data| {
601 let mut vd = ValueValidator::new(token, data);
602 vd.target(sc, Scopes::MobilizationOption);
603 });
604
605 if let Some(name) = vd.field_identifier("save_scope_as", "scope name") {
608 sc.define_name_token(name.as_str(), Scopes::MilitaryFormation, name, Temporary::No);
609 }
610 if let Some(name) = vd.field_identifier("save_temporary_scope_as", "scope name") {
611 sc.define_name_token(name.as_str(), Scopes::MilitaryFormation, name, Temporary::Yes);
612 }
613 vd.field_validated_block("ship", |block, data| {
614 let mut vd = Validator::new(block, data);
615 vd.field_target("type", sc, Scopes::ShipType);
616 vd.field_integer("count");
617 vd.field_target("state_region", sc, Scopes::StateRegion);
618 vd.field_bool("flagship");
619 });
620}
621
622pub fn validate_create_pop(
623 _key: &Token,
624 block: &Block,
625 _data: &Everything,
626 _sc: &mut ScopeContext,
627 mut vd: Validator,
628 _tooltipped: Tooltipped,
629) {
630 #[allow(clippy::integer_division)]
633 fn sum_fractions_warner<E: ErrorLoc>(sum_fractions: i64, loc: E) {
634 if sum_fractions != 100_000 {
635 let msg = format!(
636 "fractions should add to exactly 1, currently {}.{:05}",
637 sum_fractions / 100_000,
638 sum_fractions % 100_000,
639 );
640 warn(ErrorKey::Validation).msg(msg.trim_end_matches('0')).loc(loc).push();
641 }
642 }
643
644 vd.field_item("pop_type", Item::PopType);
645 vd.field_integer("size");
646
647 let mut available_cultures = TigerHashSet::default();
648
649 if let Some(token) = vd.field_value("culture") {
650 available_cultures.insert(token.clone());
651
652 vd.ban_field("cultures", || "pops with several cultures");
653 vd.field_item("culture", Item::Culture);
654 } else {
655 vd.field_validated_block("cultures", |block, data| {
656 let mut vd = Validator::new(block, data);
657 let mut sum_fractions = 0_i64;
658
659 vd.validate_item_key_values(Item::Culture, |key, mut vd| {
660 available_cultures.insert(key.clone());
661 vd.numeric_range(0.0..=1.0);
662
663 sum_fractions += vd.value().get_fixed_number().unwrap_or(0);
664 });
665
666 sum_fractions_warner(sum_fractions, block);
667 });
668 }
669
670 if block.has_key("religion") {
671 vd.ban_field("split_religion", || "pops without a `religion` field");
672 vd.field_item("religion", Item::Religion);
673 } else if block.has_key("split_religion") {
674 let mut used_cultures = TigerHashSet::default();
675
676 vd.multi_field_validated_block("split_religion", |block, data| {
677 let mut vd = Validator::new(block, data);
678 let mut only_one_culture = false;
679
680 vd.validate_item_key_blocks(Item::Culture, |key, block, data| {
681 if only_one_culture {
682 let msg = "split_religion should contain only one culture block";
683 err(ErrorKey::DuplicateItem).msg(msg).loc(key).push();
684 }
685 only_one_culture = true;
686
687 if !available_cultures.contains(key.as_str()) {
688 let msg = "culture being split does not appear in pop";
689 err(ErrorKey::FieldMissing).msg(msg).loc(key).push();
690 }
691
692 match used_cultures.get(key) {
693 Some(duplicate) => {
694 let msg =
695 format!("trying to split religion of culture {key} multiple times");
696 let msg_other = "first split here";
697 err(ErrorKey::DuplicateField)
698 .msg(msg)
699 .loc(key)
700 .loc_msg(duplicate, msg_other)
701 .push();
702 }
703 None => {
704 used_cultures.insert(key.clone());
705 }
706 }
707
708 let mut vd = Validator::new(block, data);
709 let mut sum_fractions = 0_i64;
710
711 vd.validate_item_key_values(Item::Religion, |_, mut vd| {
712 vd.numeric_range(0.0..=1.0);
713
714 sum_fractions += vd.value().get_fixed_number().unwrap_or(0);
715 });
716
717 sum_fractions_warner(sum_fractions, block);
718 });
719
720 if !only_one_culture {
721 let msg = "split_religion must contain one culture block";
722 err(ErrorKey::DuplicateItem).msg(msg).loc(block).push();
723 }
724 });
725 }
726}
727
728pub fn validate_create_state(
729 _key: &Token,
730 _block: &Block,
731 _data: &Everything,
732 sc: &mut ScopeContext,
733 mut vd: Validator,
734 _tooltipped: Tooltipped,
735) {
736 vd.field_target("country", sc, Scopes::Country);
739 vd.field_list_items("owned_provinces", Item::Province);
740 vd.field_choice("state_type", STATE_TYPES);
741}
742
743pub fn validate_form_government(
744 _key: &Token,
745 _block: &Block,
746 _data: &Everything,
747 sc: &mut ScopeContext,
748 mut vd: Validator,
749 _tooltipped: Tooltipped,
750) {
751 vd.field_script_value("value", sc);
752 vd.multi_field_item("interest_group_type", Item::InterestGroup);
753}
754
755pub fn validate_set_secret_goal(
756 _key: &Token,
757 _block: &Block,
758 _data: &Everything,
759 sc: &mut ScopeContext,
760 mut vd: Validator,
761 _tooltipped: Tooltipped,
762) {
763 vd.req_field("country");
764 vd.advice_field("tcountry", "documentation says tcountry but it's just country");
765 vd.req_field("secret_goal");
766 vd.field_item_or_target("country", sc, Item::Country, Scopes::Country);
767 vd.field_item("secret_goal", Item::SecretGoal);
768}
769
770pub fn validate_post_notification(
771 _key: &Token,
772 mut vd: ValueValidator,
773 sc: &mut ScopeContext,
774 _tooltipped: Tooltipped,
775) {
776 vd.item(Item::Message);
777 vd.implied_localization_sc("notification_", "_name", sc);
778 vd.implied_localization_sc("notification_", "_desc", sc);
779 vd.implied_localization_sc("notification_", "_tooltip", sc);
780}
781
782pub fn validate_progress(
783 _key: &Token,
784 _block: &Block,
785 _data: &Everything,
786 sc: &mut ScopeContext,
787 mut vd: Validator,
788 _tooltipped: Tooltipped,
789) {
790 vd.req_field("value");
791 vd.req_field("name");
792 vd.field_script_value("value", sc);
793 vd.field_item("name", Item::ScriptedProgressBar);
794}
795
796pub fn validate_join_war(
797 _key: &Token,
798 _block: &Block,
799 _data: &Everything,
800 sc: &mut ScopeContext,
801 mut vd: Validator,
802 _tooltipped: Tooltipped,
803) {
804 vd.req_field("target");
805 vd.req_field("side");
806 vd.field_target("target", sc, Scopes::Country);
807 vd.field_target("side", sc, Scopes::Country);
808}
809
810pub fn validate_create_truce(
811 _key: &Token,
812 _block: &Block,
813 _data: &Everything,
814 sc: &mut ScopeContext,
815 mut vd: Validator,
816 _tooltipped: Tooltipped,
817) {
818 vd.req_field("country");
819 vd.req_field("months");
820 vd.advice_field("tcountry", "documentation says tcountry but it's just country");
821 vd.field_target("country", sc, Scopes::Country);
822 vd.field_integer("months");
824}
825
826pub fn validate_create_power_bloc(
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("name");
835 vd.req_field("map_color");
836 vd.req_field("identity");
837 vd.field_validated_sc("name", sc, validate_desc);
839 vd.field_validated_block("map_color", validate_color);
841 vd.field_item("identity", Item::PowerBlocIdentity);
842 vd.multi_field_item("principle", Item::Principle);
843 vd.multi_field_target("member", sc, Scopes::Country);
844
845 vd.field_date("founding_date");
848}
849
850pub fn validate_create_lobby(
851 _key: &Token,
852 _block: &Block,
853 _data: &Everything,
854 sc: &mut ScopeContext,
855 mut vd: Validator,
856 _tooltipped: Tooltipped,
857) {
858 vd.req_field("type");
859 vd.req_field("target");
860 vd.field_item("type", Item::PoliticalLobby);
861 vd.field_target("target", sc, Scopes::Country);
862 vd.multi_field_target("add_interest_group", sc, Scopes::InterestGroup);
863 vd.field_choice("lobby_formation_reason", LOBBY_FORMATION_REASON);
865}
866
867pub fn validate_create_movement(
868 _key: &Token,
869 _block: &Block,
870 _data: &Everything,
871 sc: &mut ScopeContext,
872 mut vd: Validator,
873 _tooltipped: Tooltipped,
874) {
875 vd.req_field("type");
876 vd.field_item("type", Item::PoliticalMovement);
877 vd.advice_field("movement_type", "docs say movement_type but it's just type");
878 vd.field_target("religion", sc, Scopes::Religion);
879 vd.field_target("culture", sc, Scopes::Culture);
880}
881
882pub fn validate_create_catalyst(
883 _key: &Token,
884 _block: &Block,
885 _data: &Everything,
886 sc: &mut ScopeContext,
887 mut vd: Validator,
888 _tooltipped: Tooltipped,
889) {
890 vd.req_field("type");
891 vd.req_field("target");
892 vd.field_item("type", Item::DiplomaticCatalyst);
893 vd.field_target("target", sc, Scopes::Country);
894}
895
896pub fn validate_change_appeasement(
897 _key: &Token,
898 _block: &Block,
899 _data: &Everything,
900 sc: &mut ScopeContext,
901 mut vd: Validator,
902 _tooltipped: Tooltipped,
903) {
904 vd.req_field("amount");
905 vd.req_field("factor");
906 vd.field_item("factor", Item::PoliticalLobbyAppeasement);
907 vd.field_script_value("amount", sc);
908}
909
910pub fn validate_pop_wealth(
912 _key: &Token,
913 _block: &Block,
914 _data: &Everything,
915 sc: &mut ScopeContext,
916 mut vd: Validator,
917 _tooltipped: Tooltipped,
918) {
919 vd.req_field("wealth_distribution");
920 vd.field_script_value("wealth_distribution", sc);
921 vd.field_bool("update_loyalties");
922}
923
924pub fn validate_kill_character(
925 _key: &Token,
926 bv: &BV,
927 data: &Everything,
928 _sc: &mut ScopeContext,
929 _tooltipped: Tooltipped,
930) {
931 match bv {
932 BV::Value(value) => {
933 let mut vd = ValueValidator::new(value, data);
935 vd.bool();
936 }
937 BV::Block(block) => {
938 let mut vd = Validator::new(block, data);
940 vd.set_case_sensitive(false);
941 vd.field_bool("value");
942 vd.field_bool("hidden");
943 }
944 }
945}
946
947pub fn validate_kill_population(
950 key: &Token,
951 _block: &Block,
952 _data: &Everything,
953 sc: &mut ScopeContext,
954 mut vd: Validator,
955 _tooltipped: Tooltipped,
956) {
957 let percent = key.is("kill_population_percent") || key.is("kill_population_percent_in_state");
958 if percent {
959 vd.field_numeric_range("percent", 0.0..=1.0);
960 } else {
961 vd.field_integer("value");
962 }
963 vd.field_target("culture", sc, Scopes::Culture);
964 vd.field_target("religion", sc, Scopes::Religion);
965 vd.field_target("interest_group", sc, Scopes::InterestGroup);
966 vd.field_item("pop_type", Item::PopType);
967 vd.field_choice("strata", STRATA);
968}
969
970pub fn validate_pop_literacy(
971 _key: &Token,
972 _block: &Block,
973 _data: &Everything,
974 sc: &mut ScopeContext,
975 mut vd: Validator,
976 _tooltipped: Tooltipped,
977) {
978 vd.field_script_value("literacy_rate", sc);
979}
980
981pub fn validate_move_partial_pop(
982 _key: &Token,
983 _block: &Block,
984 _data: &Everything,
985 sc: &mut ScopeContext,
986 mut vd: Validator,
987 _tooltipped: Tooltipped,
988) {
989 vd.req_field("state");
990 vd.field_target("state", sc, Scopes::State);
991 vd.field_script_value("population", sc);
993 vd.field_script_value("population_ratio", sc);
994}
995
996pub fn validate_set_hub_name(
997 _key: &Token,
998 _block: &Block,
999 _data: &Everything,
1000 _sc: &mut ScopeContext,
1001 mut vd: Validator,
1002 _tooltipped: Tooltipped,
1003) {
1004 vd.req_field("type");
1005 vd.req_field("name");
1006 vd.field_choice("type", &["city", "farm", "mine", "port", "wood"]);
1007 vd.field_item("name", Item::Localization);
1008}
1009
1010pub fn validate_sort(
1011 _key: &Token,
1012 _block: &Block,
1013 _data: &Everything,
1014 sc: &mut ScopeContext,
1015 mut vd: Validator,
1016 _tooltipped: Tooltipped,
1017) {
1018 vd.req_field("name");
1019 vd.req_field("order");
1020 if let Some(name) = vd.field_identifier("name", "list name") {
1021 sc.open_scope(Scopes::all(), name.clone());
1023 vd.field_script_value("order", sc);
1024 sc.close();
1025 }
1026}
1027
1028pub fn validate_owes_obligation(
1029 _key: &Token,
1030 _block: &Block,
1031 _data: &Everything,
1032 sc: &mut ScopeContext,
1033 mut vd: Validator,
1034 _tooltipped: Tooltipped,
1035) {
1036 vd.req_field("country");
1037 vd.req_field("setting");
1038 vd.field_target("country", sc, Scopes::Country);
1039 vd.field_bool("setting");
1040}
1041
1042pub fn validate_owner_of_provinces(
1043 _key: &Token,
1044 _block: &Block,
1045 _data: &Everything,
1046 sc: &mut ScopeContext,
1047 mut vd: Validator,
1048 _tooltipped: Tooltipped,
1049) {
1050 vd.req_field("country");
1051 vd.req_field("provinces");
1052 vd.field_target("country", sc, Scopes::Country);
1053 vd.field_list_items("provinces", Item::Province);
1054}
1055
1056pub fn validate_violate_sovereignty_join(
1057 _key: &Token,
1058 _block: &Block,
1059 _data: &Everything,
1060 sc: &mut ScopeContext,
1061 mut vd: Validator,
1062 _tooltipped: Tooltipped,
1063) {
1064 vd.req_field("violator");
1067 vd.req_field("target");
1068 vd.req_field("join_violator");
1069 vd.field_target("violator", sc, Scopes::Country);
1070 vd.field_target("target", sc, Scopes::Country);
1071 vd.field_bool("join_violator");
1072}
1073
1074pub fn validate_ruling_ig(
1075 _key: &Token,
1076 _block: &Block,
1077 data: &Everything,
1078 _sc: &mut ScopeContext,
1079 mut vd: Validator,
1080 _tooltipped: Tooltipped,
1081) {
1082 for token in vd.values() {
1083 data.verify_exists(Item::InterestGroup, token);
1084 }
1085}
1086
1087pub fn validate_start_tutorial(
1088 _key: &Token,
1089 _block: &Block,
1090 _data: &Everything,
1091 sc: &mut ScopeContext,
1092 mut vd: Validator,
1093 _tooltipped: Tooltipped,
1094) {
1095 vd.req_field("tutorial_lesson");
1098 vd.field_item("tutorial_lesson", Item::TutorialLesson);
1099 vd.field_target("journal_entry", sc, Scopes::JournalEntry);
1100}
1101
1102pub fn validate_audio_event(
1103 _key: &Token,
1104 _block: &Block,
1105 _data: &Everything,
1106 _sc: &mut ScopeContext,
1107 mut vd: Validator,
1108 _tooltipped: Tooltipped,
1109) {
1110 vd.field_value("persistent_object"); vd.field_value("event"); }
1113
1114pub fn validate_withdraw(
1115 _key: &Token,
1116 _block: &Block,
1117 _data: &Everything,
1118 sc: &mut ScopeContext,
1119 mut vd: Validator,
1120 _tooltipped: Tooltipped,
1121) {
1122 vd.req_field("country");
1123 vd.field_target("country", sc, Scopes::Country);
1124 vd.field_bool("apply_break_penalties");
1125}
1126
1127pub fn validate_create_treaty(
1128 _key: &Token,
1129 block: &Block,
1130 _data: &Everything,
1131 sc: &mut ScopeContext,
1132 mut vd: Validator,
1133 _tooltipped: Tooltipped,
1134) {
1135 vd.field_localization("name", sc);
1136 vd.req_field_one_of(&["first_country", "amends"]);
1137 vd.req_field_one_of(&["second_country", "amends"]);
1138 vd.field_target_ok_this("first_country", sc, Scopes::Country);
1139 vd.field_target_ok_this("second_country", sc, Scopes::Country);
1140 vd.field_target_ok_this("amends", sc, Scopes::Treaty);
1141
1142 vd.field_bool("is_draft");
1143 if block.has_key("amends") && !block.get_field_bool("is_draft").unwrap_or(true) {
1144 let msg = "treaties that are amendments must be drafts";
1145 let loc = block.get_key("draft").unwrap();
1147 err(ErrorKey::Validation).msg(msg).loc(loc).push();
1148 }
1149
1150 vd.field_date("entered_into_force_on");
1151 vd.field_validated_block("binding_period", |block, data| {
1152 let mut vd = Validator::new(block, data);
1153 validate_optional_duration(&mut vd, sc);
1154 });
1155
1156 if !block.has_key("amends") && !block.has_key("articles_to_create") {
1157 let msg = "treaties that are not amendments must create at least one article";
1158 err(ErrorKey::Validation).msg(msg).loc(block).push();
1159 }
1160 vd.field_validated_block("articles_to_create", |block, data| {
1161 let mut vd = Validator::new(block, data);
1162 let mut count = 0;
1163 for block in vd.blocks() {
1164 count += 1;
1165 validate_treaty_article(block, data, sc);
1166 }
1167 if count == 0 {
1168 let msg = "treaties that are not amendments must create at least one article";
1169 err(ErrorKey::Validation).msg(msg).loc(block).push();
1170 }
1171 });
1172}
1173
1174pub fn validate_tariff_level(
1175 _key: &Token,
1176 _block: &Block,
1177 _data: &Everything,
1178 sc: &mut ScopeContext,
1179 mut vd: Validator,
1180 _tooltipped: Tooltipped,
1181) {
1182 vd.field_target("goods", sc, Scopes::Goods);
1183 vd.field_choice("level", TARIFF_LEVELS);
1184}
1185
1186pub fn validate_activate_building(
1187 _key: &Token,
1188 _block: &Block,
1189 _data: &Everything,
1190 _sc: &mut ScopeContext,
1191 mut vd: Validator,
1192 _tooltipped: Tooltipped,
1193) {
1194 vd.multi_field_item("building", Item::BuildingType);
1195}
1196
1197pub fn validate_execute_event_option(
1198 _key: &Token,
1199 _block: &Block,
1200 _data: &Everything,
1201 _sc: &mut ScopeContext,
1202 mut vd: Validator,
1203 _tooltipped: Tooltipped,
1204) {
1205 vd.req_field("event");
1206 vd.req_field("option");
1207 vd.field_item("event", Item::Event);
1208 vd.field_integer_range("option", 0..);
1210}
1211
1212pub fn validate_national_awakening(
1213 _key: &Token,
1214 _block: &Block,
1215 _data: &Everything,
1216 sc: &mut ScopeContext,
1217 mut vd: Validator,
1218 _tooltipped: Tooltipped,
1219) {
1220 vd.req_field("culture");
1221 vd.req_field("months");
1222 vd.field_target("culture", sc, Scopes::Culture);
1223 vd.field_script_value("months", sc);
1224 vd.field_target("state_region", sc, Scopes::StateRegion);
1225}
1226
1227pub fn validate_add_amendment(
1228 _key: &Token,
1229 _block: &Block,
1230 _data: &Everything,
1231 sc: &mut ScopeContext,
1232 mut vd: Validator,
1233 _tooltipped: Tooltipped,
1234) {
1235 vd.req_field("type");
1236 vd.req_field("sponsor");
1237 vd.field_item_or_target("type", sc, Item::Amendment, Scopes::AmendmentType);
1238 vd.field_target("sponsor", sc, Scopes::InterestGroup);
1239 vd.field_script_value("cooldown", sc);
1240 vd.field_script_value("timeout", sc);
1241}
1242
1243pub fn validate_spawn_entity_effect(
1244 _key: &Token,
1245 _block: &Block,
1246 _data: &Everything,
1247 sc: &mut ScopeContext,
1248 mut vd: Validator,
1249 _tooltipped: Tooltipped,
1250) {
1251 vd.req_field("name");
1252 vd.req_field("duration");
1253 vd.field_item("name", Item::Entity);
1254 vd.field_script_value("duration", sc);
1255}
1256
1257pub fn validate_teleport_to_front(
1258 _key: &Token,
1259 bv: &BV,
1260 data: &Everything,
1261 sc: &mut ScopeContext,
1262 _tooltipped: Tooltipped,
1263) {
1264 match bv {
1265 BV::Value(token) => {
1266 let mut vd = ValueValidator::new(token, data);
1267 vd.target(sc, Scopes::Front);
1268 }
1269 BV::Block(block) => {
1270 let mut vd = Validator::new(block, data);
1271 vd.field_target("front", sc, Scopes::Front);
1272 vd.field_target("base_camp", sc, Scopes::Province);
1273 }
1274 }
1275}
1276
1277pub fn validate_career_length(
1278 _key: &Token,
1279 _block: &Block,
1280 data: &Everything,
1281 sc: &mut ScopeContext,
1282 mut vd: Validator,
1283 _tooltipped: Tooltipped,
1284) {
1285 #[allow(clippy::collapsible_if)]
1286 if let Some(role) = vd.field_value("role") {
1287 if !data.item_exists(Item::CharacterRole, role.as_str())
1288 && !data.item_exists(Item::CharacterArchetype, role.as_str())
1289 {
1290 let msg = "`{role}` not found as character role or character archetype";
1291 err(ErrorKey::MissingItem).msg(msg).loc(role).push();
1292 }
1293 }
1294 validate_optional_duration(&mut vd, sc);
1295 vd.field_list_numeric_exactly("random_range", 2);
1297}
1298
1299pub fn validate_add_character_role(
1300 _key: &Token,
1301 mut vvd: ValueValidator,
1302 _sc: &mut ScopeContext,
1303 _tooltipped: Tooltipped,
1304) {
1305 let role = vvd.value();
1306 if !vvd.data().item_exists(Item::CharacterRole, role.as_str())
1307 && !vvd.data().item_exists(Item::CharacterArchetype, role.as_str())
1308 {
1309 let msg = "`{role}` not found as character role or character archetype";
1310 err(ErrorKey::MissingItem).msg(msg).loc(role).push();
1311 }
1312 vvd.accept();
1313}
1314
1315pub fn validate_temporary_hostilities(
1316 key: &Token,
1317 _block: &Block,
1318 _data: &Everything,
1319 sc: &mut ScopeContext,
1320 mut vd: Validator,
1321 _tooltipped: Tooltipped,
1322) {
1323 vd.req_field("country");
1324 vd.field_target("country", sc, Scopes::Country);
1325 vd.field_choice("type", &["naval", "war", "limited_war"]);
1326 if key.as_str().starts_with("enable_") {
1327 validate_optional_duration(&mut vd, sc);
1328 }
1329}
1330
1331pub fn validate_replace_character_roles(
1332 _key: &Token,
1333 _block: &Block,
1334 _data: &Everything,
1335 _sc: &mut ScopeContext,
1336 mut vd: Validator,
1337 _tooltipped: Tooltipped,
1338) {
1339 for field in &["add", "remove"] {
1340 vd.field_validated_list(field, |role, data| {
1341 if !data.item_exists(Item::CharacterRole, role.as_str())
1342 && !data.item_exists(Item::CharacterArchetype, role.as_str())
1343 {
1344 let msg = "`{role}` not found as character role or character archetype";
1345 err(ErrorKey::MissingItem).msg(msg).loc(role).push();
1346 }
1347 });
1348 }
1349 vd.field_bool("remove_all");
1350}
1351
1352pub fn validate_start_harvest_condition(
1353 _key: &Token,
1354 _block: &Block,
1355 _data: &Everything,
1356 sc: &mut ScopeContext,
1357 mut vd: Validator,
1358 _tooltipped: Tooltipped,
1359) {
1360 vd.req_field("type");
1361 vd.field_item("type", Item::HarvestConditionType);
1362 vd.field_script_value("intensity", sc);
1363 vd.field_script_value("duration", sc);
1364}