tiger_lib/ck3/data/
titles.rs

1use std::fmt::{Display, Formatter};
2use std::path::PathBuf;
3use std::sync::Arc;
4
5use crate::block::Block;
6use crate::ck3::data::provinces::ProvId;
7use crate::context::ScopeContext;
8use crate::desc::validate_desc;
9use crate::everything::Everything;
10use crate::fileset::{FileEntry, FileHandler};
11use crate::helpers::{TigerHashMap, dup_error};
12use crate::item::Item;
13use crate::parse::ParserMemory;
14use crate::pdxfile::PdxFile;
15use crate::report::{ErrorKey, err, warn};
16use crate::scopes::Scopes;
17use crate::token::Token;
18use crate::tooltipped::Tooltipped;
19use crate::validate::validate_possibly_named_color;
20use crate::validator::Validator;
21use crate::variables::Variables;
22
23#[derive(Clone, Debug, Default)]
24pub struct Titles {
25    titles: TigerHashMap<&'static str, Arc<Title>>,
26    baronies: TigerHashMap<ProvId, Arc<Title>>,
27}
28
29#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
30pub enum Tier {
31    Barony,
32    County,
33    Duchy,
34    Kingdom,
35    Empire,
36    Hegemony,
37}
38
39impl TryFrom<&Token> for Tier {
40    type Error = std::fmt::Error;
41    fn try_from(value: &Token) -> Result<Self, Self::Error> {
42        let s = value.as_str();
43        if s.starts_with("b_") {
44            Ok(Tier::Barony)
45        } else if s.starts_with("c_") {
46            Ok(Tier::County)
47        } else if s.starts_with("d_") {
48            Ok(Tier::Duchy)
49        } else if s.starts_with("k_") {
50            Ok(Tier::Kingdom)
51        } else if s.starts_with("e_") {
52            Ok(Tier::Empire)
53        } else if s.starts_with("h_") {
54            Ok(Tier::Hegemony)
55        } else {
56            Err(std::fmt::Error)
57        }
58    }
59}
60
61impl TryFrom<Token> for Tier {
62    type Error = std::fmt::Error;
63    fn try_from(value: Token) -> Result<Self, Self::Error> {
64        Tier::try_from(&value)
65    }
66}
67
68impl Display for Tier {
69    fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
70        match *self {
71            Tier::Barony => write!(f, "barony"),
72            Tier::County => write!(f, "county"),
73            Tier::Duchy => write!(f, "duchy"),
74            Tier::Kingdom => write!(f, "kingdom"),
75            Tier::Empire => write!(f, "empire"),
76            Tier::Hegemony => write!(f, "hegemony"),
77        }
78    }
79}
80
81impl Titles {
82    pub fn load_item(
83        &mut self,
84        key: Token,
85        block: &Block,
86        parent: Option<&'static str>,
87        is_county_capital: bool,
88    ) {
89        if let Some(other) = self.titles.get(key.as_str()) {
90            if other.key.loc.kind >= key.loc.kind {
91                dup_error(&key, &other.key, "title");
92            }
93        }
94        let title = Arc::new(Title::new(key.clone(), block.clone(), parent, is_county_capital));
95        self.titles.insert(key.as_str(), Arc::clone(&title));
96
97        let parent_tier = Tier::try_from(&key).unwrap(); // guaranteed by caller
98        if parent_tier == Tier::Barony {
99            if let Some(provid) = block.get_field_integer("province") {
100                if let Ok(provid) = ProvId::try_from(provid) {
101                    self.baronies.insert(provid, title);
102                } else {
103                    err(ErrorKey::Validation)
104                        .msg("province id out of range")
105                        .loc(block.get_field_value("province").unwrap())
106                        .push();
107                }
108            } else {
109                err(ErrorKey::Validation).msg("barony without province id").loc(&key).push();
110            }
111        }
112
113        let mut is_county_capital = parent_tier == Tier::County;
114        for (k, block) in block.iter_definitions() {
115            if let Ok(tier) = Tier::try_from(k) {
116                if tier >= parent_tier {
117                    let msg = format!("can't put a {tier} inside a {parent_tier}");
118                    err(ErrorKey::TitleTier).msg(msg).loc(k).push();
119                }
120                self.load_item(k.clone(), block, Some(key.as_str()), is_county_capital);
121                is_county_capital = false;
122            }
123        }
124        if is_county_capital && !block.get_field_bool("landless").unwrap_or(false) {
125            err(ErrorKey::Validation).msg("county with no baronies!").loc(key).push();
126        }
127    }
128
129    pub fn scan_variables(&self, registry: &mut Variables) {
130        for item in self.titles.values() {
131            // Title blocks are nested, so the parent check is to avoid re-scanning subordinate titles.
132            if item.parent.is_none() {
133                registry.scan(&item.block);
134            }
135        }
136    }
137
138    pub fn exists(&self, key: &str) -> bool {
139        self.titles.contains_key(key)
140    }
141
142    pub fn iter_keys(&self) -> impl Iterator<Item = &Token> {
143        self.titles.values().map(|item| &item.key)
144    }
145
146    pub fn get(&self, key: &str) -> Option<Arc<Title>> {
147        self.titles.get(key).cloned()
148    }
149
150    pub fn validate(&self, data: &Everything) {
151        for item in self.titles.values() {
152            item.validate(data);
153        }
154    }
155
156    pub fn capital_of(&self, prov: ProvId) -> Option<&str> {
157        self.baronies.get(&prov).and_then(|b| b.capital_of())
158    }
159
160    /// Return true iff the vassal title is in the de jure land of the liege title.
161    /// A title is always in its own de jure.
162    pub fn is_de_jure(&self, liege: &str, vassal: &str) -> bool {
163        let mut key = vassal;
164        while let Some(title) = self.titles.get(key) {
165            if key == liege {
166                return true;
167            }
168            key = if let Some(parent) = title.parent {
169                parent
170            } else {
171                return false;
172            }
173        }
174        false
175    }
176}
177
178impl FileHandler<Block> for Titles {
179    fn subpath(&self) -> PathBuf {
180        PathBuf::from("common/landed_titles")
181    }
182
183    fn load_file(&self, entry: &FileEntry, parser: &ParserMemory) -> Option<Block> {
184        if !entry.filename().to_string_lossy().ends_with(".txt") {
185            return None;
186        }
187
188        PdxFile::read(entry, parser)
189    }
190
191    fn handle_file(&mut self, _entry: &FileEntry, mut block: Block) {
192        for (key, block) in block.drain_definitions_warn() {
193            if Tier::try_from(&key).is_ok() {
194                self.load_item(key, &block, None, false);
195            } else {
196                warn(ErrorKey::Validation).msg("expected title").loc(key).push();
197            }
198        }
199    }
200}
201
202#[derive(Clone, Debug)]
203pub struct Title {
204    key: Token,
205    block: Block,
206    pub tier: Tier,
207    pub parent: Option<&'static str>,
208    is_county_capital: bool, // for baronies
209}
210
211impl Title {
212    pub fn new(
213        key: Token,
214        block: Block,
215        parent: Option<&'static str>,
216        is_county_capital: bool,
217    ) -> Self {
218        let tier = Tier::try_from(&key).unwrap(); // guaranteed by caller
219        Self { key, block, tier, parent, is_county_capital }
220    }
221
222    pub fn validate(&self, data: &Everything) {
223        // NOTE: There used to be a check that non-barony titles existed in the
224        // title history, but that seems to be optional.
225        data.verify_exists(Item::Localization, &self.key);
226        let loca = format!("{}_adj", &self.key);
227        if self.tier > Tier::Barony {
228            data.verify_exists_implied(Item::Localization, &loca, &self.key);
229        } else {
230            data.mark_used(Item::Localization, &loca);
231        }
232        // The _pre is rarely defined even in vanilla
233        let loca = format!("{}_pre", &self.key);
234        data.mark_used(Item::Localization, &loca);
235        let definite_form = self.block.field_value_is("definite_form", "yes");
236        if definite_form {
237            data.localization.suggest(&format!("{}_article", &self.key), &self.key);
238        }
239
240        let mut vd = Validator::new(&self.block, data);
241        let mut sc = ScopeContext::new(Scopes::Character, &self.key);
242
243        vd.field_validated("color", validate_possibly_named_color);
244        vd.advice_field("color2", "no longer used");
245        vd.field_bool("figurehead");
246        vd.field_bool("allow_domicile");
247        vd.field_bool("definite_form");
248        vd.field_bool("ruler_uses_title_name");
249        vd.field_bool("can_be_named_after_dynasty");
250        vd.field_bool("landless");
251        vd.field_bool("require_landless");
252        vd.field_bool("no_automatic_claims");
253        vd.field_bool("always_follows_primary_heir");
254        vd.field_bool("destroy_if_invalid_heir");
255        vd.field_bool("destroy_on_succession");
256        vd.field_bool("delete_on_destroy");
257        vd.field_bool("delete_on_gain_same_tier");
258        vd.field_bool("de_jure_drift_disabled");
259        vd.field_bool("ignore_titularity_for_title_weighting");
260        vd.field_bool("noble_family");
261        vd.field_bool("can_use_nomadic_naming");
262
263        let mut sc_entry = ScopeContext::new(Scopes::Character, &self.key);
264        sc_entry.define_name("target", Scopes::Character, &self.key);
265        sc_entry.define_name("title", Scopes::LandedTitle, &self.key);
266        sc_entry.define_name("liege", Scopes::Character, &self.key);
267        sc_entry.define_name("vassal", Scopes::Character, &self.key);
268        vd.field_validated_sc("personal_relation_entry", &mut sc_entry, validate_desc);
269        vd.field_trigger_rooted("personal_relation_vassal", Tooltipped::No, Scopes::Character);
270
271        vd.advice_field(
272            "male_names",
273            "replaced with `holding_regnal_male_names` and `posthumous_regnal_male_names`",
274        );
275        vd.advice_field(
276            "female_names",
277            "replaced with `holding_regnal_female_names` and `posthumous_regnal_female_names`",
278        );
279        vd.field_list_items("holding_regnal_male_names", Item::Localization);
280        vd.field_list_items("posthumous_regnal_male_names", Item::Localization);
281        vd.field_list_items("holding_regnal_female_names", Item::Localization);
282        vd.field_list_items("posthumous_regnal_female_names", Item::Localization);
283
284        vd.field_bool("disable_regnal_numbers");
285        vd.advice_field("enable_regnal_numbers", "replaced with `disable_regnal_numbers` in 1.18");
286
287        if Tier::try_from(&self.key) == Ok(Tier::Barony) {
288            // TODO: check that no two baronies have the same province
289            vd.field_item("province", Item::Province);
290        }
291
292        vd.field_script_value_no_breakdown("ai_primary_priority", &mut sc);
293
294        vd.field_trigger("can_create", Tooltipped::Yes, &mut sc);
295        vd.field_trigger("can_create_on_partition", Tooltipped::No, &mut sc);
296        vd.field_trigger("can_destroy", Tooltipped::Yes, &mut sc);
297
298        vd.field_validated_block("cultural_names", |block, data| {
299            let mut vd = Validator::new(block, data);
300            vd.unknown_value_fields(|key, token| {
301                data.verify_exists(Item::NameList, key);
302                data.verify_exists(Item::Localization, token);
303                let loca = format!("{token}_adj");
304                data.mark_used(Item::Localization, &loca);
305                if definite_form {
306                    let loca = format!("{token}_article");
307                    data.mark_used(Item::Localization, &loca);
308                }
309            });
310        });
311
312        let mut has_de_jure_land = false;
313        // The blocks are validated by the next level Title
314        vd.unknown_block_fields(|key, _| {
315            if Tier::try_from(key).is_err() {
316                let msg = format!("unknown field `{key}`");
317                warn(ErrorKey::UnknownField).msg(msg).loc(key).push();
318            } else {
319                has_de_jure_land = true;
320            }
321        });
322        // Check capital after the above loop, to have the has_de_jure_land result.
323        if let Some(token) = vd.field_value("capital") {
324            data.verify_exists(Item::Title, token);
325            if Tier::try_from(token) != Ok(Tier::County) {
326                let msg = "capital must be a county";
327                err(ErrorKey::TitleTier).msg(msg).loc(token).push();
328            } else if self.tier == Tier::Duchy
329                && has_de_jure_land
330                && !data.titles.is_de_jure(self.key.as_str(), token.as_str())
331            {
332                let msg = format!("capital `{token}` is not in de jure `{}`", self.key);
333                warn(ErrorKey::TitleTier).msg(msg).loc(token).push();
334            }
335        }
336    }
337
338    fn capital_of(&self) -> Option<&str> {
339        if self.is_county_capital { self.parent } else { None }
340    }
341}