1use crate::block::{BV, Block};
2use crate::db::{Db, DbKind};
3use crate::everything::Everything;
4use crate::game::{Game, GameFlags};
5use crate::helpers::{TigerHashSet, dup_error};
6use crate::item::{Item, ItemLoader};
7#[cfg(any(feature = "ck3", feature = "vic3", feature = "eu5"))]
8use crate::report::{Confidence, Severity};
9use crate::report::{ErrorKey, err, fatal, warn};
10use crate::token::Token;
11#[cfg(any(feature = "ck3", feature = "vic3", feature = "eu5"))]
12use crate::validate::validate_numeric_range;
13use crate::validator::Validator;
14
15const BODY_TYPES: &[&str] = &[
16 "male",
17 "female",
18 "boy",
19 "girl",
20 #[cfg(feature = "eu5")]
21 "adolescent_boy",
22 #[cfg(feature = "eu5")]
23 "adolescent_girl",
24 #[cfg(any(feature = "imperator", feature = "eu5"))]
25 "infant",
26];
27
28#[derive(Clone, Debug)]
29pub struct Gene {}
30
31inventory::submit! {
32 ItemLoader::Normal(GameFlags::jomini(), Item::GeneCategory, Gene::add)
33}
34
35impl Gene {
36 pub fn add(db: &mut Db, key: Token, mut block: Block) {
37 match key.as_str() {
38 "color_genes" => {
39 for (k, b) in block.drain_definitions_warn() {
40 ColorGene::add(db, k, b);
41 }
42 }
43 "age_presets" => {
44 for (k, b) in block.drain_definitions_warn() {
45 AgePresetGene::add(db, k, b);
46 }
47 }
48 "decal_atlases" => {
49 for (_k, _b) in block.drain_definitions_warn() {
50 }
52 }
53 "morph_genes" => {
54 for (k, b) in block.drain_definitions_warn() {
55 MorphGene::add(db, k, b, false);
56 }
57 }
58 "accessory_genes" => {
59 for (k, b) in block.drain_definitions_warn() {
60 AccessoryGene::add(db, k, b);
61 }
62 }
63 "special_genes" => {
64 for (k, mut b) in block.drain_definitions_warn() {
65 match k.as_str() {
66 "morph_genes" => {
67 for (k, b) in b.drain_definitions_warn() {
68 MorphGene::add(db, k, b, true);
69 }
70 }
71 "accessory_genes" => {
72 for (k, b) in b.drain_definitions_warn() {
73 AccessoryGene::add(db, k, b);
74 }
75 }
76 _ => warn(ErrorKey::ParseError).msg("unknown gene type").loc(k).push(),
77 }
78 }
79 }
80 _ => warn(ErrorKey::ParseError).msg("unknown gene type").loc(key).push(),
81 }
82 }
83
84 pub fn verify_has_template(category: &str, template: &Token, data: &Everything) {
85 if !data.item_has_property(Item::GeneCategory, category, template.as_str()) {
86 let msg = format!("gene {category} does not have template {template}");
87 err(ErrorKey::MissingItem).msg(msg).loc(template).push();
88 }
89 }
90}
91
92#[derive(Clone, Debug)]
93pub struct ColorGene {}
94
95impl ColorGene {
96 pub fn add(db: &mut Db, key: Token, block: Block) {
97 db.add(Item::GeneCategory, key, block, Box::new(Self {}));
98 }
99}
100
101impl DbKind for ColorGene {
102 #[allow(unused_variables)] fn validate(&self, key: &Token, block: &Block, data: &Everything) {
104 let mut vd = Validator::new(block, data);
105 if Game::is_ck3() {
106 data.verify_exists(Item::Localization, key);
107 }
108
109 if Game::is_ck3() {
110 vd.req_field("group");
111 }
112
113 if Game::is_imperator() {
114 vd.req_field("index");
115 vd.field_integer("index");
116 vd.field_value("max_blend");
117 }
118
119 vd.req_field("color");
120 #[cfg(any(feature = "ck3", feature = "vic3"))]
121 vd.req_field("blend_range");
122
123 vd.field_item("sync_inheritance_with", Item::GeneCategory);
124 vd.field_value("group"); vd.field_value("color"); #[cfg(any(feature = "ck3", feature = "vic3", feature = "eu5"))]
128 vd.field_validated_block("blend_range", |block, data| {
129 validate_numeric_range(block, data, 0.0, 1.0, Severity::Warning, Confidence::Weak);
130 });
131 }
132
133 fn validate_use(
134 &self,
135 _key: &Token,
136 _block: &Block,
137 data: &Everything,
138 _call_key: &Token,
139 call_block: &Block,
140 ) {
141 let mut vd = Validator::new(call_block, data);
142 vd.req_tokens_numbers_exactly(4);
143 }
144}
145
146#[derive(Clone, Debug)]
147pub struct AgePresetGene {}
148
149impl AgePresetGene {
150 pub fn add(db: &mut Db, key: Token, block: Block) {
151 db.add(Item::GeneAgePreset, key, block, Box::new(Self {}));
152 }
153}
154
155impl DbKind for AgePresetGene {
156 fn validate(&self, _key: &Token, block: &Block, data: &Everything) {
157 validate_age(block, data);
158 }
159
160 fn validate_use(
161 &self,
162 _key: &Token,
163 _block: &Block,
164 _data: &Everything,
165 call_key: &Token,
166 _call_block: &Block,
167 ) {
168 warn(ErrorKey::Validation).msg("cannot define age preset genes").loc(call_key).push();
169 }
170}
171
172#[derive(Clone, Debug)]
173pub struct MorphGene {
174 special_gene: bool,
175 templates: TigerHashSet<Token>,
176}
177
178impl MorphGene {
179 pub fn add(db: &mut Db, key: Token, block: Block, special_gene: bool) {
180 let mut templates = TigerHashSet::default();
181 for (key, _block) in block.iter_definitions() {
182 if key.is("ugliness_feature_categories") {
183 continue;
184 }
185 if let Some(other) = templates.get(key.as_str()) {
186 dup_error(key, other, "morph gene template");
187 }
188 templates.insert(key.clone());
189 }
190 db.add(Item::GeneCategory, key, block, Box::new(Self { special_gene, templates }));
191 }
192}
193
194impl DbKind for MorphGene {
195 #[allow(unused_variables)] fn validate(&self, key: &Token, block: &Block, data: &Everything) {
197 let mut vd = Validator::new(block, data);
198
199 if Game::is_ck3() {
200 data.verify_exists(Item::Localization, key);
201 }
202
203 if Game::is_imperator() {
204 vd.req_field("index");
205 vd.field_integer("index");
206 }
207
208 vd.field_list("ugliness_feature_categories"); vd.field_bool("can_have_portrait_extremity_shift");
210 if let Some(token) = vd.field_value("group")
212 && self.special_gene
213 {
214 let msg =
215 "adding a group to a gene under special_genes will make the ruler designer crash";
216 fatal(ErrorKey::Crash).msg(msg).loc(token).push();
217 }
218 vd.unknown_block_fields(|_, block| {
219 validate_morph_gene(block, data);
220 });
221 }
222
223 fn has_property(
224 &self,
225 _key: &Token,
226 _block: &Block,
227 property: &str,
228 _data: &Everything,
229 ) -> bool {
230 self.templates.contains(property)
231 }
232
233 fn validate_property_use(
234 &self,
235 _key: &Token,
236 block: &Block,
237 property: &Token,
238 caller: &str,
239 data: &Everything,
240 ) {
241 validate_portrait_modifier_use(block, data, property, caller);
242 }
243
244 fn validate_use(
245 &self,
246 _key: &Token,
247 _block: &Block,
248 data: &Everything,
249 call_key: &Token,
250 call_block: &Block,
251 ) {
252 let mut vd = Validator::new(call_block, data);
253 let mut count = 0;
254 for token in vd.values() {
255 if count % 2 == 0 {
256 if !token.is("") && !self.templates.contains(token) {
257 let msg = format!("Gene template {token} not found in category {call_key}");
258 err(ErrorKey::MissingItem).msg(msg).loc(token).push();
259 }
260 } else if let Some(i) = token.expect_integer()
261 && !(0..=256).contains(&i)
262 {
263 warn(ErrorKey::Range).msg("expected value from 0 to 256").loc(token).push();
264 }
265 count += 1;
266 if count > 4 {
267 let msg = "too many values in this gene";
268 err(ErrorKey::Validation).msg(msg).loc(token).push();
269 break;
270 }
271 }
272 if count < 4 {
273 let msg = "too few values in this gene";
274 err(ErrorKey::Validation).msg(msg).loc(call_block).push();
275 }
276 }
277
278 fn merge_in(&mut self, other: Box<dyn DbKind>) {
279 if let Some(other) = other.as_any().downcast_ref::<Self>() {
280 for key in &other.templates {
281 if let Some(already) = self.templates.get(key.as_str()) {
282 dup_error(key, already, "morph gene template");
283 }
284 self.templates.insert(key.clone());
285 }
286 }
287 }
288}
289
290fn validate_portrait_modifier_use(
291 block: &Block,
292 data: &Everything,
293 property: &Token,
294 caller: &str,
295) {
296 if let Some(block) = block.get_field_block(property.as_str()) {
298 for field in BODY_TYPES {
300 if let Some(block) = block.get_field_block(field) {
302 for (_, token) in block.iter_assignments() {
303 if token.is("empty") {
304 continue;
305 }
306 let loca = format!("PORTRAIT_MODIFIER_{caller}_{token}");
307 if !data.item_exists(Item::Localization, &loca) {
308 let msg = format!("missing localization key {loca}");
309 warn(ErrorKey::MissingLocalization)
310 .msg(msg)
311 .loc(property)
312 .loc_msg(token, "this setting")
313 .push();
314 }
315 }
316 }
317 }
318 }
319}
320
321#[derive(Clone, Debug)]
322pub struct AccessoryGene {
323 templates: TigerHashSet<Token>,
324}
325
326impl AccessoryGene {
327 pub fn add(db: &mut Db, key: Token, block: Block) {
328 let mut templates = TigerHashSet::default();
329 for (key, _) in block.iter_definitions() {
330 if key.is("ugliness_feature_categories") {
331 continue;
332 }
333 if let Some(other) = templates.get(key.as_str()) {
334 dup_error(key, other, "accessory gene template");
335 }
336 templates.insert(key.clone());
337 }
338 db.add(Item::GeneCategory, key, block, Box::new(Self { templates }));
339 }
340
341 pub fn has_template_setting(
342 _key: &Token,
343 block: &Block,
344 _data: &Everything,
345 template: &str,
346 setting: &str,
347 ) -> bool {
348 if template == "ugliness_feature_categories" {
349 return false;
350 }
351 if let Some(block) = block.get_field_block(template) {
352 for field in BODY_TYPES {
353 if let Some(block) = block.get_field_block(field) {
355 for (_, token) in block.iter_assignments() {
356 if token.is("empty") {
357 continue;
358 }
359 if token.is(setting) {
360 return true;
361 }
362 }
363 }
364 }
365 }
366 false
367 }
368}
369
370impl DbKind for AccessoryGene {
371 #[cfg(feature = "ck3")]
372 fn add_subitems(&self, _key: &Token, block: &Block, db: &mut Db) {
373 for (key, block) in block.iter_definitions() {
374 if key.is("ugliness_feature_categories") {
375 continue;
376 }
377
378 if let Some(tags) = block.get_field_value("set_tags") {
379 for tag in tags.split(',') {
380 db.add_flag(Item::AccessoryTag, tag);
381 }
382 }
383 }
384 }
385
386 fn validate(&self, _key: &Token, block: &Block, data: &Everything) {
387 let mut vd = Validator::new(block, data);
388
389 vd.field_bool("inheritable");
390 vd.field_value("group");
391
392 if Game::is_imperator() {
393 vd.req_field("index");
394 vd.field_integer("index");
395 }
396
397 vd.unknown_block_fields(|_, block| {
398 validate_accessory_gene(block, data);
399 });
400 }
401
402 fn has_property(
403 &self,
404 _key: &Token,
405 _block: &Block,
406 property: &str,
407 _data: &Everything,
408 ) -> bool {
409 self.templates.contains(property)
410 }
411
412 fn validate_property_use(
413 &self,
414 _key: &Token,
415 block: &Block,
416 property: &Token,
417 caller: &str,
418 data: &Everything,
419 ) {
420 validate_portrait_modifier_use(block, data, property, caller);
421 }
422
423 fn validate_use(
424 &self,
425 _key: &Token,
426 _block: &Block,
427 data: &Everything,
428 call_key: &Token,
429 call_block: &Block,
430 ) {
431 let mut vd = Validator::new(call_block, data);
432 let mut count = 0;
433 for token in vd.values() {
434 if count % 2 == 0 {
435 if !token.is("") && !self.templates.contains(token) {
436 let msg = format!("Gene template {token} not found in category {call_key}");
437 err(ErrorKey::MissingItem).msg(msg).loc(token).push();
438 }
439 } else if let Some(i) = token.expect_integer()
440 && !(0..=256).contains(&i)
441 {
442 warn(ErrorKey::Range).msg("expected value from 0 to 256").loc(token).push();
443 }
444 count += 1;
445 if count > 4 {
446 let msg = "too many values in this gene";
447 err(ErrorKey::Validation).msg(msg).loc(token).push();
448 break;
449 }
450 }
451 if count < 4 {
452 let msg = "too few values in this gene";
453 err(ErrorKey::Validation).msg(msg).loc(call_block).push();
454 }
455 }
456
457 fn merge_in(&mut self, other: Box<dyn DbKind>) {
458 if let Some(other) = other.as_any().downcast_ref::<Self>() {
459 for key in &other.templates {
460 if let Some(already) = self.templates.get(key.as_str()) {
461 dup_error(key, already, "morph gene template");
462 }
463 self.templates.insert(key.clone());
464 }
465 }
466 }
467}
468
469fn validate_age_field(bv: &BV, data: &Everything) {
470 match bv {
471 BV::Value(token) => data.verify_exists(Item::GeneAgePreset, token),
472 BV::Block(block) => validate_age(block, data),
473 }
474}
475
476fn validate_age(block: &Block, data: &Everything) {
477 let mut vd = Validator::new(block, data);
478 vd.req_field("mode");
479 vd.req_field("curve");
480
481 vd.field_value("mode"); vd.field_validated_block("curve", validate_curve);
483}
484
485fn validate_curve(block: &Block, data: &Everything) {
486 let mut vd = Validator::new(block, data);
487 for block in vd.blocks() {
488 validate_curve_range(block, data);
489 }
490}
491
492fn validate_hsv_curve(block: &Block, data: &Everything) {
493 let mut vd = Validator::new(block, data);
494 for block in vd.blocks() {
495 validate_hsv_curve_range(block, data);
496 }
497}
498
499fn validate_curve_range(block: &Block, data: &Everything) {
500 let mut vd = Validator::new(block, data);
501 let mut count = 0;
502 for token in vd.values() {
503 if let Some(v) = token.expect_number() {
504 count += 1;
505 #[allow(clippy::collapsible_else_if)]
506 if count == 1 {
507 if !(0.0..=1.0).contains(&v) {
508 let msg = "expected number from 0.0 to 1.0";
509 err(ErrorKey::Range).msg(msg).loc(token).push();
510 }
511 } else {
512 if !(-1.0..=1.0).contains(&v) {
513 let msg = "expected number from -1.0 to 1.0";
514 err(ErrorKey::Range).msg(msg).loc(token).push();
515 }
516 }
517 }
518 }
519 if count != 2 {
520 err(ErrorKey::Validation).msg("expected exactly 2 numbers").loc(block).push();
521 }
522}
523
524fn validate_hsv_curve_range(block: &Block, data: &Everything) {
525 let mut found_first = false;
526 let mut found_second = false;
527
528 for item in block.iter_items() {
529 if item.is_field() {
530 warn(ErrorKey::Validation).msg("unexpected key").loc(item).push();
531 } else if !found_first {
532 if let Some(token) = item.expect_value()
533 && let Some(v) = token.expect_number()
534 {
535 found_first = true;
536 if !(0.0..=1.0).contains(&v) {
537 let msg = "expected number from 0.0 to 1.0";
538 err(ErrorKey::Range).msg(msg).loc(token).push();
539 }
540 }
541 } else if !found_second && let Some(block) = item.expect_block() {
542 found_second = true;
543 let mut count = 0;
544 let mut vd = Validator::new(block, data);
545 for token in vd.values() {
546 if let Some(v) = token.expect_number() {
547 count += 1;
548 if !(-1.0..=1.0).contains(&v) {
549 let msg = "expected number from -1.0 to 1.0";
550 err(ErrorKey::Range).msg(msg).loc(token).push();
551 }
552 }
553 }
554 if count != 3 {
555 err(ErrorKey::Validation).msg("expected exactly 3 numbers").loc(block).push();
556 }
557 }
558 }
559}
560
561fn validate_morph_gene(block: &Block, data: &Everything) {
562 let mut vd = Validator::new(block, data);
563 vd.req_field("index");
564 vd.field_integer("index"); vd.field_bool("generic");
566 vd.field_bool("visible");
567 vd.field_value("positive_mirror"); vd.field_value("negative_mirror"); #[cfg(feature = "imperator")]
570 vd.field_value("set_tags");
571
572 for field in BODY_TYPES {
573 vd.field_validated(field, |bv, data| {
574 match bv {
575 BV::Value(token) => {
576 if !BODY_TYPES.contains(&token.as_str()) {
578 let msg = format!("expected one of {}", BODY_TYPES.join(", "));
579 warn(ErrorKey::Choice).msg(msg).loc(token).push();
580 }
581 }
582 BV::Block(block) => {
583 let mut vd = Validator::new(block, data);
584 vd.multi_field_validated_block("setting", validate_gene_setting);
585 #[cfg(any(feature = "ck3", feature = "vic3", feature = "eu5"))]
586 vd.multi_field_validated_block("decal", validate_gene_decal);
587 #[cfg(feature = "imperator")]
588 vd.multi_field_validated_block("decal", validate_gene_decal_imperator);
589 vd.multi_field_validated_block("texture_override", validate_texture_override);
590
591 if Game::is_imperator() {
592 vd.field_validated_block("hair_hsv_shift_curve", validate_hsv_curve);
593 vd.field_validated_block("eye_hsv_shift_curve", validate_hsv_curve);
594 vd.field_validated_block("skin_hsv_shift_curve", validate_hsv_curve);
595 } else {
596 vd.field_validated_block("hair_hsv_shift_curve", validate_shift_curve);
597 vd.field_validated_block("eye_hsv_shift_curve", validate_shift_curve);
598 vd.field_validated_block("skin_hsv_shift_curve", validate_shift_curve);
599 }
600 }
601 }
602 });
603 }
604}
605
606fn validate_accessory_gene(block: &Block, data: &Everything) {
607 let mut vd = Validator::new(block, data);
608 vd.req_field("index");
609 vd.field_integer("index"); vd.field_value("set_tags");
611 vd.field_bool("allow_game_entity_override"); for field in BODY_TYPES {
614 vd.field_validated(field, |bv, data| {
615 match bv {
616 BV::Value(token) => {
617 if !BODY_TYPES.contains(&token.as_str()) {
619 let msg = format!("expected one of {}", BODY_TYPES.join(", "));
620 warn(ErrorKey::Choice).msg(msg).loc(token).push();
621 }
622 }
623 BV::Block(block) => {
624 let mut vd = Validator::new(block, data);
625 vd.integer_keys(|_weight, bv| match bv {
626 BV::Value(token) => {
627 if !token.is("empty") && !token.is("0") {
628 data.verify_exists(Item::Accessory, token);
629 }
630 }
631 BV::Block(block) => {
632 for token in block.iter_values_warn() {
633 data.verify_exists(Item::Accessory, token);
634 }
635 }
636 });
637 }
638 }
639 });
640 }
641}
642
643fn validate_gene_setting(block: &Block, data: &Everything) {
644 let mut vd = Validator::new(block, data);
645 vd.req_field("attribute");
646 vd.req_field_one_of(&["value", "curve"]);
647 vd.field_item("attribute", Item::GeneAttribute);
648 vd.field_validated("value", |bv, data| match bv {
649 BV::Value(value) => {
650 value.expect_number();
651 }
652 BV::Block(block) => {
653 let mut vd = Validator::new(block, data);
654 vd.req_field("min");
655 vd.req_field("max");
656 vd.field_numeric("min");
657 vd.field_numeric("max");
658 }
659 });
660 vd.field_validated_block("curve", validate_curve);
661 #[cfg(feature = "imperator")]
662 vd.multi_field_validated_block("animation_curve", validate_curve);
663
664 vd.field_validated("age", validate_age_field);
665 if let Some(token) = vd.field_value("required_tags") {
666 for tag in token.split(',') {
667 if tag.starts_with("not(") {
668 let real_tag = &tag.split('(')[1].split(')')[0];
669 data.verify_exists(Item::AccessoryTag, real_tag);
670 } else {
671 data.verify_exists(Item::AccessoryTag, &tag);
672 }
673 }
674 }
675}
676
677#[cfg(any(feature = "ck3", feature = "vic3", feature = "eu5"))]
678fn validate_gene_decal(block: &Block, data: &Everything) {
679 let mut vd = Validator::new(block, data);
680 vd.req_field("body_part");
681 vd.req_field("textures");
682 #[cfg(any(feature = "ck3", feature = "vic3"))]
683 vd.req_field("priority");
684 vd.field_value("body_part"); vd.multi_field_validated_block("textures", validate_decal_textures);
686 vd.multi_field_validated_block("alpha_curve", validate_curve);
687 vd.multi_field_validated_block("uv_tiling", validate_uv_tiling);
688 vd.multi_field_validated_block("blend_modes", validate_blend_modes);
689 vd.field_integer("priority");
690 vd.field_validated("age", validate_age_field);
691 vd.field_choice("decal_apply_order", &["pre_skin_color", "post_skin_color"]);
692}
693
694#[cfg(feature = "imperator")]
695fn validate_gene_decal_imperator(block: &Block, data: &Everything) {
696 let mut vd = Validator::new(block, data);
697 vd.req_field("type");
698 vd.req_field("atlas_pos");
699 vd.field_choice("type", &["skin", "paint"]);
700 vd.field_list_integers_exactly("atlas_pos", 2);
701 vd.multi_field_validated_block("alpha_curve", validate_curve);
702}
703
704#[cfg(any(feature = "ck3", feature = "vic3", feature = "eu5"))]
705fn validate_decal_textures(block: &Block, data: &Everything) {
706 let mut vd = Validator::new(block, data);
707 vd.field_item("diffuse", Item::File);
709 vd.field_item("normal", Item::File);
710 vd.field_item("specular", Item::File);
711 vd.field_item("properties", Item::File);
712}
713
714fn validate_texture_override(block: &Block, data: &Everything) {
715 let mut vd = Validator::new(block, data);
716 vd.req_field("weight");
717 vd.field_integer("weight");
718 vd.field_item("diffuse", Item::File);
720 vd.field_item("normal", Item::File);
721 vd.field_item("specular", Item::File);
722 vd.field_item("properties", Item::File);
723}
724
725#[cfg(any(feature = "ck3", feature = "vic3", feature = "eu5"))]
726fn validate_blend_modes(block: &Block, data: &Everything) {
727 let mut vd = Validator::new(block, data);
728 let choices = &["overlay", "replace", "hard_light", "multiply"];
729 vd.field_choice("diffuse", choices);
730 vd.field_choice("normal", choices);
731 vd.field_choice("properties", choices);
732}
733
734#[cfg(any(feature = "ck3", feature = "vic3", feature = "eu5"))]
735fn validate_uv_tiling(block: &Block, data: &Everything) {
736 let mut vd = Validator::new(block, data);
737 vd.req_tokens_integers_exactly(2);
738}
739
740fn validate_shift_curve(block: &Block, data: &Everything) {
741 let mut vd = Validator::new(block, data);
742 vd.req_field("curve");
743 vd.field_validated_block("curve", validate_hsv_curve);
744 vd.field_validated("age", validate_age_field);
745}