tiger_lib/ck3/data/
bookmarks.rs

1use crate::block::Block;
2use crate::ck3::validate::validate_portrait_modifier_overrides;
3use crate::context::ScopeContext;
4use crate::data::dna::validate_genes;
5use crate::date::Date;
6use crate::db::{Db, DbKind};
7use crate::everything::Everything;
8use crate::game::GameFlags;
9use crate::item::{Item, ItemLoader, LoadAsFile, Recursive};
10use crate::pdxfile::PdxEncoding;
11use crate::report::{ErrorKey, fatal, warn};
12use crate::scopes::Scopes;
13use crate::token::Token;
14use crate::validator::Validator;
15
16#[derive(Clone, Debug)]
17pub struct BookmarkGroup {}
18
19inventory::submit! {
20    ItemLoader::Normal(GameFlags::Ck3, Item::BookmarkGroup, BookmarkGroup::add)
21}
22
23impl BookmarkGroup {
24    pub fn add(db: &mut Db, key: Token, block: Block) {
25        db.add(Item::BookmarkGroup, key, block, Box::new(Self {}));
26    }
27}
28
29impl DbKind for BookmarkGroup {
30    fn validate(&self, _key: &Token, block: &Block, data: &Everything) {
31        let mut vd = Validator::new(block, data);
32        vd.field_date("default_start_date");
33    }
34}
35
36#[derive(Clone, Debug)]
37pub struct Bookmark {}
38
39inventory::submit! {
40    ItemLoader::Normal(GameFlags::Ck3, Item::Bookmark, Bookmark::add)
41}
42
43impl Bookmark {
44    pub fn add(db: &mut Db, key: Token, block: Block) {
45        db.add(Item::Bookmark, key, block, Box::new(Self {}));
46    }
47}
48
49impl DbKind for Bookmark {
50    fn validate(&self, key: &Token, block: &Block, data: &Everything) {
51        let mut vd = Validator::new(block, data);
52        let mut sc = ScopeContext::new(Scopes::None, key);
53        vd.req_field("start_date");
54        vd.field_date("start_date");
55        vd.field_bool("is_playable");
56        vd.field_bool("recommended");
57        vd.field_bool("test_default");
58        vd.field_item("group", Item::BookmarkGroup);
59        vd.field_item("requires_dlc_flag", Item::DlcFeature);
60        vd.field_script_value("weight", &mut sc);
61
62        data.verify_exists(Item::Localization, key);
63        let loca = format!("{key}_desc");
64        data.verify_exists_implied(Item::Localization, &loca, key);
65
66        let pathname = format!("gfx/interface/bookmarks/{key}.dds");
67        data.verify_exists_implied(Item::File, &pathname, key);
68        data.verify_icon("NGameIcons|BOOKMARK_START_BUTTON_PATH", key, ".dds");
69        let pathname = format!("gfx/interface/icons/bookmark_buttons/{key}.dds");
70        data.verify_exists_implied(Item::File, &pathname, key);
71
72        let start_date = block.get_field_date("start_date");
73        vd.multi_field_validated_block("character", |block, data| {
74            if let Some(name) = block.get_field_value("name") {
75                let pathname = format!("gfx/interface/bookmarks/{key}_{name}.dds");
76                data.verify_exists_implied(Item::File, &pathname, name);
77            }
78            validate_bookmark_character(block, data, true, start_date);
79        });
80    }
81}
82
83fn validate_bookmark_character(
84    block: &Block,
85    data: &Everything,
86    toplevel: bool,
87    start_date: Option<Date>,
88) {
89    let existing_ruler =
90        !block.get_field_value("character_design_type").is_some_and(|t| t.is("noble_family"));
91    let mut vd = Validator::new(block, data);
92    vd.field_bool("tutorial");
93    vd.field_bool("test_default");
94    vd.field_bool("display");
95    if block.field_value_is("display", "no") {
96        vd.field_value("name");
97    } else {
98        vd.field_item("name", Item::Localization);
99        if let Some(name) = block.get_field_value("name") {
100            if existing_ruler && !data.item_exists(Item::BookmarkPortrait, name.as_str()) {
101                let msg =
102                    format!("bookmark portrait for {name} not found in common/bookmark_portraits");
103                let info = "This causes a crash in CK3 1.13";
104                fatal(ErrorKey::Crash).msg(msg).info(info).loc(name).push();
105            }
106        }
107    }
108    if toplevel {
109        if let Some(token) = block.get_field_value("name") {
110            let loca = format!("{token}_desc");
111            data.verify_exists_implied(Item::Localization, &loca, token);
112        }
113    } else {
114        vd.field_item("relation", Item::Localization);
115    }
116    vd.field_item("dynasty", Item::Dynasty);
117    vd.field_item("dynasty_house", Item::House);
118    if let Some(token) =
119        block.get_field_value("dynasty_house").or_else(|| block.get_field_value("dynasty"))
120    {
121        if !data.item_exists(Item::Coa, token.as_str()) {
122            let msg = format!("{} {token} not defined in {}", Item::Coa, Item::Coa.path());
123            let info = "bookmark characters must have a defined coa or their shields will be blank";
124            warn(ErrorKey::MissingItem).msg(msg).info(info).loc(token).push();
125        }
126    }
127    vd.field_integer("dynasty_splendor_level");
128    vd.field_choice("type", &["male", "female", "boy", "girl"]);
129    vd.field_date("birth");
130    vd.field_item("title", Item::Title);
131    vd.field_item("title_text_override", Item::Localization);
132    vd.field_item("government", Item::GovernmentType);
133    vd.field_item("fallback_government", Item::GovernmentType);
134    vd.field_item("culture", Item::Culture);
135    if existing_ruler {
136        vd.field_item("religion", Item::Faith);
137    } else {
138        vd.field_item("religion", Item::Localization);
139    }
140    vd.field_item("difficulty", Item::Localization);
141    vd.field_item("history_id", Item::Character);
142    vd.field_item("animation", Item::PortraitAnimation);
143    vd.field_validated_block("position", |block, data| {
144        let mut vd = Validator::new(block, data);
145        vd.req_tokens_integers_exactly(2);
146    });
147    // TODO: acceptable values?
148    vd.field_value("character_design_type");
149    vd.field_item("target_title", Item::Title);
150    if let Some(start_date) = start_date {
151        if let Some(id) = block.get_field_value("history_id") {
152            let name = block.get_field_value("name");
153            if data.item_exists(Item::Character, id.as_str()) {
154                validate_bookmark_against_history(
155                    block.get_field_value("dynasty"),
156                    "dynasty",
157                    start_date,
158                    data.characters.get_dynasty(id, start_date, data),
159                    name,
160                );
161                validate_bookmark_against_history(
162                    block.get_field_value("dynasty_house"),
163                    "house",
164                    start_date,
165                    data.characters.get_house(id, start_date),
166                    name,
167                );
168                validate_bookmark_against_history(
169                    block.get_field_value("culture"),
170                    "culture",
171                    start_date,
172                    data.characters.get_culture(id, start_date),
173                    name,
174                );
175                validate_bookmark_against_history(
176                    block.get_field_value("faith"),
177                    "faith",
178                    start_date,
179                    data.characters.get_faith(id, start_date),
180                    name,
181                );
182            }
183        }
184    }
185    vd.multi_field_validated_block("character", |block, data| {
186        validate_bookmark_character(block, data, false, start_date);
187    });
188}
189
190#[derive(Clone, Debug)]
191pub struct BookmarkPortrait {}
192
193inventory::submit! {
194    ItemLoader::Full(GameFlags::Ck3, Item::BookmarkPortrait, PdxEncoding::Utf8OptionalBom, ".txt", LoadAsFile::No, Recursive::Yes, BookmarkPortrait::add)
195}
196
197impl BookmarkPortrait {
198    pub fn add(db: &mut Db, key: Token, block: Block) {
199        db.add(Item::BookmarkPortrait, key, block, Box::new(Self {}));
200    }
201}
202
203impl DbKind for BookmarkPortrait {
204    fn validate(&self, _key: &Token, block: &Block, data: &Everything) {
205        let mut vd = Validator::new(block, data);
206        vd.field_choice("type", &["male", "female", "boy", "girl"]);
207        vd.field_value("id"); // TODO
208        vd.field_value("random_seed");
209        vd.field_precise_numeric("age");
210        vd.field_list_integers_exactly("entity", 2);
211        vd.field_validated_block("genes", validate_genes);
212        vd.field_validated_block("override", |block, data| {
213            let mut vd = Validator::new(block, data);
214            vd.field_validated_block(
215                "portrait_modifier_overrides",
216                validate_portrait_modifier_overrides,
217            );
218        });
219        vd.field_validated_block("tags", |block, data| {
220            let mut vd = Validator::new(block, data);
221            for block in vd.blocks() {
222                let mut vd = Validator::new(block, data);
223                vd.field_value("hash");
224                vd.field_bool("invert");
225            }
226        });
227    }
228}
229
230fn validate_bookmark_against_history(
231    field: Option<&Token>,
232    desc: &str,
233    date: Date,
234    history: Option<&Token>,
235    name: Option<&Token>,
236) {
237    if let Some(field) = field {
238        if let Some(history) = history {
239            if field != history {
240                let msg = format!(
241                    "{desc} is {field} in bookmark but {history} in character history at {date}"
242                );
243                warn(ErrorKey::Bookmarks)
244                    .strong()
245                    .msg(msg)
246                    .loc_msg(field, "bookmark")
247                    .loc_msg(history, "history")
248                    .opt_loc_msg(name, "character")
249                    .push();
250            }
251        } else {
252            let msg = format!(
253                "{desc} is {field} in bookmark but character has no {desc} in history at {date}"
254            );
255            warn(ErrorKey::Bookmarks)
256                .strong()
257                .msg(msg)
258                .loc(field)
259                .opt_loc_msg(name, "bookmark")
260                .push();
261        }
262    }
263}
264
265#[derive(Clone, Debug)]
266pub struct ChallengeCharacter {}
267
268inventory::submit! {
269    ItemLoader::Normal(GameFlags::Ck3, Item::ChallengeCharacter, ChallengeCharacter::add)
270}
271
272impl ChallengeCharacter {
273    pub fn add(db: &mut Db, key: Token, block: Block) {
274        db.add(Item::ChallengeCharacter, key, block, Box::new(Self {}));
275    }
276}
277
278impl DbKind for ChallengeCharacter {
279    fn validate(&self, _key: &Token, block: &Block, data: &Everything) {
280        let mut vd = Validator::new(block, data);
281        vd.field_date("start_date");
282        let start_date = block.get_field_date("start_date");
283        vd.field_validated_block("character", |block, data| {
284            validate_bookmark_character(block, data, true, start_date);
285        });
286
287        // undocumented
288
289        vd.field_list("achievements"); // TODO: validate achievements?
290    }
291}