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 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); vd.field_validated_sc("discovery_desc", &mut sc, validate_desc); vd.req_field("skill");
76 vd.field_item("skill", Item::Skill);
77
78 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 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 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); 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 vd.field_item("frame", Item::File);
329 }
330}