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 if self.special_gene {
213 let msg = "adding a group to a gene under special_genes will make the ruler designer crash";
214 fatal(ErrorKey::Crash).msg(msg).loc(token).push();
215 }
216 }
217 vd.unknown_block_fields(|_, block| {
218 validate_morph_gene(block, data);
219 });
220 }
221
222 fn has_property(
223 &self,
224 _key: &Token,
225 _block: &Block,
226 property: &str,
227 _data: &Everything,
228 ) -> bool {
229 self.templates.contains(property)
230 }
231
232 fn validate_property_use(
233 &self,
234 _key: &Token,
235 block: &Block,
236 property: &Token,
237 caller: &str,
238 data: &Everything,
239 ) {
240 validate_portrait_modifier_use(block, data, property, caller);
241 }
242
243 fn validate_use(
244 &self,
245 _key: &Token,
246 _block: &Block,
247 data: &Everything,
248 call_key: &Token,
249 call_block: &Block,
250 ) {
251 let mut vd = Validator::new(call_block, data);
252 let mut count = 0;
253 for token in vd.values() {
254 if count % 2 == 0 {
255 if !token.is("") && !self.templates.contains(token) {
256 let msg = format!("Gene template {token} not found in category {call_key}");
257 err(ErrorKey::MissingItem).msg(msg).loc(token).push();
258 }
259 } else if let Some(i) = token.expect_integer() {
260 if !(0..=256).contains(&i) {
261 warn(ErrorKey::Range).msg("expected value from 0 to 256").loc(token).push();
262 }
263 }
264 count += 1;
265 if count > 4 {
266 let msg = "too many values in this gene";
267 err(ErrorKey::Validation).msg(msg).loc(token).push();
268 break;
269 }
270 }
271 if count < 4 {
272 let msg = "too few values in this gene";
273 err(ErrorKey::Validation).msg(msg).loc(call_block).push();
274 }
275 }
276
277 fn merge_in(&mut self, other: Box<dyn DbKind>) {
278 if let Some(other) = other.as_any().downcast_ref::<Self>() {
279 for key in &other.templates {
280 if let Some(already) = self.templates.get(key.as_str()) {
281 dup_error(key, already, "morph gene template");
282 }
283 self.templates.insert(key.clone());
284 }
285 }
286 }
287}
288
289fn validate_portrait_modifier_use(
290 block: &Block,
291 data: &Everything,
292 property: &Token,
293 caller: &str,
294) {
295 if let Some(block) = block.get_field_block(property.as_str()) {
297 for field in BODY_TYPES {
299 if let Some(block) = block.get_field_block(field) {
301 for (_, token) in block.iter_assignments() {
302 if token.is("empty") {
303 continue;
304 }
305 let loca = format!("PORTRAIT_MODIFIER_{caller}_{token}");
306 if !data.item_exists(Item::Localization, &loca) {
307 let msg = format!("missing localization key {loca}");
308 warn(ErrorKey::MissingLocalization)
309 .msg(msg)
310 .loc(property)
311 .loc_msg(token, "this setting")
312 .push();
313 }
314 }
315 }
316 }
317 }
318}
319
320#[derive(Clone, Debug)]
321pub struct AccessoryGene {
322 templates: TigerHashSet<Token>,
323}
324
325impl AccessoryGene {
326 pub fn add(db: &mut Db, key: Token, block: Block) {
327 let mut templates = TigerHashSet::default();
328 for (key, _) in block.iter_definitions() {
329 if key.is("ugliness_feature_categories") {
330 continue;
331 }
332 if let Some(other) = templates.get(key.as_str()) {
333 dup_error(key, other, "accessory gene template");
334 }
335 templates.insert(key.clone());
336 }
337 db.add(Item::GeneCategory, key, block, Box::new(Self { templates }));
338 }
339
340 pub fn has_template_setting(
341 _key: &Token,
342 block: &Block,
343 _data: &Everything,
344 template: &str,
345 setting: &str,
346 ) -> bool {
347 if template == "ugliness_feature_categories" {
348 return false;
349 }
350 if let Some(block) = block.get_field_block(template) {
351 for field in BODY_TYPES {
352 if let Some(block) = block.get_field_block(field) {
354 for (_, token) in block.iter_assignments() {
355 if token.is("empty") {
356 continue;
357 }
358 if token.is(setting) {
359 return true;
360 }
361 }
362 }
363 }
364 }
365 false
366 }
367}
368
369impl DbKind for AccessoryGene {
370 #[cfg(feature = "ck3")]
371 fn add_subitems(&self, _key: &Token, block: &Block, db: &mut Db) {
372 for (key, block) in block.iter_definitions() {
373 if key.is("ugliness_feature_categories") {
374 continue;
375 }
376
377 if let Some(tags) = block.get_field_value("set_tags") {
378 for tag in tags.split(',') {
379 db.add_flag(Item::AccessoryTag, tag);
380 }
381 }
382 }
383 }
384
385 fn validate(&self, _key: &Token, block: &Block, data: &Everything) {
386 let mut vd = Validator::new(block, data);
387
388 vd.field_bool("inheritable");
389 vd.field_value("group");
390
391 if Game::is_imperator() {
392 vd.req_field("index");
393 vd.field_integer("index");
394 }
395
396 vd.unknown_block_fields(|_, block| {
397 validate_accessory_gene(block, data);
398 });
399 }
400
401 fn has_property(
402 &self,
403 _key: &Token,
404 _block: &Block,
405 property: &str,
406 _data: &Everything,
407 ) -> bool {
408 self.templates.contains(property)
409 }
410
411 fn validate_property_use(
412 &self,
413 _key: &Token,
414 block: &Block,
415 property: &Token,
416 caller: &str,
417 data: &Everything,
418 ) {
419 validate_portrait_modifier_use(block, data, property, caller);
420 }
421
422 fn validate_use(
423 &self,
424 _key: &Token,
425 _block: &Block,
426 data: &Everything,
427 call_key: &Token,
428 call_block: &Block,
429 ) {
430 let mut vd = Validator::new(call_block, data);
431 let mut count = 0;
432 for token in vd.values() {
433 if count % 2 == 0 {
434 if !token.is("") && !self.templates.contains(token) {
435 let msg = format!("Gene template {token} not found in category {call_key}");
436 err(ErrorKey::MissingItem).msg(msg).loc(token).push();
437 }
438 } else if let Some(i) = token.expect_integer() {
439 if !(0..=256).contains(&i) {
440 warn(ErrorKey::Range).msg("expected value from 0 to 256").loc(token).push();
441 }
442 }
443 count += 1;
444 if count > 4 {
445 let msg = "too many values in this gene";
446 err(ErrorKey::Validation).msg(msg).loc(token).push();
447 break;
448 }
449 }
450 if count < 4 {
451 let msg = "too few values in this gene";
452 err(ErrorKey::Validation).msg(msg).loc(call_block).push();
453 }
454 }
455
456 fn merge_in(&mut self, other: Box<dyn DbKind>) {
457 if let Some(other) = other.as_any().downcast_ref::<Self>() {
458 for key in &other.templates {
459 if let Some(already) = self.templates.get(key.as_str()) {
460 dup_error(key, already, "morph gene template");
461 }
462 self.templates.insert(key.clone());
463 }
464 }
465 }
466}
467
468fn validate_age_field(bv: &BV, data: &Everything) {
469 match bv {
470 BV::Value(token) => data.verify_exists(Item::GeneAgePreset, token),
471 BV::Block(block) => validate_age(block, data),
472 }
473}
474
475fn validate_age(block: &Block, data: &Everything) {
476 let mut vd = Validator::new(block, data);
477 vd.req_field("mode");
478 vd.req_field("curve");
479
480 vd.field_value("mode"); vd.field_validated_block("curve", validate_curve);
482}
483
484fn validate_curve(block: &Block, data: &Everything) {
485 let mut vd = Validator::new(block, data);
486 for block in vd.blocks() {
487 validate_curve_range(block, data);
488 }
489}
490
491fn validate_hsv_curve(block: &Block, data: &Everything) {
492 let mut vd = Validator::new(block, data);
493 for block in vd.blocks() {
494 validate_hsv_curve_range(block, data);
495 }
496}
497
498fn validate_curve_range(block: &Block, data: &Everything) {
499 let mut vd = Validator::new(block, data);
500 let mut count = 0;
501 for token in vd.values() {
502 if let Some(v) = token.expect_number() {
503 count += 1;
504 #[allow(clippy::collapsible_else_if)]
505 if count == 1 {
506 if !(0.0..=1.0).contains(&v) {
507 let msg = "expected number from 0.0 to 1.0";
508 err(ErrorKey::Range).msg(msg).loc(token).push();
509 }
510 } else {
511 if !(-1.0..=1.0).contains(&v) {
512 let msg = "expected number from -1.0 to 1.0";
513 err(ErrorKey::Range).msg(msg).loc(token).push();
514 }
515 }
516 }
517 }
518 if count != 2 {
519 err(ErrorKey::Validation).msg("expected exactly 2 numbers").loc(block).push();
520 }
521}
522
523fn validate_hsv_curve_range(block: &Block, data: &Everything) {
524 let mut found_first = false;
525 let mut found_second = false;
526
527 for item in block.iter_items() {
528 if item.is_field() {
529 warn(ErrorKey::Validation).msg("unexpected key").loc(item).push();
530 } else if !found_first {
531 if let Some(token) = item.expect_value() {
532 if let Some(v) = token.expect_number() {
533 found_first = true;
534 if !(0.0..=1.0).contains(&v) {
535 let msg = "expected number from 0.0 to 1.0";
536 err(ErrorKey::Range).msg(msg).loc(token).push();
537 }
538 }
539 }
540 } else if !found_second {
541 if 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}
561
562fn validate_morph_gene(block: &Block, data: &Everything) {
563 let mut vd = Validator::new(block, data);
564 vd.req_field("index");
565 vd.field_integer("index"); vd.field_bool("generic");
567 vd.field_bool("visible");
568 vd.field_value("positive_mirror"); vd.field_value("negative_mirror"); #[cfg(feature = "imperator")]
571 vd.field_value("set_tags");
572
573 for field in BODY_TYPES {
574 vd.field_validated(field, |bv, data| {
575 match bv {
576 BV::Value(token) => {
577 if !BODY_TYPES.contains(&token.as_str()) {
579 let msg = format!("expected one of {}", BODY_TYPES.join(", "));
580 warn(ErrorKey::Choice).msg(msg).loc(token).push();
581 }
582 }
583 BV::Block(block) => {
584 let mut vd = Validator::new(block, data);
585 vd.multi_field_validated_block("setting", validate_gene_setting);
586 #[cfg(any(feature = "ck3", feature = "vic3", feature = "eu5"))]
587 vd.multi_field_validated_block("decal", validate_gene_decal);
588 #[cfg(feature = "imperator")]
589 vd.multi_field_validated_block("decal", validate_gene_decal_imperator);
590 vd.multi_field_validated_block("texture_override", validate_texture_override);
591
592 if Game::is_imperator() {
593 vd.field_validated_block("hair_hsv_shift_curve", validate_hsv_curve);
594 vd.field_validated_block("eye_hsv_shift_curve", validate_hsv_curve);
595 vd.field_validated_block("skin_hsv_shift_curve", validate_hsv_curve);
596 } else {
597 vd.field_validated_block("hair_hsv_shift_curve", validate_shift_curve);
598 vd.field_validated_block("eye_hsv_shift_curve", validate_shift_curve);
599 vd.field_validated_block("skin_hsv_shift_curve", validate_shift_curve);
600 }
601 }
602 }
603 });
604 }
605}
606
607fn validate_accessory_gene(block: &Block, data: &Everything) {
608 let mut vd = Validator::new(block, data);
609 vd.req_field("index");
610 vd.field_integer("index"); vd.field_value("set_tags");
612 vd.field_bool("allow_game_entity_override"); for field in BODY_TYPES {
615 vd.field_validated(field, |bv, data| {
616 match bv {
617 BV::Value(token) => {
618 if !BODY_TYPES.contains(&token.as_str()) {
620 let msg = format!("expected one of {}", BODY_TYPES.join(", "));
621 warn(ErrorKey::Choice).msg(msg).loc(token).push();
622 }
623 }
624 BV::Block(block) => {
625 let mut vd = Validator::new(block, data);
626 vd.integer_keys(|_weight, bv| match bv {
627 BV::Value(token) => {
628 if !token.is("empty") && !token.is("0") {
629 data.verify_exists(Item::Accessory, token);
630 }
631 }
632 BV::Block(block) => {
633 for token in block.iter_values_warn() {
634 data.verify_exists(Item::Accessory, token);
635 }
636 }
637 });
638 }
639 }
640 });
641 }
642}
643
644fn validate_gene_setting(block: &Block, data: &Everything) {
645 let mut vd = Validator::new(block, data);
646 vd.req_field("attribute");
647 vd.req_field_one_of(&["value", "curve"]);
648 vd.field_item("attribute", Item::GeneAttribute);
649 vd.field_validated("value", |bv, data| match bv {
650 BV::Value(value) => {
651 value.expect_number();
652 }
653 BV::Block(block) => {
654 let mut vd = Validator::new(block, data);
655 vd.req_field("min");
656 vd.req_field("max");
657 vd.field_numeric("min");
658 vd.field_numeric("max");
659 }
660 });
661 vd.field_validated_block("curve", validate_curve);
662 #[cfg(feature = "imperator")]
663 vd.multi_field_validated_block("animation_curve", validate_curve);
664
665 vd.field_validated("age", validate_age_field);
666 if let Some(token) = vd.field_value("required_tags") {
667 for tag in token.split(',') {
668 if tag.starts_with("not(") {
669 let real_tag = &tag.split('(')[1].split(')')[0];
670 data.verify_exists(Item::AccessoryTag, real_tag);
671 } else {
672 data.verify_exists(Item::AccessoryTag, &tag);
673 }
674 }
675 }
676}
677
678#[cfg(any(feature = "ck3", feature = "vic3", feature = "eu5"))]
679fn validate_gene_decal(block: &Block, data: &Everything) {
680 let mut vd = Validator::new(block, data);
681 vd.req_field("body_part");
682 vd.req_field("textures");
683 #[cfg(any(feature = "ck3", feature = "vic3"))]
684 vd.req_field("priority");
685 vd.field_value("body_part"); vd.multi_field_validated_block("textures", validate_decal_textures);
687 vd.multi_field_validated_block("alpha_curve", validate_curve);
688 vd.multi_field_validated_block("uv_tiling", validate_uv_tiling);
689 vd.multi_field_validated_block("blend_modes", validate_blend_modes);
690 vd.field_integer("priority");
691 vd.field_validated("age", validate_age_field);
692 vd.field_choice("decal_apply_order", &["pre_skin_color", "post_skin_color"]);
693}
694
695#[cfg(feature = "imperator")]
696fn validate_gene_decal_imperator(block: &Block, data: &Everything) {
697 let mut vd = Validator::new(block, data);
698 vd.req_field("type");
699 vd.req_field("atlas_pos");
700 vd.field_choice("type", &["skin", "paint"]);
701 vd.field_list_integers_exactly("atlas_pos", 2);
702 vd.multi_field_validated_block("alpha_curve", validate_curve);
703}
704
705#[cfg(any(feature = "ck3", feature = "vic3", feature = "eu5"))]
706fn validate_decal_textures(block: &Block, data: &Everything) {
707 let mut vd = Validator::new(block, data);
708 vd.field_item("diffuse", Item::File);
710 vd.field_item("normal", Item::File);
711 vd.field_item("specular", Item::File);
712 vd.field_item("properties", Item::File);
713}
714
715fn validate_texture_override(block: &Block, data: &Everything) {
716 let mut vd = Validator::new(block, data);
717 vd.req_field("weight");
718 vd.field_integer("weight");
719 vd.field_item("diffuse", Item::File);
721 vd.field_item("normal", Item::File);
722 vd.field_item("specular", Item::File);
723 vd.field_item("properties", Item::File);
724}
725
726#[cfg(any(feature = "ck3", feature = "vic3", feature = "eu5"))]
727fn validate_blend_modes(block: &Block, data: &Everything) {
728 let mut vd = Validator::new(block, data);
729 let choices = &["overlay", "replace", "hard_light", "multiply"];
730 vd.field_choice("diffuse", choices);
731 vd.field_choice("normal", choices);
732 vd.field_choice("properties", choices);
733}
734
735#[cfg(any(feature = "ck3", feature = "vic3", feature = "eu5"))]
736fn validate_uv_tiling(block: &Block, data: &Everything) {
737 let mut vd = Validator::new(block, data);
738 vd.req_tokens_integers_exactly(2);
739}
740
741fn validate_shift_curve(block: &Block, data: &Everything) {
742 let mut vd = Validator::new(block, data);
743 vd.req_field("curve");
744 vd.field_validated_block("curve", validate_hsv_curve);
745 vd.field_validated("age", validate_age_field);
746}