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 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"); 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 vd.field_list("achievements"); }
291}