tiger_lib/ck3/data/
title_history.rs

1use std::path::PathBuf;
2
3use crate::block::Block;
4use crate::ck3::data::titles::Tier;
5use crate::date::Date;
6use crate::everything::Everything;
7use crate::fileset::{FileEntry, FileHandler};
8use crate::helpers::TigerHashMap;
9use crate::item::Item;
10use crate::parse::ParserMemory;
11use crate::pdxfile::PdxFile;
12use crate::report::{ErrorKey, err, warn};
13use crate::scopes::Scopes;
14use crate::token::Token;
15use crate::tooltipped::Tooltipped;
16use crate::validator::Validator;
17use crate::variables::Variables;
18
19#[derive(Clone, Debug, Default)]
20pub struct TitleHistories {
21    histories: TigerHashMap<&'static str, TitleHistory>,
22}
23
24impl TitleHistories {
25    pub fn load_item(&mut self, key: Token, mut block: Block) {
26        if let Some(other) = self.histories.get_mut(key.as_str()) {
27            // Multiple entries are valid but could easily be a mistake.
28            if other.key.loc.kind >= key.loc.kind {
29                warn(ErrorKey::DuplicateItem)
30                    .msg("title has two definition blocks, they will be added together")
31                    .loc(&other.key)
32                    .loc_msg(key, "the other one is here")
33                    .push();
34            }
35            other.block.append(&mut block);
36        } else {
37            self.histories.insert(key.as_str(), TitleHistory::new(key.clone(), block));
38        }
39    }
40
41    pub fn scan_variables(&self, registry: &mut Variables) {
42        for item in self.histories.values() {
43            registry.scan(&item.block);
44        }
45    }
46
47    pub fn exists(&self, key: &str) -> bool {
48        self.histories.contains_key(key)
49    }
50
51    pub fn iter_keys(&self) -> impl Iterator<Item = &Token> {
52        self.histories.values().map(|item| &item.key)
53    }
54
55    pub fn validate(&self, data: &Everything) {
56        for item in self.histories.values() {
57            item.validate(data);
58        }
59    }
60
61    pub fn verify_has_holder(&self, key: &Token, date: Date, data: &Everything, overlord: &str) {
62        if let Some(item) = self.histories.get(key.as_str()) {
63            item.verify_has_holder(key, date, data, overlord);
64        } else {
65            let msg = format!("{key} has no title history");
66            err(ErrorKey::MissingItem).msg(msg).loc(key).push();
67        }
68    }
69}
70
71impl FileHandler<Block> for TitleHistories {
72    fn subpath(&self) -> PathBuf {
73        PathBuf::from("history/titles")
74    }
75
76    fn load_file(&self, entry: &FileEntry, parser: &ParserMemory) -> Option<Block> {
77        if !entry.filename().to_string_lossy().ends_with(".txt") {
78            return None;
79        }
80
81        PdxFile::read_detect_encoding(entry, parser)
82    }
83
84    fn handle_file(&mut self, _entry: &FileEntry, mut block: Block) {
85        for (key, block) in block.drain_definitions_warn() {
86            if Tier::try_from(&key).is_ok() {
87                self.load_item(key, block);
88            } else {
89                warn(ErrorKey::Validation).msg("expected title").loc(key).push();
90            }
91        }
92    }
93}
94
95#[derive(Clone, Debug)]
96pub struct TitleHistory {
97    key: Token,
98    block: Block,
99    tier: Tier,
100}
101
102impl TitleHistory {
103    pub fn new(key: Token, block: Block) -> Self {
104        let tier = Tier::try_from(&key).unwrap(); // guaranteed by caller
105        Self { key, block, tier }
106    }
107
108    pub fn verify_has_holder(&self, token: &Token, date: Date, data: &Everything, overlord: &str) {
109        let info = format!("setting the {overlord} will not have effect here");
110
111        if let Some(holder) = self.block.get_field_at_date("holder", date) {
112            // if holder is not a value then we already warned about that
113            if let Some(holder) = holder.get_value() {
114                if holder.is("0") {
115                    let msg = format!("{token} has no holder at {date}");
116                    err(ErrorKey::History).msg(msg).info(info).loc(token).push();
117                } else if !data.characters.is_alive(holder, date) {
118                    let msg = format!("holder of {token} is not alive at {date}");
119                    err(ErrorKey::History).msg(msg).info(info).loc(token).push();
120                }
121            }
122        } else {
123            let msg = format!("{token} has no holder at {date}");
124            err(ErrorKey::History).msg(msg).info(info).loc(token).push();
125        }
126    }
127
128    pub fn validate_history(&self, date: Date, block: &Block, data: &Everything) {
129        let mut vd = Validator::new(block, data);
130        vd.field_numeric("change_development_level");
131        if let Some(token) = vd.field_value("holder") {
132            if !token.is("0") {
133                data.verify_exists(Item::Character, token);
134                if data.item_exists(Item::Character, token.as_str()) {
135                    data.characters.verify_alive(token, date);
136                }
137            }
138        }
139        if let Some(token) = vd.field_value("holder_ignore_head_of_faith_requirement") {
140            if !token.is("0") {
141                data.verify_exists(Item::Character, token);
142                if data.item_exists(Item::Character, token.as_str()) {
143                    data.characters.verify_alive(token, date);
144                }
145            }
146        }
147
148        if let Some(token) = vd.field_value("liege") {
149            if !token.is("0") {
150                data.verify_exists(Item::Title, token);
151                if let Some(title) = data.titles.get(token.as_str()) {
152                    if title.tier <= self.tier {
153                        let msg = format!("liege must be higher tier than {}", self.key);
154                        err(ErrorKey::TitleTier).msg(msg).loc(token).push();
155                    }
156                    data.title_history.verify_has_holder(token, date, data, "liege");
157                }
158            }
159        }
160
161        if let Some(token) = vd.field_value("de_jure_liege") {
162            if !token.is("0") {
163                data.verify_exists(Item::Title, token);
164                if let Some(title) = data.titles.get(token.as_str()) {
165                    if title.tier <= self.tier {
166                        let msg = format!("liege must be higher tier than {}", self.key);
167                        err(ErrorKey::TitleTier).msg(msg).loc(token).push();
168                    }
169                }
170            }
171        }
172
173        vd.field_validated_block("tributary_of", |block, data| {
174            let mut vd = Validator::new(block, data);
175            vd.field_validated_value("suzerain", |_, mut vvd| {
176                vvd.item(Item::Title);
177                data.title_history.verify_has_holder(vvd.value(), date, data, "suzerain");
178            });
179            vd.field_item("contract_group", Item::SubjectContractGroup);
180        });
181
182        vd.field_item("government", Item::GovernmentType);
183
184        vd.field_block("succession_laws"); // TODO
185        vd.field_bool("remove_succession_laws");
186
187        vd.field_item("name", Item::Localization);
188        vd.field_bool("reset_name");
189
190        vd.field_item("insert_title_history", Item::TitleHistory);
191
192        vd.field_effect_rooted("effect", Tooltipped::No, Scopes::LandedTitle);
193    }
194
195    pub fn validate(&self, data: &Everything) {
196        data.verify_exists(Item::Title, &self.key);
197
198        let mut vd = Validator::new(&self.block, data);
199        vd.validate_history_blocks(|date, _key, block, data| {
200            self.validate_history(date, block, data);
201        });
202    }
203}