1use crate::block::{BV, Block};
2use crate::ck3::data::scripted_animations::validate_scripted_animation;
3use crate::ck3::tables::misc::PROVINCE_FILTERS;
4use crate::ck3::validate::{validate_ai_targets, validate_cost, validate_quick_trigger};
5use crate::context::ScopeContext;
6use crate::db::{Db, DbKind};
7use crate::desc::validate_desc;
8use crate::everything::Everything;
9use crate::game::GameFlags;
10use crate::item::{Item, ItemLoader};
11use crate::report::{ErrorKey, err, warn};
12use crate::scopes::Scopes;
13use crate::token::Token;
14use crate::tooltipped::Tooltipped;
15use crate::trigger::validate_trigger;
16use crate::validate::{validate_duration, validate_modifiers_with_base};
17use crate::validator::Validator;
18
19#[derive(Clone, Debug)]
20pub struct ActivityType {}
21
22inventory::submit! {
23 ItemLoader::Normal(GameFlags::Ck3, Item::ActivityType, ActivityType::add)
24}
25
26impl ActivityType {
27 pub fn add(db: &mut Db, key: Token, block: Block) {
28 db.add(Item::ActivityType, key, block, Box::new(Self {}));
29 }
30}
31
32impl DbKind for ActivityType {
33 fn add_subitems(&self, _key: &Token, block: &Block, db: &mut Db) {
34 if let Some(block) = block.get_field_block("options") {
35 for (key, block) in block.iter_definitions() {
36 db.add_flag(Item::ActivityOptionCategory, key.clone());
37 for (key, _) in block.iter_definitions() {
38 db.add_flag(Item::ActivityOption, key.clone());
39 }
40 }
41 }
42 if let Some(block) = block.get_field_block("phases") {
43 for (key, _) in block.iter_definitions() {
44 db.add_flag(Item::ActivityPhase, key.clone());
45 }
46 }
47 if let Some(block) = block.get_field_block("special_guests") {
48 for (key, _) in block.iter_definitions() {
49 db.add_flag(Item::SpecialGuest, key.clone());
50 }
51 }
52 if let Some(block) = block.get_field_block("window_characters") {
54 for (key, _) in block.iter_definitions() {
55 db.add_flag(Item::SpecialGuest, key.clone());
56 }
57 }
58 if let Some(vec) = block.get_field_list("guest_subsets") {
59 for token in vec {
60 db.add_flag(Item::GuestSubset, token.clone());
61 }
62 }
63 }
64
65 fn validate(&self, key: &Token, block: &Block, data: &Everything) {
66 let define = "NGameIcons|ACTIVITY_TYPE_ICON_PATH";
67 data.verify_icon(define, key, ".dds");
68 data.verify_icon(define, key, "_header.dds");
69
70 let define = "NGameIcons|ACTIVITY_HEADER_BACKGROUND_PATH";
71 data.verify_icon(define, key, ".dds");
72
73 let mut vd = Validator::new(block, data);
74 let has_special_option = block.has_key("special_option_category");
75
76 data.verify_exists(Item::Localization, key);
77 let loca = format!("{key}_name");
78 data.verify_exists_implied(Item::Localization, &loca, key);
79 let loca = format!("{key}_desc");
80 data.verify_exists_implied(Item::Localization, &loca, key);
81 let loca = format!("{key}_owner");
82 data.verify_exists_implied(Item::Localization, &loca, key);
83 let loca = format!("{key}_destination_selection");
84 data.verify_exists_implied(Item::Localization, &loca, key);
85 let loca = format!("{key}_selection_tooltip");
86 data.verify_exists_implied(Item::Localization, &loca, key);
87 let loca = format!("{key}_guest_help_text");
88 data.verify_exists_implied(Item::Localization, &loca, key);
89 let loca = format!("{key}_predicted_cost");
90 data.verify_exists_implied(Item::Localization, &loca, key);
91 let loca = format!("TRAVEL_NAME_FOR_{key}");
92 data.verify_exists_implied(Item::Localization, &loca, key);
93
94 if block.has_key("province_description") {
95 let mut sc = ScopeContext::new(Scopes::Province, key);
96 sc.define_name("host", Scopes::Character, key);
97 if has_special_option {
98 sc.define_name("special_option", Scopes::Flag, key);
99 }
100 vd.field_validated_sc("province_description", &mut sc, validate_desc);
101 } else {
102 let loca = format!("{key}_province_desc");
103 data.verify_exists_implied(Item::Localization, &loca, key);
104 }
105
106 if block.has_key("host_description") {
107 let mut sc = ScopeContext::new(Scopes::Character, key);
108 vd.field_validated_sc("host_description", &mut sc, validate_desc);
109 } else {
110 let loca = format!("{key}_host_desc");
111 data.verify_exists_implied(Item::Localization, &loca, key);
112 }
113
114 vd.field_integer("sort_order");
115 vd.field_bool("notify_player_can_join_activity");
116 vd.field_item("activity_group_type", Item::ActivityGroupType);
117
118 if block.has_key("guest_description") {
120 let mut sc = ScopeContext::new(Scopes::Character, key);
121 vd.field_validated_sc("guest_description", &mut sc, validate_desc);
122 } else {
123 let loca = format!("{key}_guest_desc");
124 data.verify_exists_implied(Item::Localization, &loca, key);
125 }
126
127 let ch_host_activity_sc = |key: &Token| {
128 let mut sc = ScopeContext::new(Scopes::Character, key);
129 sc.define_name("host", Scopes::Character, key);
130 sc.define_name("activity", Scopes::Activity, key);
131 sc
132 };
133 let ac_host_activity_sc = |key: &Token| {
134 let mut sc = ScopeContext::new(Scopes::Activity, key);
135 sc.define_name("host", Scopes::Character, key);
136 sc.define_name("activity", Scopes::Activity, key);
137 sc
138 };
139
140 if block.has_key("conclusion_description") {
141 let mut sc = ch_host_activity_sc(key);
142 vd.field_validated_sc("conclusion_description", &mut sc, validate_desc);
143 } else {
144 let loca = format!("{key}_conclusion_desc");
145 data.verify_exists_implied(Item::Localization, &loca, key);
146 }
147
148 let mut join_chance_sc = ScopeContext::new(Scopes::Character, key);
149 join_chance_sc.define_name("host", Scopes::Character, key);
150 join_chance_sc.define_name("minimal_travel_time", Scopes::Value, key);
151 join_chance_sc.define_name("estimated_arrival_diff_days", Scopes::Value, key);
153 join_chance_sc.define_list("special_guests", Scopes::Character, key);
154
155 let mut special_guests_sc = ScopeContext::new(Scopes::Character, key);
156 if has_special_option {
157 special_guests_sc.define_name("special_option", Scopes::Flag, key);
158 }
159 special_guests_sc.define_list("special_guests", Scopes::Character, key);
160
161 let mut max_guests_sc = ScopeContext::new(Scopes::all(), key);
163
164 if let Some(block) = block.get_field_block("special_guests") {
165 for (key, _) in block.iter_definitions() {
166 join_chance_sc.define_name(key.as_str(), Scopes::Character, key);
167 special_guests_sc.define_name(key.as_str(), Scopes::Character, key);
168 }
169 }
170 if let Some(block) = block.get_field_block("options") {
171 for (_, block) in block.iter_definitions() {
172 for (key, _) in block.iter_definitions() {
173 join_chance_sc.define_name(key.as_str(), Scopes::Flag, key);
174 max_guests_sc.define_name(key.as_str(), Scopes::Flag, key);
175 }
176 }
177 }
178
179 let mut sc = ScopeContext::new(Scopes::Character, key);
180
181 vd.field_trigger("is_shown", Tooltipped::No, &mut sc);
182 vd.field_trigger("can_plan", Tooltipped::Yes, &mut sc);
183 vd.field_trigger("can_start", Tooltipped::Yes, &mut sc);
187 vd.field_trigger("can_start_showing_failures_only", Tooltipped::FailuresOnly, &mut sc);
188
189 vd.field_bool("can_always_plan");
190 vd.field_script_value("num_pickable_phases", &mut sc);
191 vd.field_script_value("max_pickable_phases_per_province", &mut sc);
192 vd.field_validated_block_sc("wait_time_before_start", &mut sc, validate_duration);
193 vd.field_validated_block_sc("max_guest_arrival_delay_time", &mut sc, validate_duration);
194 vd.field_numeric("max_route_deviation_mult");
195
196 vd.field_validated_block_sc("cooldown", &mut sc, validate_duration);
197
198 vd.replaced_field("is_grand_activity", "activity_group_type");
199 vd.field_bool("is_single_location");
200 vd.field_choice("planner_type", &["province", "holder"]);
201
202 vd.field_script_value_no_breakdown("ai_will_do", &mut sc);
203 vd.field_integer("ai_check_interval");
204 vd.field_validated_key_block("ai_check_interval_by_tier", |key, b, data| {
205 let mut vd = Validator::new(b, data);
206 for tier in &["barony", "county", "duchy", "kingdom", "empire", "hegemony"] {
207 vd.req_field(tier);
208 vd.field_integer(tier);
209 }
210 if block.has_key("ai_check_interval") {
211 let msg = "must not have both `ai_check_interval` and `ai_check_interval_by_tier`";
212 warn(ErrorKey::Validation).msg(msg).loc(key).push();
213 }
214 });
215 vd.field_script_value_no_breakdown("ai_select_num_provinces", &mut sc);
216
217 vd.field_trigger_builder("is_valid", Tooltipped::No, ac_host_activity_sc);
218 vd.field_effect_builder("on_invalidated", Tooltipped::No, ac_host_activity_sc);
219 vd.field_effect_builder("on_host_death", Tooltipped::No, ac_host_activity_sc);
220
221 vd.field_choice("province_filter", PROVINCE_FILTERS);
222 vd.field_choice("ai_province_filter", PROVINCE_FILTERS);
223 let province_filter = block.get_field_value("province_filter");
224 let ai_province_filter = block.get_field_value("ai_province_filter");
225 if province_filter.is_some_and(|t| t.is("landed_title"))
226 || ai_province_filter.is_some_and(|t| t.is("landed_title"))
227 {
228 if let Some(province_filter) = province_filter {
229 if let Some(ai_province_filter) = ai_province_filter {
230 if province_filter != ai_province_filter {
231 let msg = "for `landed_title`, `province_filter` and `ai_province_filter` must be the same";
232 err(ErrorKey::Validation).msg(msg).loc(ai_province_filter).push();
233 }
234 }
235 }
236 vd.field_item("province_filter_target", Item::Title);
237 } else if province_filter.is_some_and(|t| t.is("geographical_region"))
238 || ai_province_filter.is_some_and(|t| t.is("geographical_region"))
239 {
240 if let Some(province_filter) = province_filter {
241 if let Some(ai_province_filter) = ai_province_filter {
242 if province_filter != ai_province_filter {
243 let msg = "for `geographical_region`, `province_filter` and `ai_province_filter` must be the same";
244 err(ErrorKey::Validation).msg(msg).loc(ai_province_filter).push();
245 }
246 }
247 }
248 vd.field_item("province_filter_target", Item::Region);
249 } else {
250 vd.ban_field(
251 "province_filter_target",
252 || "`landed_title` or `geographical_region` filters",
253 );
254 }
255
256 let mut sc = ScopeContext::new(Scopes::Province, key);
257 sc.define_name("host", Scopes::Character, key);
258 vd.field_script_value_no_breakdown("province_score", &mut sc);
259
260 if has_special_option {
261 sc.define_name("special_option", Scopes::Flag, key);
262 }
263 vd.field_trigger("is_location_valid", Tooltipped::No, &mut sc);
264
265 sc.define_name("score", Scopes::Value, key);
266 vd.field_script_value_no_breakdown("ai_will_select_province", &mut sc);
267
268 vd.field_integer("max_province_icons");
269
270 vd.field_item("special_option_category", Item::ActivityOptionCategory);
271 let special_option_category = block.get_field_value("special_option_category");
272
273 vd.field_validated_block("options", |block, data| {
274 let mut vd = Validator::new(block, data);
275 vd.unknown_block_fields(|key, block| {
276 data.verify_exists(Item::Localization, key);
278 let mut vd = Validator::new(block, data);
279 let is_special_option =
280 special_option_category.is_some_and(|special| key.is(special.as_str()));
281 if !is_special_option {
282 let define = "NGameIcons|ACTIVITY_OPTION_CATEGORY_TEXTURE_PATH";
283 data.verify_icon(define, key, ".dds");
284 }
285 vd.unknown_block_fields(|key, block| {
286 validate_option(key, block, data, has_special_option, is_special_option);
287 });
288 });
289 });
290
291 vd.field_validated_block("phases", |block, data| {
292 let mut vd = Validator::new(block, data);
293 vd.unknown_block_fields(|key, block| {
294 validate_phase(key, block, data, has_special_option);
295 });
296 });
297
298 vd.field_validated_key_block("cost", |key, block, data| {
299 let mut sc = ScopeContext::new(Scopes::Character, key);
300 if has_special_option {
301 sc.define_name("special_option", Scopes::Flag, key);
302 }
303 sc.define_name("province", Scopes::Province, key);
304 sc.define_list("provinces", Scopes::Province, key);
305 validate_cost(block, data, &mut sc);
306 });
307
308 let mut sc = ScopeContext::new(Scopes::Character, key);
309 vd.field_validated_block_sc("ui_predicted_cost", &mut sc, validate_cost);
310
311 vd.field_script_value("max_guests", &mut max_guests_sc);
312 vd.field_script_value("reserved_guest_slots", &mut max_guests_sc);
314 vd.field_bool("allow_zero_guest_invites");
315
316 vd.field_validated_block("guest_invite_rules", |block, data| {
317 let mut vd = Validator::new(block, data);
318 vd.field_validated_block("rules", |block, data| {
319 validate_rules(block, data);
320 });
321 vd.field_validated_block("defaults", |block, data| {
322 validate_rules(block, data);
324 });
325 });
326
327 let mut sc = ScopeContext::new(Scopes::Character, key);
328 sc.define_name("host", Scopes::Character, key);
329 if has_special_option {
330 sc.define_name("special_option", Scopes::Flag, key);
331 }
332 vd.field_trigger("can_be_activity_guest", Tooltipped::No, &mut sc);
333
334 vd.field_list("guest_subsets");
335
336 vd.field_validated_block("special_guests", |block, data| {
337 let mut vd = Validator::new(block, data);
338 vd.unknown_block_fields(|key, block| {
339 validate_special_guest(
340 key,
341 block,
342 data,
343 has_special_option,
344 &mut special_guests_sc,
345 );
346 });
347 });
348
349 vd.field_validated_block("locales", |block, data| {
350 let mut vd = Validator::new(block, data);
351 vd.unknown_block_fields(|_, block| {
353 let mut vd = Validator::new(block, data);
354 vd.field_trigger_builder("is_available", Tooltipped::No, ac_host_activity_sc);
355 vd.field_list_items("locales", Item::ActivityLocale);
356 });
357 });
358
359 let mut sc = ch_host_activity_sc(key);
360 vd.field_validated_block_sc("locale_cooldown", &mut sc, validate_duration);
361 vd.field_validated_block_sc("auto_select_locale_cooldown", &mut sc, validate_duration);
362 for field in &[
363 "on_enter_travel_state",
364 "on_enter_passive_state",
365 "on_enter_active_state",
366 "on_leave_travel_state",
367 "on_leave_passive_state",
368 "on_leave_active_state",
369 "on_travel_state_pulse",
370 "on_passive_state_pulse",
371 "on_active_state_pulse",
372 "on_complete",
373 ] {
374 vd.field_effect_builder(field, Tooltipped::No, ch_host_activity_sc);
375 }
376 vd.field_effect_builder("on_start", Tooltipped::No, |key| {
377 let mut sc = ch_host_activity_sc(key);
378 sc.change_root(Scopes::Activity, key);
379 sc
380 });
381
382 sc.change_root(Scopes::None, key);
383 vd.field_validated_block_sc("early_locale_opening_duration", &mut sc, validate_duration);
384
385 vd.field_bool("open_invite");
386
387 vd.field_validated_block("host_intents", |block, data| {
388 let mut vd = Validator::new(block, data);
389 vd.field_list_items("intents", Item::ActivityIntent);
390 vd.field_item("default", Item::ActivityIntent);
391 vd.field_list_items("player_defaults", Item::ActivityIntent);
392 });
393 vd.field_validated_block("guest_intents", |block, data| {
394 let mut vd = Validator::new(block, data);
395 vd.field_list_items("intents", Item::ActivityIntent);
396 vd.field_item("default", Item::ActivityIntent);
397 vd.field_list_items("player_defaults", Item::ActivityIntent);
398 });
399
400 vd.field_validated_block_sc(
401 "guest_join_chance",
402 &mut join_chance_sc,
403 validate_modifiers_with_base,
404 );
405
406 vd.field_validated_block("activity_window_widgets", |block, data| {
407 let mut vd = Validator::new(block, data);
408 vd.unknown_value_fields(|key, _| {
409 let pathname = format!("gui/activity_window_widgets/{key}.gui");
410 data.verify_exists_implied(Item::File, &pathname, key);
411 });
413 });
414
415 vd.field_validated_block("activity_planner_widgets", |block, data| {
416 let mut vd = Validator::new(block, data);
417 vd.unknown_value_fields(|key, _| {
418 let pathname = format!("gui/activity_window_widgets/{key}.gui");
419 data.verify_exists_implied(Item::File, &pathname, key);
420 });
422 });
423
424 let mut sc = ScopeContext::new(Scopes::Activity, key);
425 sc.define_name("host", Scopes::Character, key);
426 sc.define_name("activity", Scopes::Activity, key);
427 vd.multi_field_validated("map_entity", |bv, data| match bv {
428 BV::Value(token) => data.verify_exists(Item::Entity, token),
429 BV::Block(block) => {
430 let mut vd = Validator::new(block, data);
431 vd.field_trigger("trigger", Tooltipped::No, &mut sc);
432 vd.field_item("reference", Item::Entity);
433 }
434 });
435 vd.multi_field_validated_block("background", |block, data| {
436 let mut vd = Validator::new(block, data);
437 vd.field_trigger("trigger", Tooltipped::No, &mut sc);
438 vd.field_item("texture", Item::File);
439 vd.field_item("environment", Item::PortraitEnvironment);
440 vd.field_item("ambience", Item::Sound);
441 vd.field_item("music", Item::Music);
442 });
443 vd.multi_field_validated_block("locale_background", |block, data| {
444 let mut vd = Validator::new(block, data);
445 vd.field_trigger("trigger", Tooltipped::No, &mut sc);
446 vd.field_item("texture", Item::File);
447 vd.field_item("environment", Item::PortraitEnvironment);
448 vd.field_item("ambience", Item::Sound);
449 vd.field_item("music", Item::Music);
450 });
451 vd.field_validated_block("window_characters", |block, data| {
452 let mut vd = Validator::new(block, data);
453 vd.unknown_block_fields(|key, block| {
454 validate_window_characters(key, block, data);
455 });
456 });
457 vd.field_validated_key_block("travel_entourage_selection", |key, block, data| {
458 validate_tes(key, block, data, has_special_option);
459 });
460
461 vd.field_validated_block("pulse_actions", |block, data| {
463 let mut vd = Validator::new(block, data);
464 vd.field_list_items("entries", Item::ActivityPulseAction);
465 vd.field_numeric_range("chance_of_no_event", 0.0..=100.0);
466 });
467 }
468}
469
470fn validate_rules(block: &Block, data: &Everything) {
471 let mut vd = Validator::new(block, data);
472 vd.integer_keys(|_, bv| {
473 if let Some(token) = bv.expect_value() {
474 data.verify_exists(Item::GuestInviteRule, token);
475 }
476 });
477}
478
479pub fn validate_tes(key: &Token, block: &Block, data: &Everything, has_special_option: bool) {
480 let mut sc = ScopeContext::new(Scopes::Character, key);
481 sc.define_name("host", Scopes::Character, key);
482 sc.define_name("owner", Scopes::Character, key);
483 sc.define_list("special_guests", Scopes::Character, key);
485 if has_special_option {
486 sc.define_name("special_option", Scopes::Flag, key);
487 }
488 let mut vd = Validator::new(block, data);
489 vd.field_script_value("weight", &mut sc);
490 let mut sc = ScopeContext::new(Scopes::None, key);
491 vd.field_script_value("max", &mut sc);
492 vd.field_script_value("ai_max", &mut sc);
493 vd.field_integer("invite_rule_order");
494}
495
496fn validate_window_characters(key: &Token, block: &Block, data: &Everything) {
497 let mut vd = Validator::new(block, data);
498 let loca = format!("activity_window_character_{key}");
499 data.verify_exists_implied(Item::Localization, &loca, key);
500 vd.field_item("camera", Item::PortraitCamera);
501 vd.field_effect_builder("effect", Tooltipped::No, |key| {
502 let mut sc = ScopeContext::new(Scopes::Activity, key);
503 sc.define_name("activity", Scopes::Activity, key);
504 sc.define_name("host", Scopes::Character, key);
505 sc.define_name("player", Scopes::Character, key);
506 sc.define_list("characters", Scopes::Character, key);
507 sc
508 });
509
510 let mut sc = ScopeContext::new(Scopes::Activity, key);
511 sc.define_name("activity", Scopes::Activity, key);
512 sc.define_name("host", Scopes::Character, key);
513 sc.define_name("player", Scopes::Character, key);
514 sc.define_name("character", Scopes::Character, key);
515 vd.field_validated_sc("scripted_animation", &mut sc, validate_scripted_animation);
516 vd.field_item("animation", Item::PortraitAnimation);
517}
518
519fn validate_phase(key: &Token, block: &Block, data: &Everything, has_special_option: bool) {
520 data.verify_exists(Item::Localization, key);
521 let loca = format!("{key}_desc");
522 data.verify_exists_implied(Item::Localization, &loca, key);
523
524 let define = "NGameIcons|ACTIVITY_PHASE_ICON_PATH";
525 data.verify_icon(define, key, ".dds");
526
527 let mut vd = Validator::new(block, data);
528 vd.field_bool("is_predefined");
529 let mut sc = ScopeContext::new(Scopes::None, key);
530 vd.field_script_value("number_of_picks", &mut sc);
531 vd.field_integer("order");
533 vd.field_choice(
534 "location_source",
535 &["pickable", "first_picked_phase", "last_picked_phase", "scripted"],
536 );
537 if block.field_value_is("location_source", "scripted") {
538 vd.field_effect_builder("select_scripted_location", Tooltipped::No, |key| {
539 let mut sc = ScopeContext::new(Scopes::Character, key);
540 if has_special_option {
541 sc.define_name("special_option", Scopes::Flag, key);
542 }
543 sc
544 });
545 } else {
546 vd.ban_field("select_scripted_location", || "location_source = scripted");
547 }
548 vd.field_script_value_no_breakdown_builder("ai_will_do", |key| {
549 let mut sc = ScopeContext::new(Scopes::Character, key);
550 sc.define_name("province", Scopes::Province, key);
551 if has_special_option {
552 sc.define_name("special_option", Scopes::Flag, key);
553 }
554 sc
555 });
556
557 let mut sc = ScopeContext::new(Scopes::Character, key);
558 sc.define_name("province", Scopes::Province, key);
559 if has_special_option {
560 sc.define_name("special_option", Scopes::Flag, key);
561 }
562 vd.field_trigger("is_shown", Tooltipped::No, &mut sc);
563 vd.field_trigger("can_pick", Tooltipped::Yes, &mut sc);
564 vd.field_trigger_builder("is_valid", Tooltipped::No, |key| {
565 let mut sc = ScopeContext::new(Scopes::Activity, key);
566 sc.define_name("host", Scopes::Character, key);
567 sc.define_name("province", Scopes::Province, key);
568 sc
569 });
570
571 let mut sc = ScopeContext::new(Scopes::Character, key);
572 sc.define_name("activity", Scopes::Activity, key);
573 sc.define_name("host", Scopes::Character, key);
574 vd.field_effect("on_enter_phase", Tooltipped::No, &mut sc);
575 vd.field_effect("on_phase_active", Tooltipped::No, &mut sc);
576 vd.field_effect("on_end", Tooltipped::No, &mut sc);
577 vd.field_effect("on_monthly_pulse", Tooltipped::No, &mut sc);
578 vd.field_effect("on_weekly_pulse", Tooltipped::No, &mut sc);
579 sc.define_name("province", Scopes::Province, key);
580 vd.field_effect("on_invalidated", Tooltipped::No, &mut sc);
581
582 let mut sc = ScopeContext::new(Scopes::Character, key);
583 sc.define_name("province", Scopes::Province, key);
584 sc.define_name("previous_province", Scopes::Province, key);
585 if has_special_option {
586 sc.define_name("special_option", Scopes::Flag, key);
587 }
588 vd.field_validated_block_sc("cost", &mut sc, validate_cost);
589}
590
591fn validate_special_guest(
592 key: &Token,
593 block: &Block,
594 data: &Everything,
595 has_special_option: bool,
596 special_guests_sc: &mut ScopeContext,
597) {
598 let mut vd = Validator::new(block, data);
599 data.verify_exists(Item::Localization, key);
600 let loca = format!("{key}_desc");
601 data.verify_exists_implied(Item::Localization, &loca, key);
602 let loca = format!("{key}_for_host");
603 data.verify_exists_implied(Item::Localization, &loca, key);
604 let loca = format!("{key}_desc_for_host");
605 data.verify_exists_implied(Item::Localization, &loca, key);
606 let mut sc = ScopeContext::new(Scopes::Character, key);
607 if has_special_option {
608 sc.define_name("special_option", Scopes::Flag, key);
609 }
610 vd.field_trigger("is_shown", Tooltipped::No, &mut sc);
611 vd.field_bool("is_required");
612
613 vd.field_effect("select_character", Tooltipped::No, special_guests_sc);
614
615 special_guests_sc.define_name("host", Scopes::Character, key);
616 vd.field_trigger("can_pick", Tooltipped::No, special_guests_sc);
617 vd.field_effect("on_invite", Tooltipped::No, special_guests_sc);
618
619 vd.field_script_value_no_breakdown_rooted("ai_will_do", Scopes::Character);
620}
621
622#[derive(Clone, Debug)]
623pub struct ActivityLocale {}
624
625inventory::submit! {
626 ItemLoader::Normal(GameFlags::Ck3, Item::ActivityLocale, ActivityLocale::add)
627}
628
629impl ActivityLocale {
630 pub fn add(db: &mut Db, key: Token, block: Block) {
631 db.add(Item::ActivityLocale, key, block, Box::new(Self {}));
632 }
633}
634
635impl DbKind for ActivityLocale {
636 fn validate(&self, key: &Token, block: &Block, data: &Everything) {
637 let mut vd = Validator::new(block, data);
638
639 data.verify_exists(Item::Localization, key);
640 let loca = format!("{key}_desc");
641 data.verify_exists_implied(Item::Localization, &loca, key);
642
643 let define = "NGameIcons|ACTIVITY_LOCALE_ICON_PATH";
644 data.verify_icon(define, key, ".dds");
645
646 let mut sc = ScopeContext::new(Scopes::Character, key);
647 sc.define_name("host", Scopes::Character, key);
648 sc.define_name("activity", Scopes::Activity, key);
649 vd.field_trigger("is_available", Tooltipped::No, &mut sc);
650 vd.field_script_value("chance", &mut sc);
651 vd.field_effect("on_enter_locale", Tooltipped::No, &mut sc);
652 vd.field_script_value_no_breakdown("ai_will_do", &mut sc);
653 vd.field_validated_block_sc("cooldown", &mut sc, validate_duration);
654
655 vd.multi_field_validated_key("visuals", validate_visuals);
656 }
657}
658
659fn validate_visuals(key: &Token, bv: &BV, data: &Everything) {
660 fn validate_visual(visual: &Token, data: &Everything) {
661 let pathname = format!("/gui/activity_locale_widgets/{visual}.gui");
662 data.verify_exists_implied(Item::File, &pathname, visual);
663 }
664
665 match bv {
666 BV::Value(token) => validate_visual(token, data),
667 BV::Block(block) => {
668 let mut vd = Validator::new(block, data);
669 let mut sc = ScopeContext::new(Scopes::Activity, key);
670 sc.define_name("activity", Scopes::Activity, key);
671 sc.define_name("host", Scopes::Character, key);
672 vd.field_trigger("trigger", Tooltipped::No, &mut sc);
673 vd.req_field("reference");
674 if let Some(token) = vd.field_value("reference") {
675 validate_visual(token, data);
676 }
677 }
678 }
679}
680
681#[derive(Clone, Debug)]
682pub struct GuestInviteRule {}
683
684inventory::submit! {
685 ItemLoader::Normal(GameFlags::Ck3, Item::GuestInviteRule, GuestInviteRule::add)
686}
687
688impl GuestInviteRule {
689 pub fn add(db: &mut Db, key: Token, block: Block) {
690 db.add(Item::GuestInviteRule, key, block, Box::new(Self {}));
691 }
692}
693
694impl DbKind for GuestInviteRule {
695 fn validate(&self, key: &Token, block: &Block, data: &Everything) {
696 let mut vd = Validator::new(block, data);
697 data.verify_exists(Item::Localization, key);
698
699 vd.field_effect_builder("effect", Tooltipped::No, |key| {
700 let mut sc = ScopeContext::new(Scopes::Character, key);
701 sc.define_list("characters", Scopes::Character, key);
704 sc
705 });
706 }
707}
708
709#[derive(Clone, Debug)]
710pub struct ActivityPulseAction {}
711
712inventory::submit! {
713 ItemLoader::Normal(GameFlags::Ck3, Item::ActivityPulseAction, ActivityPulseAction::add)
714}
715
716impl ActivityPulseAction {
717 pub fn add(db: &mut Db, key: Token, block: Block) {
718 db.add(Item::ActivityPulseAction, key, block, Box::new(Self {}));
719 }
720}
721
722impl DbKind for ActivityPulseAction {
723 fn validate(&self, key: &Token, block: &Block, data: &Everything) {
724 let mut vd = Validator::new(block, data);
725
726 let loca = format!("{key}_title");
727 data.verify_exists_implied(Item::Localization, &loca, key);
728
729 let define = "NGameIcons|ACTIVITY_PULSE_ACTION_ICON_PATH";
730 let icon = vd.field_value("icon").unwrap_or(key);
731 data.verify_icon(define, icon, ".dds");
732
733 let mut sc = ScopeContext::new(Scopes::Activity, key);
734 sc.define_name("activity", Scopes::Activity, key);
735 sc.define_name("host", Scopes::Character, key);
736 sc.define_name("province", Scopes::Province, key);
737 vd.field_trigger("is_valid", Tooltipped::No, &mut sc);
738 vd.field_script_value("weight", &mut sc);
739 vd.field_effect("effect", Tooltipped::No, &mut sc);
740 }
741}
742
743#[derive(Clone, Debug)]
744pub struct ActivityIntent {}
745
746inventory::submit! {
747 ItemLoader::Normal(GameFlags::Ck3, Item::ActivityIntent, ActivityIntent::add)
748}
749
750impl ActivityIntent {
751 pub fn add(db: &mut Db, key: Token, block: Block) {
752 db.add(Item::ActivityIntent, key, block, Box::new(Self {}));
753 }
754}
755
756impl DbKind for ActivityIntent {
757 fn validate(&self, key: &Token, block: &Block, data: &Everything) {
758 let mut vd = Validator::new(block, data);
759
760 data.verify_exists(Item::Localization, key);
761 let loca = format!("{key}_desc");
762 data.verify_exists_implied(Item::Localization, &loca, key);
763
764 let define = "NGameIcons|ACTIVITY_INTENTS_ICON_PATH";
765 let icon = vd.field_value("icon").unwrap_or(key);
766 data.verify_icon(define, icon, ".dds");
767
768 vd.field_bool("auto_complete");
769
770 for field in &["is_shown", "is_valid"] {
771 vd.field_trigger_builder(field, Tooltipped::No, |key| {
772 let mut sc = ScopeContext::new(Scopes::Character, key);
773 sc.define_name("magnificence", Scopes::Value, key);
774 sc.define_name("special_option", Scopes::Flag, key);
775 sc
776 });
777 }
778
779 vd.field_trigger_builder("is_target_valid", Tooltipped::No, |key| {
780 let mut sc = ScopeContext::new(Scopes::Character, key);
781 sc.define_name("target", Scopes::Character, key);
782 sc.define_name("special_option", Scopes::Flag, key);
783 sc
784 });
785
786 vd.field_effect_rooted("on_invalidated", Tooltipped::No, Scopes::Character);
787 vd.field_effect_builder("on_target_invalidated", Tooltipped::No, |key| {
788 let mut sc = ScopeContext::new(Scopes::Character, key);
789 sc.define_name("target", Scopes::Character, key);
790 sc
791 });
792
793 vd.field_script_value_no_breakdown_builder("ai_will_do", |key| {
794 let mut sc = ScopeContext::new(Scopes::Character, key);
795 sc.define_name("activity", Scopes::Activity, key);
796 sc
797 });
798
799 vd.multi_field_validated_block("ai_targets", validate_ai_targets);
800 vd.field_validated_block("ai_target_quick_trigger", validate_quick_trigger);
801
802 vd.field_script_value_no_breakdown_builder("ai_target_score", |key| {
803 let mut sc = ScopeContext::new(Scopes::Character, key);
804 sc.define_name("target", Scopes::Character, key);
805 sc.define_name("special_option", Scopes::Flag, key);
806 sc
807 });
808
809 let mut sc = ScopeContext::new(Scopes::Character, key);
810 vd.field_validated_sc("scripted_animation", &mut sc, validate_scripted_animation);
811 }
812}
813
814fn validate_option(
815 key: &Token,
816 block: &Block,
817 data: &Everything,
818 has_special_option: bool,
819 is_special_option: bool,
820) {
821 let mut vd = Validator::new(block, data);
822 data.verify_exists(Item::Localization, key);
823 let loca = format!("{key}_desc");
824 data.verify_exists_implied(Item::Localization, &loca, key);
825 if is_special_option {
826 data.verify_icon("NGameIcons|ACTIVITY_OPTION_TEXTURE_PATH", key, ".dds");
827 data.verify_icon("NGameIcons|ACTIVITY_OPTION_ICON_PATH", key, "_icon.dds");
828 }
829 let mut sc = ScopeContext::new(Scopes::Character, key);
830 if has_special_option {
831 sc.define_name("special_option", Scopes::Flag, key);
832 }
833 vd.field_trigger("is_shown", Tooltipped::No, &mut sc);
834 vd.field_trigger("is_valid", Tooltipped::Yes, &mut sc);
835 vd.field_script_value_no_breakdown("ai_will_do", &mut sc);
836
837 vd.field_effect_builder("on_start", Tooltipped::No, |key| {
838 let mut sc = ScopeContext::new(Scopes::Activity, key);
839 sc.define_name("activity", Scopes::Activity, key);
840 sc.define_name("host", Scopes::Character, key);
841 sc
842 });
843
844 vd.field_validated("default", |bv, data| {
845 match bv {
846 BV::Value(token) => {
847 if !token.is("yes") {
848 let msg = "expected `default = yes`";
849 warn(ErrorKey::Validation).msg(msg).loc(token).push();
850 }
851 }
852 BV::Block(block) => {
853 let mut sc = ScopeContext::new(Scopes::Character, key);
855 validate_trigger(block, data, &mut sc, Tooltipped::No);
856 }
857 }
858 });
859
860 vd.field_list_items("blocked_intents", Item::ActivityIntent);
862 vd.field_list_items("blocked_phases", Item::ActivityPhase);
863
864 let mut sc = ScopeContext::new(Scopes::Character, key);
865 vd.field_validated_block_sc("cost", &mut sc, validate_cost);
866
867 vd.field_validated_key_block("travel_entourage_selection", |key, block, data| {
868 validate_tes(key, block, data, has_special_option);
869 });
870}
871
872#[derive(Clone, Debug)]
873pub struct ActivityGroupType {}
874
875inventory::submit! {
876 ItemLoader::Normal(GameFlags::Ck3, Item::ActivityGroupType, ActivityGroupType::add)
877}
878
879impl ActivityGroupType {
880 pub fn add(db: &mut Db, key: Token, block: Block) {
881 db.add(Item::ActivityGroupType, key, block, Box::new(Self {}));
882 }
883}
884
885impl DbKind for ActivityGroupType {
886 fn validate(&self, _key: &Token, block: &Block, data: &Everything) {
887 let mut vd = Validator::new(block, data);
888
889 vd.field_integer("sort_order");
890 vd.field_list("gui_tags");
891 }
892}