1use crate::Everything;
2use crate::block::{BV, Block};
3use crate::ck3::modif::ModifKinds;
4use crate::ck3::tables::misc::LEGEND_QUALITY;
5use crate::ck3::validate::validate_cost;
6use crate::context::ScopeContext;
7use crate::db::{Db, DbKind};
8use crate::desc::validate_desc;
9use crate::effect::validate_effect;
10use crate::game::GameFlags;
11use crate::helpers::{TigerHashMap, TigerHashSet};
12use crate::item::{Item, ItemLoader};
13use crate::modif::validate_modifs;
14use crate::report::{ErrorKey, err};
15use crate::scopes::Scopes;
16use crate::script_value::{validate_non_dynamic_script_value, validate_script_value_no_breakdown};
17use crate::token::Token;
18use crate::tooltipped::Tooltipped;
19use crate::trigger::validate_target;
20use crate::validate::{validate_duration, validate_possibly_named_color};
21use crate::validator::Validator;
22
23#[derive(Clone, Debug)]
24pub struct LegendType {}
25
26inventory::submit! {
27 ItemLoader::Normal(GameFlags::Ck3, Item::LegendType, LegendType::add)
28}
29
30impl LegendType {
31 pub fn add(db: &mut Db, key: Token, block: Block) {
32 db.add(Item::LegendType, key, block, Box::new(Self {}));
33 }
34}
35
36impl DbKind for LegendType {
37 fn validate(&self, key: &Token, block: &Block, data: &crate::Everything) {
38 let loca = format!("legend_{key}");
39 data.verify_exists_implied(Item::Localization, &loca, key);
40 let loca = format!("legend_{key}_name");
41 data.verify_exists_implied(Item::Localization, &loca, key);
42 let loca = format!("legend_{key}_desc");
43 data.verify_exists_implied(Item::Localization, &loca, key);
44
45 let mut vd = Validator::new(block, data);
46 vd.field_validated("color", validate_possibly_named_color);
47 vd.field_effect_builder("on_province_spread", Tooltipped::No, build_province_legend_sc);
48 vd.field_effect_builder("on_province_recovered", Tooltipped::No, build_province_legend_sc);
49 vd.field_effect_rooted("on_start", Tooltipped::No, Scopes::Legend);
50 vd.field_effect_rooted("on_end", Tooltipped::No, Scopes::Legend);
51 vd.field_effect_builder("on_yearly", Tooltipped::No, build_character_legend_sc);
53 vd.field_effect_builder(
55 "on_legend_start_promote",
56 Tooltipped::No,
57 build_character_legend_sc,
58 );
59 vd.field_effect_builder(
60 "on_legend_stop_promote",
61 Tooltipped::No,
62 build_character_legend_sc,
63 );
64 vd.field_trigger_builder(
66 "is_valid_protagonist",
67 Tooltipped::No,
68 build_character_character_sc,
69 );
70 vd.field_validated_build_sc(
71 "ai_protagonist_weight",
72 build_character_character_sc,
73 validate_script_value_no_breakdown,
74 );
75 vd.field_validated_block("quality", |block, data| {
76 let mut vd = Validator::new(block, data);
77 for &quality in LEGEND_QUALITY {
78 vd.req_field(quality);
79 vd.field_validated_block(quality, validate_legend_quality);
80 }
81 });
82 }
83
84 fn validate_call(
85 &self,
86 _key: &Token,
87 block: &Block,
88 _from: &Token,
89 _from_block: &Block,
90 data: &Everything,
91 sc: &mut ScopeContext,
92 ) {
93 if let Some(block) = block.get_field_block("quality") {
94 for (_, block) in block.iter_definitions() {
95 if let Some(block) = block.get_field_block("impact") {
96 if let Some(block) = block.get_field_block("on_complete") {
97 validate_effect(block, data, sc, Tooltipped::Yes); }
99 }
100 }
101 }
102 }
103}
104
105fn build_province_legend_sc(key: &Token) -> ScopeContext {
106 let mut sc = ScopeContext::new(Scopes::Province, key);
107 sc.define_name("legend", Scopes::Legend, key);
108 sc
109}
110
111fn build_character_legend_sc(key: &Token) -> ScopeContext {
112 let mut sc = ScopeContext::new(Scopes::Character, key);
113 sc.define_name("legend", Scopes::Legend, key);
114 sc
115}
116
117fn build_character_character_sc(key: &Token) -> ScopeContext {
118 let mut sc = ScopeContext::new(Scopes::Character, key);
119 sc.define_name("creator", Scopes::Character, key);
120 sc
121}
122
123fn validate_legend_quality(block: &Block, data: &Everything) {
124 let mut vd = Validator::new(block, data);
125 vd.field_validated_build_sc(
126 "spread_chance",
127 build_province_legend_sc,
128 validate_script_value_no_breakdown,
129 );
130 vd.field_validated("max_provinces", validate_non_dynamic_script_value);
131 vd.field_validated_block_rooted("owner_cost", Scopes::Character, validate_cost);
132 vd.field_validated_block_build_sc("promoter_cost", build_character_legend_sc, validate_cost);
133 vd.field_validated_block_rooted("creation_cost", Scopes::Character, validate_cost);
134 vd.field_validated_block_rooted("upgrade_cost", Scopes::Character, validate_cost);
135 vd.field_validated_block_rooted("removal_duration", Scopes::None, validate_duration);
136 vd.field_validated_block("impact", |block, data| {
137 let mut vd = Validator::new(block, data);
138 validate_impact_modifiers(&mut vd);
139 vd.field_block("on_complete");
141 });
142 vd.field_validated_block("ai_chance", validate_ai_chance);
143}
144
145fn validate_impact_modifiers(vd: &mut Validator) {
146 vd.field_validated_block("province_modifier", |block, data| {
147 let vd = Validator::new(block, data);
148 validate_modifs(block, data, ModifKinds::Province, vd);
149 });
150 vd.field_validated_block("county_modifier", |block, data| {
151 let vd = Validator::new(block, data);
152 validate_modifs(block, data, ModifKinds::County, vd);
153 });
154 vd.field_validated_block("owner_modifier", |block, data| {
155 let vd = Validator::new(block, data);
156 validate_modifs(block, data, ModifKinds::Character, vd);
157 });
158 vd.field_validated_block("promoter_modifier", |block, data| {
159 let vd = Validator::new(block, data);
160 validate_modifs(block, data, ModifKinds::Character, vd);
161 });
162}
163
164fn validate_ai_chance(block: &Block, data: &Everything) {
165 let mut vd = Validator::new(block, data);
166
167 vd.field_validated_rooted("create", Scopes::Character, validate_script_value_no_breakdown);
168 vd.field_validated_build_sc(
169 "promote",
170 build_character_legend_sc,
171 validate_script_value_no_breakdown,
172 );
173 vd.field_validated_build_sc(
174 "take_unowned",
175 build_character_legend_sc,
176 validate_script_value_no_breakdown,
177 );
178 vd.field_validated_build_sc(
179 "upgrade",
180 build_character_legend_sc,
181 validate_script_value_no_breakdown,
182 );
183 vd.field_validated_build_sc(
184 "complete",
185 |key| {
186 let mut sc = build_character_legend_sc(key);
187 sc.define_name("can_afford_current_level", Scopes::Bool, key);
188 sc
189 },
190 validate_script_value_no_breakdown,
191 );
192}
193
194#[derive(Clone, Debug)]
195pub struct LegendSeed {}
196
197inventory::submit! {
198 ItemLoader::Normal(GameFlags::Ck3, Item::LegendSeed, LegendSeed::add)
199}
200
201impl LegendSeed {
202 pub fn add(db: &mut Db, key: Token, block: Block) {
203 db.add(Item::LegendSeed, key, block, Box::new(Self {}));
204 }
205}
206
207impl DbKind for LegendSeed {
208 fn validate(&self, key: &Token, block: &Block, data: &Everything) {
209 let loca = format!("legend_{key}");
210 data.verify_exists_implied(Item::Localization, &loca, key);
211 let loca = format!("legend_{key}_desc");
212 data.verify_exists_implied(Item::Localization, &loca, key);
213
214 let mut vd = Validator::new(block, data);
215 vd.req_field("quality");
216 vd.req_field("type");
217 vd.req_field("chronicle");
218
219 vd.field_choice("quality", LEGEND_QUALITY);
220 vd.field_item("type", Item::LegendType);
221 vd.field_trigger_rooted("is_shown", Tooltipped::No, Scopes::Character);
222 vd.field_trigger_rooted("is_valid", Tooltipped::Yes, Scopes::Character);
224
225 if let Some(chronicle_token) = vd.field_value("chronicle").cloned() {
226 data.verify_exists(Item::LegendChronicle, &chronicle_token);
227
228 if let Some((_, _, chronicle)) =
229 data.get_item::<LegendChronicle>(Item::LegendChronicle, chronicle_token.as_str())
230 {
231 vd.field_validated_key_block("chronicle_properties", |key, block, data| {
232 let mut found_properties = TigerHashSet::default();
233 let mut sc = ScopeContext::new(Scopes::Character, key);
234 let mut vd = Validator::new(block, data);
235 vd.unknown_fields(|key, bv| {
236 if let Some(scopes) = chronicle.properties.get(key).copied() {
237 found_properties.insert(key.clone());
238
239 match bv {
240 BV::Value(value) => {
241 validate_target(value, data, &mut sc, scopes);
242 }
243 BV::Block(block) => {
244 let mut vd = Validator::new(block, data);
245 vd.field_target("target", &mut sc, scopes);
246 vd.field_trigger("is_valid", Tooltipped::No, &mut sc);
247 }
248 }
249 } else {
250 let msg =
251 format!("property {key} not found in {chronicle_token} chronicle");
252 err(ErrorKey::Validation).msg(msg).loc(key).push();
253 }
254 });
255
256 for property in chronicle.properties.keys() {
257 if !found_properties.contains(property) {
258 let msg = format!("property {property} not found");
259 err(ErrorKey::Validation)
260 .msg(msg)
261 .loc(key)
262 .loc_msg(property, "from here")
263 .push();
264 }
265 }
266 });
267 vd.field_validated_block("chronicle_chapters", |block, data| {
268 let mut vd = Validator::new(block, data);
269 vd.unknown_value_fields(|key, value| {
270 if !chronicle.chapters.contains(key) {
271 let msg =
272 format!("chapter {key} not found in {chronicle_token} chronicle");
273 err(ErrorKey::Validation).msg(msg).loc(key).push();
274 }
275 data.verify_exists(Item::Localization, value);
276 });
277 });
278
279 if let Some(value) = vd.field_value("type") {
281 data.validate_call(
282 Item::LegendType,
283 key,
284 block,
285 &mut build_impact_on_complete_sc(chronicle, value),
286 );
287 }
288 }
289 }
290 }
291}
292
293#[derive(Clone, Debug)]
294pub struct LegendChronicle {
295 pub properties: TigerHashMap<Token, Scopes>,
296 chapters: TigerHashSet<Token>,
297}
298
299inventory::submit! {
300 ItemLoader::Normal(GameFlags::Ck3, Item::LegendChronicle, LegendChronicle::add)
301}
302
303impl LegendChronicle {
304 pub fn add(db: &mut Db, key: Token, block: Block) {
305 let mut properties = TigerHashMap::default();
306 let mut chapters = TigerHashSet::default();
307
308 if let Some(block) = block.get_field_block("properties") {
309 for (key, value) in block.iter_assignments() {
310 if let Some(scopes) = Scopes::from_snake_case(value.as_str()) {
311 properties.insert(key.clone(), scopes);
312 }
313 }
314 }
315
316 if let Some(block) = block.get_field_block("chapters") {
317 for (key, _) in block.iter_assignments() {
318 chapters.insert(key.clone());
319 }
320 }
321 db.add(Item::LegendChronicle, key, block, Box::new(Self { properties, chapters }));
322 }
323}
324
325impl DbKind for LegendChronicle {
326 fn add_subitems(&self, _key: &Token, block: &Block, db: &mut Db) {
327 if let Some(block) = block.get_field_block("properties") {
328 for (key, _) in block.iter_assignments() {
329 db.add_flag(Item::LegendProperty, key.clone());
330 }
331 }
332
333 if let Some(block) = block.get_field_block("chapters") {
334 for (key, _) in block.iter_assignments() {
335 db.add_flag(Item::LegendChapter, key.clone());
336 }
337 }
338 }
339
340 fn validate(&self, key: &Token, block: &Block, data: &Everything) {
341 let mut vd = Validator::new(block, data);
342
343 if !vd.field_validated_build_sc(
344 "name",
345 |key| build_impact_on_complete_sc(self, key),
346 validate_desc,
347 ) {
348 let loca = format!("legend_chronicle_{key}");
349 data.verify_exists_implied(Item::Localization, &loca, key);
350 }
351
352 if !vd.field_validated_build_sc(
353 "description",
354 |key| build_impact_on_complete_sc(self, key),
355 validate_desc,
356 ) {
357 let loca = format!("legend_chronicle_{key}_desc");
358 data.verify_exists_implied(Item::Localization, &loca, key);
359 }
360
361 vd.field_item("portrait_animation", Item::PortraitAnimation);
363
364 vd.field_validated_block("properties", |block, data| {
365 let mut vd = Validator::new(block, data);
366 vd.unknown_value_fields(|_, value| {
367 if Scopes::from_snake_case(value.as_str()).is_none() {
368 let msg = "expected a valid scope type";
369 err(ErrorKey::Validation).msg(msg).loc(value).push();
370 }
371 });
372 });
373 vd.field_validated_block_build_sc(
374 "chapters",
375 |key| build_root_properties_sc(Scopes::Legend, self, key),
376 |block, data, sc| {
377 let mut vd = Validator::new(block, data);
378 vd.unknown_value_fields(|_, value| {
379 data.validate_localization_sc(value.as_str(), sc);
380 });
381 },
382 );
383 vd.field_validated_block("impact", |block, data| {
385 let mut vd = Validator::new(block, data);
386 validate_impact_modifiers(&mut vd);
387 vd.field_effect_builder("on_complete", Tooltipped::Yes, |key| {
389 build_impact_on_complete_sc(self, key)
390 });
391 });
392 }
393}
394
395fn build_impact_on_complete_sc(chronicle: &LegendChronicle, key: &Token) -> ScopeContext {
396 let mut sc = build_root_properties_sc(Scopes::Character, chronicle, key);
397 sc.define_name("protagonist", Scopes::Character, key);
398 sc
399}
400
401fn build_root_properties_sc(
402 root: Scopes,
403 chronicle: &LegendChronicle,
404 key: &Token,
405) -> ScopeContext {
406 let mut sc = ScopeContext::new(root, key);
407 for (property, scopes) in &chronicle.properties {
408 sc.define_name(property.as_str(), *scopes, key);
409 }
410 sc
411}