Skip to main content

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                && let Some(parent) = self.characters.get(token.as_str())
204                && let Some(mut cycle_vec) = self.check_ancestor_cycles(parent)
205            {
206                // unwrap is safe because the vec is always created with one element
207                if &ch.key == cycle_vec.first().unwrap() {
208                    let msg = "character is their own ancestor";
209                    cycle_vec.reverse();
210                    cycle_vec.pop();
211                    let mut report = fatal(ErrorKey::Crash).msg(msg).loc(&ch.key);
212                    for token in cycle_vec {
213                        report = report.loc_msg(token, "from here");
214                    }
215                    report.push();
216                } else {
217                    cycle_vec.push(token.clone());
218                    // Returning here means if the father is in a cycle we won't check the
219                    // mother, but that's ok because we're not promising to find all
220                    // cycles anyway.
221                    ch.ancestor_state.store(AncestorState::Checked, Ordering::Release);
222                    return Some(cycle_vec);
223                }
224            }
225        }
226        ch.ancestor_state.store(AncestorState::Checked, Ordering::Release);
227        None
228    }
229
230    pub fn check_pod_flags(&self, data: &Everything) {
231        for item in self.characters.values() {
232            if item.born_by(self.config_only_born) {
233                item.check_pod_flags(data);
234            }
235        }
236    }
237}
238
239impl FileHandler<Block> for Characters {
240    fn config(&mut self, config: &Block) {
241        if let Some(block) = config.get_field_block("characters")
242            && let Some(born) = block.get_field_value("only_born")
243            && let Ok(date) = Date::try_from(born)
244        {
245            self.config_only_born = Some(date);
246        }
247    }
248
249    fn subpath(&self) -> PathBuf {
250        PathBuf::from("history/characters")
251    }
252
253    fn load_file(&self, entry: &FileEntry, parser: &ParserMemory) -> Option<Block> {
254        if !entry.filename().to_string_lossy().ends_with(".txt") {
255            return None;
256        }
257
258        PdxFile::read(entry, parser)
259    }
260
261    fn handle_file(&mut self, _entry: &FileEntry, mut block: Block) {
262        for (key, block) in block.drain_definitions_warn() {
263            self.load_item(key, block);
264        }
265    }
266
267    fn finalize(&mut self) {
268        // Find loops in the ancestry tree. These will crash the game.
269        for item in self.characters.values() {
270            let opt_cycle_vec = self.check_ancestor_cycles(item);
271            // It shouldn't be possible that a cycle vec is passed back beyond the first character.
272            assert!(opt_cycle_vec.is_none());
273        }
274    }
275}
276
277#[atomic_enum]
278enum AncestorState {
279    Unchecked,
280    Checking,
281    Checked,
282}
283
284#[derive(Debug)]
285pub struct Character {
286    key: Token,
287    block: Block,
288    /// Storage for the ancestor cycle check algorithm.
289    ancestor_state: AtomicAncestorState,
290}
291
292impl Character {
293    pub fn new(key: Token, block: Block) -> Self {
294        Self { key, block, ancestor_state: AtomicAncestorState::new(AncestorState::Unchecked) }
295    }
296
297    pub fn born_by(&self, born_by: Option<Date>) -> bool {
298        if let Some(date) = born_by {
299            self.block.get_field_at_date("birth", date).is_some()
300        } else {
301            true
302        }
303    }
304
305    pub fn gender(&self) -> Gender {
306        Gender::from_female_bool(self.block.get_field_bool("female").unwrap_or(false))
307    }
308
309    pub fn is_alive(&self, date: Date) -> bool {
310        // TODO: figure out if we need to account for deaths triggered in effect { } blocks
311        self.block.get_field_at_date("birth", date).is_some()
312            && self.block.get_field_at_date("death", date).is_none()
313    }
314
315    pub fn get_dynasty(&self, date: Date) -> Option<&Token> {
316        self.block.get_field_value_at_date("dynasty", date)
317    }
318
319    pub fn get_house(&self, date: Date) -> Option<&Token> {
320        self.block.get_field_value_at_date("dynasty_house", date)
321    }
322
323    pub fn get_culture(&self, date: Date) -> Option<&Token> {
324        self.block.get_field_value_at_date("culture", date)
325    }
326
327    pub fn get_faith(&self, date: Date) -> Option<&Token> {
328        self.block
329            .get_field_value_at_date("faith", date)
330            .or_else(|| self.block.get_field_value_at_date("religion", date))
331    }
332
333    fn validate_life_event(
334        date: Date,
335        gender: Gender,
336        key: &Token,
337        bv: &BV,
338        data: &Everything,
339        sc: &mut ScopeContext,
340    ) -> Option<(LifeEventType, Token)> {
341        use LifeEventType::*;
342
343        match bv {
344            BV::Value(value) => {
345                if matches!(key.as_str(), "trait" | "add_trait") && value.as_str() == "saint" {
346                    return Some((Posthumous, value.clone()));
347                }
348
349                match key.as_str() {
350                    "name" => {
351                        data.localization.verify_name_exists(value, Severity::Warning);
352                        return None;
353                    }
354                    "birth" => {
355                        if !value.is("yes") && Date::from_str(value.as_str()).is_err() {
356                            let msg = "expected `yes` or a date";
357                            err(ErrorKey::Validation).msg(msg).loc(value).push();
358                        }
359                        return Some((Birth, key.clone()));
360                    }
361                    "death" => {
362                        if !value.is("yes") && !value.is_date() {
363                            data.verify_exists(Item::DeathReason, value);
364                        }
365                        return Some((Death, key.clone()));
366                    }
367                    // religion and faith both mean faith here
368                    "religion" | "faith" => {
369                        data.verify_exists(Item::Faith, value);
370                        return None;
371                    }
372                    "culture" => {
373                        data.verify_exists(Item::Culture, value);
374                        return None;
375                    }
376                    "trait" => {
377                        data.verify_exists(Item::Trait, value);
378                        return None;
379                    }
380                    "employer" => {
381                        if value.is("0") {
382                            return Some((Unemployed, key.clone()));
383                        }
384                        data.verify_exists(Item::Character, value);
385                        if data.item_exists(Item::Character, value.as_str()) {
386                            data.characters.verify_alive(value, date);
387                        }
388                        return Some((Employed, key.clone()));
389                    }
390                    "moved_to_pool" => {
391                        if !value.is("yes") {
392                            let msg = "expected `yes`";
393                            err(ErrorKey::Validation).msg(msg).loc(value).push();
394                        }
395                        return Some((Unemployed, key.clone()));
396                    }
397                    "give_council_position" => {
398                        data.verify_exists(Item::CouncilPosition, value);
399                        return None;
400                    }
401                    "capital" => {
402                        data.verify_exists(Item::Title, value);
403                        if !value.as_str().starts_with("c_") {
404                            err(ErrorKey::Validation)
405                                .msg("capital must be a county")
406                                .loc(value)
407                                .push();
408                        }
409                        return None;
410                    }
411                    "add_spouse" | "add_matrilineal_spouse" => {
412                        data.characters.verify_exists_gender(value, gender.flip());
413                        if data.item_exists(Item::Character, value.as_str()) {
414                            data.characters.verify_alive(value, date);
415                        }
416                        return Some((AddSpouse, value.clone()));
417                    }
418                    "add_same_sex_spouse" => {
419                        data.characters.verify_exists_gender(value, gender);
420                        if data.item_exists(Item::Character, value.as_str()) {
421                            data.characters.verify_alive(value, date);
422                        }
423                        return Some((AddSpouse, value.clone()));
424                    }
425                    "add_concubine" => {
426                        data.characters.verify_exists_gender(value, gender.flip());
427                        if data.item_exists(Item::Character, value.as_str()) {
428                            data.characters.verify_alive(value, date);
429                        }
430                        return None;
431                    }
432                    "remove_spouse" => return Some((RemoveSpouse, value.clone())),
433                    "dynasty" => {
434                        data.verify_exists(Item::Dynasty, value);
435                        return None;
436                    }
437                    "dynasty_house" => {
438                        data.verify_exists(Item::House, value);
439                        return None;
440                    }
441                    _ => (),
442                }
443            }
444            BV::Block(block) => match key.as_str() {
445                "death" => {
446                    let mut vd = Validator::new(block, data);
447                    vd.req_field("death_reason");
448                    vd.field_item("death_reason", Item::DeathReason);
449                    vd.field_item("killer", Item::Character);
450                    return Some((Death, key.clone()));
451                }
452                "effect" => {
453                    validate_effect(block, data, sc, Tooltipped::No);
454                    return None;
455                }
456                _ => (),
457            },
458        }
459
460        // unknown effect field
461        validate_effect_field(
462            Lowercase::empty(),
463            key,
464            Comparator::Equals(Single),
465            bv,
466            data,
467            sc,
468            Tooltipped::No,
469            &mut SpecialTokens::none(),
470        );
471
472        None
473    }
474
475    fn validate_life(character: &Token, life_events: Vec<LifeEvent>) {
476        let mut birth = None;
477        let mut death = None;
478        let mut spouses = TigerHashSet::<Token>::default();
479        let mut employed = false;
480
481        for LifeEvent { date, index: _, token, event } in life_events {
482            use LifeEventType::*;
483
484            if birth.is_none() && event != Birth {
485                let msg = format!("{character} was not born yet on {date}");
486                let mut loc = token.loc;
487                loc.column = 0;
488                warn(ErrorKey::History).msg(msg).loc(loc).push();
489            }
490
491            if let Some((death_date, death_loc)) = death
492                && event != Posthumous
493            {
494                let msg = format!(
495                    "{character} was not alive on {date}, had already died on {death_date}"
496                );
497                let mut loc = token.loc;
498                loc.column = 0;
499                warn(ErrorKey::History).msg(msg).loc(loc).loc_msg(death_loc, "from here").push();
500            }
501
502            match event {
503                Birth => {
504                    let mut loc = token.loc;
505                    loc.column = 0;
506
507                    if let Some((birth_date, birth_loc)) = birth {
508                        let msg = format!(
509                            "{character} couldn't be born again on {date}, was born already on {birth_date}"
510                        );
511                        warn(ErrorKey::History)
512                            .msg(msg)
513                            .loc(loc)
514                            .loc_msg(birth_loc, "from here")
515                            .push();
516                    }
517                    birth = Some((date, loc));
518                }
519                AddSpouse => {
520                    if !spouses.insert(token.clone()) {
521                        let msg = format!("{character} already had {token} as a spouse on {date}");
522                        let curr_token = spouses.get(&token).unwrap();
523                        warn(ErrorKey::History)
524                            .msg(msg)
525                            .loc(token)
526                            .loc_msg(curr_token, "from here")
527                            .push();
528                    }
529                }
530                RemoveSpouse => {
531                    if !spouses.remove(&token) {
532                        let msg = format!("{character} did not have {token} as a spouse on {date}");
533                        warn(ErrorKey::History).msg(msg).loc(token).push();
534                    }
535                }
536                Employed => employed = true,
537                Unemployed => {
538                    if !employed {
539                        let msg = format!("{character} was unemployed anyway on {date}");
540                        untidy(ErrorKey::History).msg(msg).loc(token).push();
541                    }
542                    employed = false;
543                }
544                Death => {
545                    let mut loc = token.loc;
546                    loc.column = 0;
547                    death = Some((date, loc));
548                }
549                Posthumous => {
550                    if death.is_none() {
551                        let msg = format!("{character} had not died yet on {date}");
552                        warn(ErrorKey::History).msg(msg).loc(token).push();
553                    }
554                }
555            }
556        }
557    }
558
559    fn validate(&self, data: &Everything) {
560        let mut vd = Validator::new(&self.block, data);
561        let mut sc = ScopeContext::new(Scopes::Character, &self.key);
562
563        if self.key.as_str().contains('.') {
564            let msg =
565                format!("`character:{}` will not work because of the dot in the id", &self.key);
566            let info = "script code will not be able to refer to this character";
567            warn(ErrorKey::CharacterId).msg(msg).info(info).loc(&self.key).push();
568        }
569
570        vd.req_field("name");
571        if let Some(name) = vd.field_value("name") {
572            data.localization.verify_name_exists(name, Severity::Warning);
573        }
574
575        vd.field_item("dna", Item::Dna);
576        vd.field_bool("female");
577        vd.field_integer("martial");
578        vd.field_integer("prowess");
579        vd.field_integer("diplomacy");
580        vd.field_integer("intrigue");
581        vd.field_integer("stewardship");
582        vd.field_integer("learning");
583        vd.multi_field_item("trait", Item::Trait);
584
585        if let Some(ch) = vd.field_value("father") {
586            data.characters.verify_exists_gender(ch, Gender::Male);
587        }
588
589        if let Some(ch) = vd.field_value("mother") {
590            data.characters.verify_exists_gender(ch, Gender::Female);
591        }
592
593        vd.field_bool("disallow_random_traits");
594
595        // religion and faith both mean faith here
596        vd.field_item("religion", Item::Faith);
597        vd.field_item("faith", Item::Faith);
598
599        vd.field_item("culture", Item::Culture);
600
601        vd.field_item("dynasty", Item::Dynasty);
602        vd.field_item("dynasty_house", Item::House);
603
604        vd.field_item("give_nickname", Item::Nickname);
605        vd.field_item("sexuality", Item::Sexuality);
606        vd.field_numeric("health");
607        vd.field_numeric("fertility");
608
609        vd.field_validated_block("portrait_override", |block, data| {
610            let mut vd = Validator::new(block, data);
611            vd.field_validated_block(
612                "portrait_modifier_overrides",
613                validate_portrait_modifier_overrides,
614            );
615            vd.field_validated_block("hair", validate_color);
616        });
617
618        let mut life_events = Vec::new();
619        let gender = Gender::from_female_bool(self.block.get_field_bool("female").unwrap_or(false));
620        vd.validate_history_blocks(|date, _key, block, data| {
621            for (index, (key, bv)) in block.iter_assignments_and_definitions_warn().enumerate() {
622                if let Some((life_event_type, token)) =
623                    Self::validate_life_event(date, gender, key, bv, data, &mut sc)
624                {
625                    life_events.push(LifeEvent { date, index, event: life_event_type, token });
626                }
627            }
628        });
629
630        life_events.sort_unstable();
631        Self::validate_life(&self.key, life_events);
632    }
633
634    fn check_pod_flags(&self, _data: &Everything) {
635        if self.block.has_key("dna")
636            && self.has_trait("nosferatu")
637            && !self.has_flag("had_POD_character_nosferatu_looks")
638            && !self.key.is("791762")
639        {
640            let msg = "nosferatu with predefined dna lacks had_POD_character_nosferatu_looks";
641            err(ErrorKey::PrincesOfDarkness).msg(msg).loc(&self.key).push();
642        }
643    }
644
645    fn has_flag(&self, flag: &str) -> bool {
646        for (key, block) in self.block.iter_definitions() {
647            if key.is_date() {
648                if block_has_flag(block, flag) {
649                    return true;
650                }
651                for block in block.get_field_blocks("effect") {
652                    if block_has_flag(block, flag) {
653                        return true;
654                    }
655                }
656            }
657        }
658        false
659    }
660
661    fn has_trait(&self, tr: &str) -> bool {
662        for token in self.block.get_field_values("trait") {
663            if token.is(tr) {
664                return true;
665            }
666        }
667        for (key, block) in self.block.iter_definitions() {
668            if key.is_date() {
669                for token in block.get_field_values("add_trait") {
670                    if token.is(tr) {
671                        return true;
672                    }
673                }
674                if let Some(block) = block.get_field_block("effect") {
675                    for token in block.get_field_values("add_trait") {
676                        if token.is(tr) {
677                            return true;
678                        }
679                    }
680                }
681            }
682        }
683        false
684    }
685}
686
687fn block_has_flag(block: &Block, flag: &str) -> bool {
688    for token in block.get_field_values("add_character_flag") {
689        if token.is(flag) {
690            return true;
691        }
692    }
693    for block in block.get_field_blocks("add_character_flag") {
694        if block.field_value_is("flag", flag) {
695            return true;
696        }
697    }
698    false
699}
700
701#[derive(Debug)]
702enum LifeEventType {
703    /// All other events must happen after birth
704    Birth,
705    AddSpouse,
706    RemoveSpouse,
707    Employed,
708    /// Must be employed already
709    Unemployed,
710    /// All other events must happen before death
711    Death,
712    Posthumous,
713    // TODO add Effect validation, e.g. `add_trait`
714}
715
716impl PartialEq for LifeEventType {
717    fn eq(&self, other: &Self) -> bool {
718        std::mem::discriminant(self) == std::mem::discriminant(other)
719    }
720}
721
722impl Eq for LifeEventType {}
723
724#[derive(Debug)]
725struct LifeEvent {
726    date: Date,
727    index: usize,
728    event: LifeEventType,
729    token: Token,
730}
731
732impl PartialEq for LifeEvent {
733    fn eq(&self, other: &Self) -> bool {
734        self.date == other.date && self.index == other.index
735    }
736}
737
738impl Eq for LifeEvent {}
739
740impl Ord for LifeEvent {
741    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
742        match self.date.cmp(&other.date) {
743            std::cmp::Ordering::Equal => self.index.cmp(&other.index),
744            other => other,
745        }
746    }
747}
748
749impl PartialOrd for LifeEvent {
750    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
751        Some(self.cmp(other))
752    }
753}