tiger_lib/ck3/data/
schemes.rs

1use crate::block::Block;
2use crate::ck3::modif::ModifKinds;
3use crate::ck3::tables::misc::{AGENT_SLOT_CONTRIBUTION_TYPE, AI_TARGETS};
4use crate::ck3::validate::validate_cost;
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::modif::validate_modifs;
12use crate::report::{ErrorKey, warn};
13use crate::scopes::Scopes;
14use crate::script_value::validate_non_dynamic_script_value;
15use crate::token::Token;
16use crate::tooltipped::Tooltipped;
17use crate::validate::{validate_duration, validate_modifiers_with_base};
18use crate::validator::Validator;
19
20#[derive(Clone, Debug)]
21pub struct Scheme {}
22
23inventory::submit! {
24    ItemLoader::Normal(GameFlags::Ck3, Item::Scheme, Scheme::add)
25}
26
27impl Scheme {
28    pub fn add(db: &mut Db, key: Token, block: Block) {
29        db.add(Item::Scheme, key, block, Box::new(Self {}));
30    }
31}
32
33impl DbKind for Scheme {
34    fn validate(&self, key: &Token, block: &Block, data: &Everything) {
35        fn sc_secrecy(key: &Token) -> ScopeContext {
36            let mut sc = ScopeContext::new(Scopes::Scheme, key);
37            let target_scopes =
38                Scopes::Character | Scopes::LandedTitle | Scopes::Culture | Scopes::Faith;
39            sc.define_name("target", target_scopes, key);
40            sc.define_name("owner", Scopes::Character, key);
41            sc.define_name("exposed", Scopes::Bool, key);
42            sc
43        }
44
45        let mut vd = Validator::new(block, data);
46        let target_scopes =
47            Scopes::Character | Scopes::LandedTitle | Scopes::Culture | Scopes::Faith;
48        let mut sc = ScopeContext::new(Scopes::Character, key);
49        sc.define_name("scheme", Scopes::Scheme, key);
50        sc.define_name("target", target_scopes, key);
51        sc.define_name("owner", Scopes::Character, key);
52        sc.define_name("exposed", Scopes::Bool, key);
53
54        // let modif = format!("max_{key}_schemes_add");
55        // data.verify_exists_implied(Item::ModifierFormat, &modif, key);
56        // let modif = format!("{key}_scheme_power_add");
57        // data.verify_exists_implied(Item::ModifierFormat, &modif, key);
58        // let modif = format!("{key}_scheme_power_mult");
59        // data.verify_exists_implied(Item::ModifierFormat, &modif, key);
60        // let modif = format!("{key}_scheme_resistance_add");
61        // data.verify_exists_implied(Item::ModifierFormat, &modif, key);
62        // let modif = format!("{key}_scheme_resistance_mult");
63        // data.verify_exists_implied(Item::ModifierFormat, &modif, key);
64
65        data.verify_exists(Item::Localization, key);
66        let loca = format!("{key}_action");
67        data.verify_exists_implied(Item::Localization, &loca, key);
68
69        vd.req_field("desc");
70        vd.field_validated_sc("desc", &mut sc, validate_desc);
71        vd.req_field("success_desc");
72        vd.field_validated_sc("success_desc", &mut sc, validate_desc); // undocumented
73        vd.field_validated_sc("discovery_desc", &mut sc, validate_desc); // undocumented
74
75        vd.req_field("skill");
76        vd.field_item("skill", Item::Skill);
77
78        // "political" is undocumented
79        vd.field_choice("category", &["personal", "contract", "hostile", "political"]);
80        vd.field_choice("target_type", &["character", "title", "culture", "faith", "nothing"]);
81
82        vd.advice_field("hostile", "deprecated; replaced with `category`");
83        vd.field_bool("hostile");
84
85        let icon = vd.field_value("icon").unwrap_or(key);
86        data.verify_icon("NGameIcons|SCHEME_TYPE_ICON_PATH", icon, ".dds");
87        vd.field_item("illustration", Item::File);
88
89        vd.field_trigger("allow", Tooltipped::Yes, &mut sc);
90        vd.field_trigger("valid", Tooltipped::No, &mut sc);
91
92        vd.field_integer("agent_join_threshold");
93        vd.field_integer("agent_leave_threshold");
94        vd.field_bool("uses_resistance");
95
96        vd.field_bool("is_basic");
97
98        vd.field_trigger("valid_agent", Tooltipped::No, &mut sc);
99
100        vd.field_list_choice("agent_groups_owner_perspective", AI_TARGETS);
101        vd.field_list_choice("agent_groups_target_character_perspective", AI_TARGETS);
102
103        vd.field_script_value("odds_prediction", &mut sc);
104
105        vd.field_validated_key_block("agent_join_chance", |key, block, data| {
106            let mut sc = sc.clone();
107            sc.define_name("gift", Scopes::Bool, key);
108            validate_modifiers_with_base(block, data, &mut sc);
109        });
110        vd.field_validated_block_sc("agent_success_chance", &mut sc, validate_modifiers_with_base);
111        vd.field_validated_block_sc("base_success_chance", &mut sc, validate_modifiers_with_base);
112        vd.advice_field(
113            "base_maximum_success_chance",
114            "docs say `base_maximum_success_chance` but it's `base_maximum_success`",
115        );
116        vd.field_script_value("base_maximum_success", &mut sc);
117        vd.advice_field("maximum_success", "Replaced with `base_maximum_success`");
118        vd.field_integer_range("minimum_success", 0..=100);
119        vd.field_integer_range("maximum_secrecy", 0..=100);
120        vd.field_integer_range("minimum_secrecy", 0..=100);
121        vd.advice_field("maximum_progress_chance", "Removed in 1.13");
122        vd.advice_field("minimum_progress_chance", "Removed in 1.13");
123        vd.field_script_value("base_progress_goal", &mut sc);
124        vd.field_integer("maximum_breaches");
125
126        vd.advice_field("power_per_skill_point", "Replaced with `speed_per_skill_point`");
127        vd.advice_field("resistance_per_skill_point", "Removed in 1.13");
128        vd.advice_field("power_per_agent_skill_point", "Removed in 1.13");
129        vd.advice_field(
130            "spymaster_power_per_skill_point",
131            "Replaced with `spymaster_speed_per_skill_point`",
132        );
133        vd.advice_field("spymaster_resistance_per_skill_point", "Removed in 1.13");
134        vd.advice_field("tier_resistance", "Removed in 1.13");
135        vd.advice_field("uses_agents", "Removed in 1.13");
136
137        vd.field_validated_block("pulse_actions", |block, data| {
138            let mut vd = Validator::new(block, data);
139            vd.field_list_items("entries", Item::SchemePulseAction);
140            vd.field_validated("chance_of_no_event", validate_non_dynamic_script_value);
141        });
142
143        vd.field_validated_block_sc("cooldown", &mut sc, validate_duration);
144
145        vd.field_bool("is_secret");
146        vd.field_trigger("use_secrecy", Tooltipped::No, &mut sc);
147        vd.field_script_value_no_breakdown_builder("base_secrecy", sc_secrecy);
148
149        // on_start is undocumented
150        for field in &[
151            "on_start",
152            "on_phase_completed",
153            "on_hud_click",
154            "on_monthly",
155            "on_semiyearly",
156            "on_invalidated",
157        ] {
158            vd.field_effect_rooted(field, Tooltipped::No, Scopes::Scheme);
159        }
160        vd.advice_field("on_ready", "Replaced with `on_phase_completed`");
161        vd.advice_field("on_agent_join", "Removed in 1.13");
162        vd.advice_field("on_agent_leave", "Removed in 1.13");
163        vd.advice_field("on_agent_exposed", "Removed in 1.13");
164
165        vd.field_bool("freeze_scheme_when_traveling");
166        vd.field_bool("freeze_scheme_when_traveling_target");
167        vd.field_bool("cancel_scheme_when_traveling");
168        vd.field_bool("cancel_scheme_when_traveling_target");
169        vd.field_bool("hide_target_name");
170
171        vd.field_script_value("speed_per_skill_point", &mut sc);
172        vd.field_script_value("speed_per_target_skill_point", &mut sc);
173        vd.field_script_value("success_chance_growth_per_skill_point", &mut sc);
174        vd.field_script_value("spymaster_speed_per_skill_point", &mut sc);
175        vd.field_script_value("target_spymaster_speed_per_skill_point", &mut sc);
176        vd.field_integer("tier_speed");
177
178        // undocumented
179
180        vd.field_script_value("phases_per_agent_charge", &mut sc);
181    }
182}
183
184#[derive(Clone, Debug)]
185pub struct AgentType {}
186
187inventory::submit! {
188    ItemLoader::Normal(GameFlags::Ck3, Item::AgentType, AgentType::add)
189}
190
191impl AgentType {
192    pub fn add(db: &mut Db, key: Token, block: Block) {
193        db.add(Item::AgentType, key, block, Box::new(Self {}));
194    }
195}
196
197impl DbKind for AgentType {
198    fn validate(&self, key: &Token, block: &Block, data: &Everything) {
199        fn sc_builder(key: &Token) -> ScopeContext {
200            let target_scopes =
201                Scopes::Character | Scopes::LandedTitle | Scopes::Culture | Scopes::Faith;
202            let mut sc = ScopeContext::new(Scopes::Character, key);
203            sc.define_name("owner", Scopes::Character, key);
204            sc.define_name("scheme", Scopes::Scheme, key);
205            sc.define_name("target", target_scopes, key);
206            sc
207        }
208
209        let mut vd = Validator::new(block, data);
210
211        data.verify_exists(Item::Localization, key);
212        let loca = format!("{key}_i");
213        data.verify_exists_implied(Item::Localization, &loca, key);
214        let loca = format!("{key}_desc");
215        data.verify_exists_implied(Item::Localization, &loca, key);
216
217        vd.field_trigger_builder("valid_agent_for_slot", Tooltipped::Yes, |key| {
218            let target_scopes =
219                Scopes::Character | Scopes::LandedTitle | Scopes::Culture | Scopes::Faith;
220            let mut sc = ScopeContext::new(Scopes::Character, key);
221            sc.define_name("owner", Scopes::Character, key);
222            sc.define_name("target", target_scopes, key);
223            sc
224        });
225        vd.field_choice("contribution_type", AGENT_SLOT_CONTRIBUTION_TYPE);
226
227        vd.field_script_value_builder("contribution", sc_builder);
228    }
229}
230
231#[derive(Clone, Debug)]
232pub struct SchemePulseAction {}
233
234inventory::submit! {
235    ItemLoader::Normal(GameFlags::Ck3, Item::SchemePulseAction, SchemePulseAction::add)
236}
237
238impl SchemePulseAction {
239    pub fn add(db: &mut Db, key: Token, block: Block) {
240        db.add(Item::SchemePulseAction, key, block, Box::new(Self {}));
241    }
242}
243
244impl DbKind for SchemePulseAction {
245    fn validate(&self, key: &Token, block: &Block, data: &Everything) {
246        fn sc_builder(key: &Token) -> ScopeContext {
247            let mut sc = ScopeContext::new(Scopes::Scheme, key);
248            sc.define_name("scheme", Scopes::Scheme, key); // docs say "activity"
249            sc.define_name("owner", Scopes::Character, key);
250            sc
251        }
252
253        let mut vd = Validator::new(block, data);
254
255        let icon = vd.field_value("icon").unwrap_or(key);
256        data.verify_icon("NGameIcons|STATICMODIFIER_ICON_PATH", icon, ".dds");
257
258        vd.field_localization("hud_text", &mut sc_builder(key));
259
260        vd.field_trigger_builder("is_valid", Tooltipped::No, sc_builder);
261        vd.field_script_value_no_breakdown_builder("weight", sc_builder);
262        vd.field_effect_builder("effect", Tooltipped::No, sc_builder);
263    }
264}
265
266#[derive(Clone, Debug)]
267pub struct Countermeasure {}
268
269inventory::submit! {
270    ItemLoader::Normal(GameFlags::Ck3, Item::Countermeasure, Countermeasure::add)
271}
272
273impl Countermeasure {
274    pub fn add(db: &mut Db, key: Token, block: Block) {
275        db.add(Item::Countermeasure, key, block, Box::new(Self {}));
276    }
277}
278
279impl DbKind for Countermeasure {
280    fn add_subitems(&self, _key: &Token, block: &Block, db: &mut Db) {
281        if let Some(parameters) = block.get_field_block("parameters") {
282            for (key, _) in parameters.iter_assignments() {
283                db.add_flag(Item::CountermeasureParameter, key.clone());
284            }
285        }
286    }
287
288    fn validate(&self, key: &Token, block: &Block, data: &Everything) {
289        let mut vd = Validator::new(block, data);
290
291        let loca = format!("scheme_countermeasure_type_{key}");
292        data.verify_exists_implied(Item::Localization, &loca, key);
293        let loca = format!("scheme_countermeasure_type_{key}_desc");
294        data.verify_exists_implied(Item::Localization, &loca, key);
295
296        data.verify_icon("NGameIcons|SCHEME_COUNTERMEASURE_TYPE_ICON_PATH", key, ".dds");
297
298        vd.field_trigger_rooted("is_shown", Tooltipped::No, Scopes::Character);
299        vd.field_trigger_rooted(
300            "is_valid_showing_failures_only",
301            Tooltipped::FailuresOnly,
302            Scopes::Character,
303        );
304        vd.field_effect_rooted("on_activate", Tooltipped::Yes, Scopes::Character);
305
306        let mut sc = ScopeContext::new(Scopes::Character, key);
307        vd.field_validated_block_sc("activation_cost", &mut sc, validate_cost);
308
309        vd.field_validated_block("owner_modifier", |block, data| {
310            let vd = Validator::new(block, data);
311            validate_modifs(block, data, ModifKinds::Character, vd);
312        });
313
314        vd.field_script_value_no_breakdown_rooted("ai_will_do", Scopes::Character);
315
316        vd.field_validated_block("parameters", |block, data| {
317            let mut vd = Validator::new(block, data);
318            vd.unknown_value_fields(|_, value| {
319                if !value.is("yes") {
320                    let msg = "only `yes` currently makes sense here";
321                    warn(ErrorKey::Validation).msg(msg).loc(value).push();
322                }
323            });
324        });
325
326        // undocumented
327
328        vd.field_item("frame", Item::File);
329    }
330}