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(); 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 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 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, }
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(); Self { key, block, tier, parent, is_county_capital }
220 }
221
222 pub fn validate(&self, data: &Everything) {
223 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 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 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 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 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}