tiger_lib/ck3/data/
characters.rs

1//! Registry and validation of all characters defined in history/characters/
2
3use std::fmt::{Display, Formatter};
4use std::path::PathBuf;
5use std::str::FromStr;
6use std::sync::atomic::Ordering;
7
8use atomic_enum::atomic_enum;
9
10use crate::block::{BV, Block, Comparator, Eq::*};
11use crate::ck3::data::houses::House;
12use crate::ck3::validate::validate_portrait_modifier_overrides;
13use crate::context::ScopeContext;
14use crate::date::Date;
15use crate::effect::{validate_effect, validate_effect_field};
16use crate::everything::Everything;
17use crate::fileset::{FileEntry, FileHandler};
18use crate::helpers::{TigerHashMap, TigerHashSet};
19use crate::item::Item;
20use crate::lowercase::Lowercase;
21use crate::parse::ParserMemory;
22use crate::pdxfile::PdxFile;
23use crate::report::{ErrorKey, Severity, err, fatal, untidy, warn};
24use crate::scopes::Scopes;
25use crate::special_tokens::SpecialTokens;
26use crate::token::Token;
27use crate::tooltipped::Tooltipped;
28use crate::validate::validate_color;
29use crate::validator::Validator;
30use crate::variables::Variables;
31
32#[derive(Copy, Clone, Debug, Eq, PartialEq)]
33pub enum Gender {
34    Male,
35    Female,
36}
37
38impl Gender {
39    fn from_female_bool(b: bool) -> Self {
40        if b { Gender::Female } else { Gender::Male }
41    }
42
43    fn flip(self) -> Self {
44        match self {
45            Gender::Male => Gender::Female,
46            Gender::Female => Gender::Male,
47        }
48    }
49}
50
51impl Display for Gender {
52    fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
53        match *self {
54            Gender::Male => write!(f, "male"),
55            Gender::Female => write!(f, "female"),
56        }
57    }
58}
59
60#[derive(Debug, Default)]
61#[allow(clippy::struct_field_names)]
62pub struct Characters {
63    config_only_born: Option<Date>,
64
65    characters: TigerHashMap<&'static str, Character>,
66
67    /// These are characters with duplicate ids. We can't put them in the `characters` map because of the ids,
68    /// but we do want to validate them.
69    duplicates: Vec<Character>,
70}
71
72impl Characters {
73    fn load_item(&mut self, key: Token, block: Block) {
74        if let Some(other) = self.characters.get(key.as_str()) {
75            if self.config_only_born.is_none()
76                || self
77                    .config_only_born
78                    .and_then(|date| block.get_field_at_date("birth", date))
79                    .is_some()
80            {
81                err(ErrorKey::DuplicateCharacter)
82                    .strong()
83                    .msg("duplicate character id")
84                    .info("this will create two characters with the same id")
85                    .loc(&other.key)
86                    .loc_msg(&key, "duplicate")
87                    .push();
88                self.duplicates.push(Character::new(key, block));
89            }
90        } else {
91            self.characters.insert(key.as_str(), Character::new(key, block));
92        }
93    }
94
95    pub fn verify_exists_gender(&self, item: &Token, gender: Gender) {
96        if let Some(ch) = self.characters.get(item.as_str()) {
97            if gender != ch.gender() {
98                let msg = format!("character is not {gender}");
99                err(ErrorKey::WrongGender).msg(msg).loc(item).push();
100            }
101        } else {
102            let msg = format!("character {item} not defined in history/characters/");
103            err(ErrorKey::MissingItem).msg(msg).loc(item).push();
104        }
105    }
106
107    pub fn scan_variables(&self, registry: &mut Variables) {
108        for item in self.characters.values() {
109            registry.scan(&item.block);
110        }
111        for item in &self.duplicates {
112            registry.scan(&item.block);
113        }
114    }
115
116    pub fn exists(&self, key: &str) -> bool {
117        self.characters.contains_key(key)
118    }
119
120    pub fn iter_keys(&self) -> impl Iterator<Item = &Token> {
121        self.characters.values().map(|ch| &ch.key).chain(self.duplicates.iter().map(|ch| &ch.key))
122    }
123
124    pub fn is_alive(&self, item: &Token, date: Date) -> bool {
125        if let Some(item) = self.characters.get(item.as_str()) {
126            item.is_alive(date)
127        } else {
128            false
129        }
130    }
131
132    pub fn verify_alive(&self, item: &Token, date: Date) {
133        if !self.is_alive(item, date) {
134            let msg = format!("{item} is not alive on {date}");
135            warn(ErrorKey::History).msg(msg).loc(item).push();
136        }
137    }
138
139    pub fn get_dynasty<'a>(
140        &'a self,
141        id: &Token,
142        date: Date,
143        data: &'a Everything,
144    ) -> Option<&'a Token> {
145        self.characters.get(id.as_str()).and_then(|ch| {
146            ch.get_dynasty(date).or_else(|| {
147                ch.get_house(date).and_then(|house| House::get_dynasty(house.as_str(), data))
148            })
149        })
150    }
151
152    pub fn get_house(&self, id: &Token, date: Date) -> Option<&Token> {
153        self.characters.get(id.as_str()).and_then(|ch| ch.get_house(date))
154    }
155
156    pub fn get_culture(&self, id: &Token, date: Date) -> Option<&Token> {
157        self.characters.get(id.as_str()).and_then(|ch| ch.get_culture(date))
158    }
159
160    pub fn get_faith(&self, id: &Token, date: Date) -> Option<&Token> {
161        self.characters.get(id.as_str()).and_then(|ch| ch.get_faith(date))
162    }
163
164    pub fn validate(&self, data: &Everything) {
165        for item in self.characters.values() {
166            if item.born_by(self.config_only_born) {
167                item.validate(data);
168            }
169        }
170        for item in &self.duplicates {
171            if item.born_by(self.config_only_born) {
172                item.validate(data);
173            }
174        }
175    }
176
177    // Recursive depth-first search of ancestor graph to see if there are any cycles.
178    // If a cycle is found, print a warning about it that includes all the characters in the cycle.
179    //
180    // If there are multiple cycles, this algorithm might not find them all, because that's an
181    // NP-hard problem. Fortunately the user can just re-run after fixing the known cycles.
182    //
183    // Returns None if `ch` is not part of any cycles.
184    // Returns a vector of `Token` if `ch` is part of a cycle.
185    fn check_ancestor_cycles(&self, ch: &Character) -> Option<Vec<Token>> {
186        match ch.ancestor_state.load(Ordering::Acquire) {
187            AncestorState::Unchecked => {
188                ch.ancestor_state.store(AncestorState::Checking, Ordering::Release);
189            }
190            AncestorState::Checking => {
191                // Found a cycle
192                return Some(vec![ch.key.clone()]);
193            }
194            AncestorState::Checked => {
195                // Reached a char that is known not to be part of any cycles
196                // (or is part of a cycle that has already been reported)
197                return None;
198            }
199        }
200
201        for field in &["father", "mother"] {
202            if let Some(token) = ch.block.get_field_value(field) {
203                if let Some(parent) = self.characters.get(token.as_str()) {
204                    if let Some(mut cycle_vec) = self.check_ancestor_cycles(parent) {
205                        // unwrap is safe because the vec is always created with one element
206                        if &ch.key == cycle_vec.first().unwrap() {
207                            let msg = "character is their own ancestor";
208                            cycle_vec.reverse();
209                            cycle_vec.pop();
210                            let mut report = fatal(ErrorKey::Crash).msg(msg).loc(&ch.key);
211                            for token in cycle_vec {
212                                report = report.loc_msg(token, "from here");
213                            }
214                            report.push();
215                        } else {
216                            cycle_vec.push(token.clone());
217                            // Returning here means if the father is in a cycle we won't check the
218                            // mother, but that's ok because we're not promising to find all
219                            // cycles anyway.
220                            ch.ancestor_state.store(AncestorState::Checked, Ordering::Release);
221                            return Some(cycle_vec);
222                        }
223                    }
224                }
225            }
226        }
227        ch.ancestor_state.store(AncestorState::Checked, Ordering::Release);
228        None
229    }
230
231    pub fn check_pod_flags(&self, data: &Everything) {
232        for item in self.characters.values() {
233            if item.born_by(self.config_only_born) {
234                item.check_pod_flags(data);
235            }
236        }
237    }
238}
239
240impl FileHandler<Block> for Characters {
241    fn config(&mut self, config: &Block) {
242        if let Some(block) = config.get_field_block("characters") {
243            if let Some(born) = block.get_field_value("only_born") {
244                if let Ok(date) = Date::try_from(born) {
245                    self.config_only_born = Some(date);
246                }
247            }
248        }
249    }
250
251    fn subpath(&self) -> PathBuf {
252        PathBuf::from("history/characters")
253    }
254
255    fn load_file(&self, entry: &FileEntry, parser: &ParserMemory) -> Option<Block> {
256        if !entry.filename().to_string_lossy().ends_with(".txt") {
257            return None;
258        }
259
260        PdxFile::read(entry, parser)
261    }
262
263    fn handle_file(&mut self, _entry: &FileEntry, mut block: Block) {
264        for (key, block) in block.drain_definitions_warn() {
265            self.load_item(key, block);
266        }
267    }
268
269    fn finalize(&mut self) {
270        // Find loops in the ancestry tree. These will crash the game.
271        for item in self.characters.values() {
272            let opt_cycle_vec = self.check_ancestor_cycles(item);
273            // It shouldn't be possible that a cycle vec is passed back beyond the first character.
274            assert!(opt_cycle_vec.is_none());
275        }
276    }
277}
278
279#[atomic_enum]
280enum AncestorState {
281    Unchecked,
282    Checking,
283    Checked,
284}
285
286#[derive(Debug)]
287pub struct Character {
288    key: Token,
289    block: Block,
290    /// Storage for the ancestor cycle check algorithm.
291    ancestor_state: AtomicAncestorState,
292}
293
294impl Character {
295    pub fn new(key: Token, block: Block) -> Self {
296        Self { key, block, ancestor_state: AtomicAncestorState::new(AncestorState::Unchecked) }
297    }
298
299    pub fn born_by(&self, born_by: Option<Date>) -> bool {
300        if let Some(date) = born_by {
301            self.block.get_field_at_date("birth", date).is_some()
302        } else {
303            true
304        }
305    }
306
307    pub fn gender(&self) -> Gender {
308        Gender::from_female_bool(self.block.get_field_bool("female").unwrap_or(false))
309    }
310
311    pub fn is_alive(&self, date: Date) -> bool {
312        // TODO: figure out if we need to account for deaths triggered in effect { } blocks
313        self.block.get_field_at_date("birth", date).is_some()
314            && self.block.get_field_at_date("death", date).is_none()
315    }
316
317    pub fn get_dynasty(&self, date: Date) -> Option<&Token> {
318        self.block.get_field_value_at_date("dynasty", date)
319    }
320
321    pub fn get_house(&self, date: Date) -> Option<&Token> {
322        self.block.get_field_value_at_date("dynasty_house", date)
323    }
324
325    pub fn get_culture(&self, date: Date) -> Option<&Token> {
326        self.block.get_field_value_at_date("culture", date)
327    }
328
329    pub fn get_faith(&self, date: Date) -> Option<&Token> {
330        self.block
331            .get_field_value_at_date("faith", date)
332            .or_else(|| self.block.get_field_value_at_date("religion", date))
333    }
334
335    fn validate_life_event(
336        date: Date,
337        gender: Gender,
338        key: &Token,
339        bv: &BV,
340        data: &Everything,
341        sc: &mut ScopeContext,
342    ) -> Option<(LifeEventType, Token)> {
343        use LifeEventType::*;
344
345        match bv {
346            BV::Value(value) => {
347                if matches!(key.as_str(), "trait" | "add_trait") && value.as_str() == "saint" {
348                    return Some((Posthumous, value.clone()));
349                }
350
351                match key.as_str() {
352                    "name" => {
353                        data.localization.verify_name_exists(value, Severity::Warning);
354                        return None;
355                    }
356                    "birth" => {
357                        if !value.is("yes") && Date::from_str(value.as_str()).is_err() {
358                            let msg = "expected `yes` or a date";
359                            err(ErrorKey::Validation).msg(msg).loc(value).push();
360                        }
361                        return Some((Birth, key.clone()));
362                    }
363                    "death" => {
364                        if !value.is("yes") && !value.is_date() {
365                            data.verify_exists(Item::DeathReason, value);
366                        }
367                        return Some((Death, key.clone()));
368                    }
369                    // religion and faith both mean faith here
370                    "religion" | "faith" => {
371                        data.verify_exists(Item::Faith, value);
372                        return None;
373                    }
374                    "culture" => {
375                        data.verify_exists(Item::Culture, value);
376                        return None;
377                    }
378                    "trait" => {
379                        data.verify_exists(Item::Trait, value);
380                        return None;
381                    }
382                    "employer" => {
383                        if value.is("0") {
384                            return Some((Unemployed, key.clone()));
385                        }
386                        data.verify_exists(Item::Character, value);
387                        if data.item_exists(Item::Character, value.as_str()) {
388                            data.characters.verify_alive(value, date);
389                        }
390                        return Some((Employed, key.clone()));
391                    }
392                    "moved_to_pool" => {
393                        if !value.is("yes") {
394                            let msg = "expected `yes`";
395                            err(ErrorKey::Validation).msg(msg).loc(value).push();
396                        }
397                        return Some((Unemployed, key.clone()));
398                    }
399                    "give_council_position" => {
400                        data.verify_exists(Item::CouncilPosition, value);
401                        return None;
402                    }
403                    "capital" => {
404                        data.verify_exists(Item::Title, value);
405                        if !value.as_str().starts_with("c_") {
406                            err(ErrorKey::Validation)
407                                .msg("capital must be a county")
408                                .loc(value)
409                                .push();
410                        }
411                        return None;
412                    }
413                    "add_spouse" | "add_matrilineal_spouse" => {
414                        data.characters.verify_exists_gender(value, gender.flip());
415                        if data.item_exists(Item::Character, value.as_str()) {
416                            data.characters.verify_alive(value, date);
417                        }
418                        return Some((AddSpouse, value.clone()));
419                    }
420                    "add_same_sex_spouse" => {
421                        data.characters.verify_exists_gender(value, gender);
422                        if data.item_exists(Item::Character, value.as_str()) {
423                            data.characters.verify_alive(value, date);
424                        }
425                        return Some((AddSpouse, value.clone()));
426                    }
427                    "add_concubine" => {
428                        data.characters.verify_exists_gender(value, gender.flip());
429                        if data.item_exists(Item::Character, value.as_str()) {
430                            data.characters.verify_alive(value, date);
431                        }
432                        return None;
433                    }
434                    "remove_spouse" => return Some((RemoveSpouse, value.clone())),
435                    "dynasty" => {
436                        data.verify_exists(Item::Dynasty, value);
437                        return None;
438                    }
439                    "dynasty_house" => {
440                        data.verify_exists(Item::House, value);
441                        return None;
442                    }
443                    _ => (),
444                }
445            }
446            BV::Block(block) => match key.as_str() {
447                "death" => {
448                    let mut vd = Validator::new(block, data);
449                    vd.req_field("death_reason");
450                    vd.field_item("death_reason", Item::DeathReason);
451                    vd.field_item("killer", Item::Character);
452                    return Some((Death, key.clone()));
453                }
454                "effect" => {
455                    validate_effect(block, data, sc, Tooltipped::No);
456                    return None;
457                }
458                _ => (),
459            },
460        }
461
462        // unknown effect field
463        validate_effect_field(
464            Lowercase::empty(),
465            key,
466            Comparator::Equals(Single),
467            bv,
468            data,
469            sc,
470            Tooltipped::No,
471            &mut SpecialTokens::none(),
472        );
473
474        None
475    }
476
477    fn validate_life(character: &Token, life_events: Vec<LifeEvent>) {
478        let mut birth = None;
479        let mut death = None;
480        let mut spouses = TigerHashSet::<Token>::default();
481        let mut employed = false;
482
483        for LifeEvent { date, index: _, token, event } in life_events {
484            use LifeEventType::*;
485
486            if birth.is_none() && event != Birth {
487                let msg = format!("{character} was not born yet on {date}");
488                let mut loc = token.loc;
489                loc.column = 0;
490                warn(ErrorKey::History).msg(msg).loc(loc).push();
491            }
492
493            if let Some((death_date, death_loc)) = death {
494                if event != Posthumous {
495                    let msg = format!(
496                        "{character} was not alive on {date}, had already died on {death_date}"
497                    );
498                    let mut loc = token.loc;
499                    loc.column = 0;
500                    warn(ErrorKey::History)
501                        .msg(msg)
502                        .loc(loc)
503                        .loc_msg(death_loc, "from here")
504                        .push();
505                }
506            }
507
508            match event {
509                Birth => {
510                    let mut loc = token.loc;
511                    loc.column = 0;
512
513                    if let Some((birth_date, birth_loc)) = birth {
514                        let msg = format!(
515                            "{character} couldn't be born again on {date}, was born already on {birth_date}"
516                        );
517                        warn(ErrorKey::History)
518                            .msg(msg)
519                            .loc(loc)
520                            .loc_msg(birth_loc, "from here")
521                            .push();
522                    }
523                    birth = Some((date, loc));
524                }
525                AddSpouse => {
526                    if !spouses.insert(token.clone()) {
527                        let msg = format!("{character} already had {token} as a spouse on {date}");
528                        let curr_token = spouses.get(&token).unwrap();
529                        warn(ErrorKey::History)
530                            .msg(msg)
531                            .loc(token)
532                            .loc_msg(curr_token, "from here")
533                            .push();
534                    }
535                }
536                RemoveSpouse => {
537                    if !spouses.remove(&token) {
538                        let msg = format!("{character} did not have {token} as a spouse on {date}");
539                        warn(ErrorKey::History).msg(msg).loc(token).push();
540                    }
541                }
542                Employed => employed = true,
543                Unemployed => {
544                    if !employed {
545                        let msg = format!("{character} was unemployed anyway on {date}");
546                        untidy(ErrorKey::History).msg(msg).loc(token).push();
547                    }
548                    employed = false;
549                }
550                Death => {
551                    let mut loc = token.loc;
552                    loc.column = 0;
553                    death = Some((date, loc));
554                }
555                Posthumous => {
556                    if death.is_none() {
557                        let msg = format!("{character} had not died yet on {date}");
558                        warn(ErrorKey::History).msg(msg).loc(token).push();
559                    }
560                }
561            }
562        }
563    }
564
565    fn validate(&self, data: &Everything) {
566        let mut vd = Validator::new(&self.block, data);
567        let mut sc = ScopeContext::new(Scopes::Character, &self.key);
568
569        if self.key.as_str().contains('.') {
570            let msg =
571                format!("`character:{}` will not work because of the dot in the id", &self.key);
572            let info = "script code will not be able to refer to this character";
573            warn(ErrorKey::CharacterId).msg(msg).info(info).loc(&self.key).push();
574        }
575
576        vd.req_field("name");
577        if let Some(name) = vd.field_value("name") {
578            data.localization.verify_name_exists(name, Severity::Warning);
579        }
580
581        vd.field_item("dna", Item::Dna);
582        vd.field_bool("female");
583        vd.field_integer("martial");
584        vd.field_integer("prowess");
585        vd.field_integer("diplomacy");
586        vd.field_integer("intrigue");
587        vd.field_integer("stewardship");
588        vd.field_integer("learning");
589        vd.multi_field_item("trait", Item::Trait);
590
591        if let Some(ch) = vd.field_value("father") {
592            data.characters.verify_exists_gender(ch, Gender::Male);
593        }
594
595        if let Some(ch) = vd.field_value("mother") {
596            data.characters.verify_exists_gender(ch, Gender::Female);
597        }
598
599        vd.field_bool("disallow_random_traits");
600
601        // religion and faith both mean faith here
602        vd.field_item("religion", Item::Faith);
603        vd.field_item("faith", Item::Faith);
604
605        vd.field_item("culture", Item::Culture);
606
607        vd.field_item("dynasty", Item::Dynasty);
608        vd.field_item("dynasty_house", Item::House);
609
610        vd.field_item("give_nickname", Item::Nickname);
611        vd.field_item("sexuality", Item::Sexuality);
612        vd.field_numeric("health");
613        vd.field_numeric("fertility");
614
615        vd.field_validated_block("portrait_override", |block, data| {
616            let mut vd = Validator::new(block, data);
617            vd.field_validated_block(
618                "portrait_modifier_overrides",
619                validate_portrait_modifier_overrides,
620            );
621            vd.field_validated_block("hair", validate_color);
622        });
623
624        let mut life_events = Vec::new();
625        let gender = Gender::from_female_bool(self.block.get_field_bool("female").unwrap_or(false));
626        vd.validate_history_blocks(|date, _key, block, data| {
627            for (index, (key, bv)) in block.iter_assignments_and_definitions_warn().enumerate() {
628                if let Some((life_event_type, token)) =
629                    Self::validate_life_event(date, gender, key, bv, data, &mut sc)
630                {
631                    life_events.push(LifeEvent { date, index, event: life_event_type, token });
632                }
633            }
634        });
635
636        life_events.sort_unstable();
637        Self::validate_life(&self.key, life_events);
638    }
639
640    fn check_pod_flags(&self, _data: &Everything) {
641        if self.block.has_key("dna")
642            && self.has_trait("nosferatu")
643            && !self.has_flag("had_POD_character_nosferatu_looks")
644            && !self.key.is("791762")
645        {
646            let msg = "nosferatu with predefined dna lacks had_POD_character_nosferatu_looks";
647            err(ErrorKey::PrincesOfDarkness).msg(msg).loc(&self.key).push();
648        }
649    }
650
651    fn has_flag(&self, flag: &str) -> bool {
652        for (key, block) in self.block.iter_definitions() {
653            if key.is_date() {
654                if block_has_flag(block, flag) {
655                    return true;
656                }
657                for block in block.get_field_blocks("effect") {
658                    if block_has_flag(block, flag) {
659                        return true;
660                    }
661                }
662            }
663        }
664        false
665    }
666
667    fn has_trait(&self, tr: &str) -> bool {
668        for token in self.block.get_field_values("trait") {
669            if token.is(tr) {
670                return true;
671            }
672        }
673        for (key, block) in self.block.iter_definitions() {
674            if key.is_date() {
675                for token in block.get_field_values("add_trait") {
676                    if token.is(tr) {
677                        return true;
678                    }
679                }
680                if let Some(block) = block.get_field_block("effect") {
681                    for token in block.get_field_values("add_trait") {
682                        if token.is(tr) {
683                            return true;
684                        }
685                    }
686                }
687            }
688        }
689        false
690    }
691}
692
693fn block_has_flag(block: &Block, flag: &str) -> bool {
694    for token in block.get_field_values("add_character_flag") {
695        if token.is(flag) {
696            return true;
697        }
698    }
699    for block in block.get_field_blocks("add_character_flag") {
700        if block.field_value_is("flag", flag) {
701            return true;
702        }
703    }
704    false
705}
706
707#[derive(Debug)]
708enum LifeEventType {
709    /// All other events must happen after birth
710    Birth,
711    AddSpouse,
712    RemoveSpouse,
713    Employed,
714    /// Must be employed already
715    Unemployed,
716    /// All other events must happen before death
717    Death,
718    Posthumous,
719    // TODO add Effect validation, e.g. `add_trait`
720}
721
722impl PartialEq for LifeEventType {
723    fn eq(&self, other: &Self) -> bool {
724        std::mem::discriminant(self) == std::mem::discriminant(other)
725    }
726}
727
728impl Eq for LifeEventType {}
729
730#[derive(Debug)]
731struct LifeEvent {
732    date: Date,
733    index: usize,
734    event: LifeEventType,
735    token: Token,
736}
737
738impl PartialEq for LifeEvent {
739    fn eq(&self, other: &Self) -> bool {
740        self.date == other.date && self.index == other.index
741    }
742}
743
744impl Eq for LifeEvent {}
745
746impl Ord for LifeEvent {
747    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
748        match self.date.cmp(&other.date) {
749            std::cmp::Ordering::Equal => self.index.cmp(&other.index),
750            other => other,
751        }
752    }
753}
754
755impl PartialOrd for LifeEvent {
756    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
757        Some(self.cmp(other))
758    }
759}