1use crate::block::Block;
2use crate::context::ScopeContext;
3use crate::db::{Db, DbKind};
4use crate::everything::Everything;
5use crate::game::GameFlags;
6use crate::item::{Item, ItemLoader};
7use crate::modif::validate_modifs;
8use crate::report::{ErrorKey, err};
9use crate::scopes::Scopes;
10use crate::token::Token;
11use crate::tooltipped::Tooltipped;
12use crate::validator::Validator;
13use crate::vic3::modif::ModifKinds;
14
15#[derive(Clone, Debug)]
16pub struct TreatyArticle {}
17
18inventory::submit! {
19 ItemLoader::Normal(GameFlags::Vic3, Item::TreatyArticle, TreatyArticle::add)
20}
21
22impl TreatyArticle {
23 pub fn add(db: &mut Db, key: Token, block: Block) {
24 db.add(Item::TreatyArticle, key, block, Box::new(Self {}));
25 }
26}
27
28impl DbKind for TreatyArticle {
29 fn validate(&self, key: &Token, block: &Block, data: &Everything) {
30 let mut vd = Validator::new(block, data);
31
32 data.verify_exists(Item::Localization, key);
33 data.verify_exists_implied(Item::Localization, &format!("{key}_desc"), key);
34 data.verify_exists_implied(Item::Localization, &format!("{key}_effects_desc"), key);
35 data.verify_exists_implied(Item::Localization, &format!("{key}_article_short_desc"), key);
36
37 vd.req_field("kind");
38 vd.field_choice("kind", &["directed", "mutual"]);
39 let is_directed = block.get_field_value("kind").is_some_and(|k| k.is("directed"));
40
41 vd.field_integer_range("cost", 0..);
42 vd.field_numeric("relations_progress_per_day");
43 vd.field_integer_range("relations_improvement_max", 0..);
44 vd.field_integer_range("relations_improvement_min", 0..);
45 vd.field_integer("max_target_involvement");
46 vd.field_choice(
47 "target_involvement_applies_to",
48 &["source_country", "target_country", "mutual"],
49 );
50
51 vd.field_item("icon", Item::File);
52
53 let flags = &[
54 "is_alliance",
55 "is_defensive_pact",
56 "is_guarantee_independence",
57 "is_support_independence",
58 "is_host_power_bloc_embassy",
59 "is_offer_embassy",
60 "is_investment_rights",
61 "is_join_power_bloc",
62 "is_trade_privilege",
63 "is_military_access",
64 "is_military_assistance",
65 "is_transit_rights",
66 "is_non_colonization_agreement",
67 "is_goods_transfer",
68 "is_money_transfer",
69 "is_monopoly_for_company",
70 "is_prohibit_goods_trade_with_world_market",
71 "is_no_tariffs",
72 "is_no_subventions",
73 "is_take_on_debt",
74 "is_treaty_port",
75 "is_law_commitment",
76 "is_non_piracy_agreement",
77 "is_abandon_piracy",
78 "can_be_renegotiated",
79 "can_be_enforced",
80 "causes_state_transfer",
81 "friendly",
82 "giftable",
83 "hostile",
84 "target_subjects_as_input",
86 "ai_consider_threaten_naval_hostilities",
87 "exclude_from_make_this_work",
88 "is_ship_transfer",
89 "is_toll_exemption",
90 "is_strait_access",
91 "is_no_strait_closure",
92 "is_no_tolls",
93 ];
94 vd.field_list_choice("flags", flags);
95
96 vd.field_choice(
97 "usage_limit",
98 &["once_per_treaty", "once_per_side", "once_per_side_with_same_inputs"],
99 );
100
101 if is_directed {
102 vd.req_field("maintenance_paid_by");
103 vd.field_choice("maintenance_paid_by", &["target_country", "source_country"]);
104 } else {
105 vd.ban_field("maintenance_paid_by", || "directed articles");
106 }
107
108 let required_inputs = &[
109 "quantity",
110 "goods",
111 "state",
112 "strategic_region",
113 "company",
114 "building_type",
115 "law_type",
116 "country",
117 "ship",
118 ];
119 vd.field_list_choice("required_inputs", required_inputs);
120
121 let required_inputs = block.get_field_list("required_inputs");
122 let required_inputs = required_inputs.as_ref().map_or(&[][..], Vec::as_slice);
123 for input in required_inputs {
124 if input.is("quantity") {
125 fn build_quantity_sc(key: &Token, is_directed: bool) -> ScopeContext {
126 let mut sc = build_article_sc(key, is_directed);
127 sc.define_name("other_country", Scopes::Country, key);
128 sc
129 }
130
131 vd.field_script_value_builder("quantity_min_value", |key| {
132 build_quantity_sc(key, is_directed)
133 });
134 vd.field_script_value_builder("quantity_max_value", |key| {
135 build_quantity_sc(key, is_directed)
136 });
137 } else {
138 let valid_trigger = format!("{input}_valid_trigger");
139 vd.field_trigger_builder(&valid_trigger, Tooltipped::No, |key| {
140 build_input_sc(key, input)
141 });
142 }
143 }
144
145 vd.field_list_items("mutual_exclusions", Item::TreatyArticle);
146 vd.field_list_items("unlocked_by_technologies", Item::Technology);
147 vd.field_list_items("automatically_support", Item::DiplomaticPlay);
148 vd.field_validated_key_block("non_fulfillment", |key, block, data| {
149 let mut vd = Validator::new(block, data);
150 vd.field_choice("consequences", &["none", "withdraw", "freeze"]);
151 let is_consequences_none =
152 block.get_field_value("consequences").is_none_or(|t| t.is("none"));
153 if is_consequences_none {
154 vd.ban_field("max_consecutive_contraventions", || "withdraw/freeze");
155 vd.ban_field("conditions", || "withdraw/freeze");
156 } else if block
157 .get_field_block("conditions")
158 .is_none_or(|b| b.num_items() == 0 || b.field_value_is("always", "no"))
159 {
160 let msg = "at least one of the conditions triggers must be non-empty";
161 err(ErrorKey::Validation).msg(msg).loc(key).push();
162 }
163
164 vd.field_integer_range("max_consecutive_contraventions", 0..);
165 vd.field_validated_block("conditions", |block, data| {
166 let mut vd = Validator::new(block, data);
167 for interval in &["weekly", "monthly", "yearly"] {
168 vd.field_trigger_builder(interval, Tooltipped::No, |key| {
169 let mut sc = ScopeContext::new(Scopes::Country, key);
170 sc.define_name("article", Scopes::TreatyArticle, key);
171 sc
172 });
173 }
174 });
175 });
176
177 if !is_directed {
178 vd.ban_field("source_modifier", || "directed");
179 vd.ban_field("target_modifier", || "directed");
180 }
181
182 for modifier in &["source_modifier", "target_modifier", "mutual_modifier"] {
183 vd.multi_field_validated_block(modifier, |block, data| {
184 let vd = Validator::new(block, data);
185 validate_modifs(block, data, ModifKinds::Country, vd);
186 });
187 }
188
189 vd.field_trigger_rooted("visible", Tooltipped::No, Scopes::Country);
190 vd.field_trigger_builder("possible", Tooltipped::Yes, |key| {
191 let mut sc = ScopeContext::new(Scopes::Country, key);
192 sc.define_name("other_country", Scopes::Country, key);
193 sc
194 });
195 vd.multi_field_validated_block("requirement_to_maintain", |block, data| {
196 let mut vd = Validator::new(block, data);
197 vd.field_trigger_builder("trigger", Tooltipped::Yes, |key| {
198 build_article_treaty_sc(key, is_directed)
199 });
200 vd.field_trigger_builder("show_about_to_break_warning", Tooltipped::No, |key| {
201 build_article_treaty_sc(key, is_directed)
202 });
203 });
204 vd.field_trigger_builder("can_ratify", Tooltipped::Yes, |key| {
205 build_article_treaty_sc(key, is_directed)
206 });
207
208 for active in &["on_entry_into_force", "on_enforced"] {
209 vd.field_effect_builder(active, Tooltipped::Yes, |key| {
210 let mut sc = ScopeContext::new(Scopes::None, key);
211 sc.define_name("treaty_options", Scopes::TreatyOptions, key);
212 sc.define_name("article_options", Scopes::TreatyArticleOptions, key);
213 sc
214 });
215 }
216
217 vd.field_trigger_builder("can_withdraw", Tooltipped::Yes, |key| {
218 let mut sc = ScopeContext::new(Scopes::None, key);
219 sc.define_name("withdrawing_country", Scopes::Country, key);
220 sc.define_name("non_withdrawing_country", Scopes::Country, key);
221
222 if is_directed {
223 sc.define_name("source_country", Scopes::Country, key);
224 sc.define_name("target_country", Scopes::Country, key);
225 } else {
226 sc.define_name("first_country", Scopes::Country, key);
227 sc.define_name("second_country", Scopes::Country, key);
228 }
229 sc
230 });
231
232 for deactive in &["on_withdrawal", "on_break"] {
233 vd.field_effect_builder(deactive, Tooltipped::Yes, |key| {
234 let mut sc = ScopeContext::new(Scopes::None, key);
235 sc.define_name("treaty_options", Scopes::TreatyOptions, key);
236 sc.define_name("article", Scopes::TreatyArticle, key);
237 sc.define_name("withdrawing_country", Scopes::Country, key);
238 sc.define_name("non_withdrawing_country", Scopes::Country, key);
239 sc
240 });
241 }
242
243 vd.field_validated_block("ai", |block, data| {
244 validate_ai(block, data, required_inputs, is_directed);
245 });
246
247 vd.field_validated_block("wargoal", |block, data| {
248 let mut vd = Validator::new(block, data);
249 vd.field_integer("execution_priority");
250 let contestion_types = &[
251 "control_target_state",
252 "control_target_country_capital",
253 "control_any_target_country_state",
254 "control_own_state",
255 "control_own_capital",
256 "control_all_own_states",
257 "control_all_target_country_claims",
258 "control_any_target_incorporated_state",
259 ];
260 vd.field_choice("contestion_type", contestion_types);
261
262 for script_value in &["maneuvers", "infamy"] {
263 vd.field_script_value_builder(script_value, |key| {
264 let mut sc = ScopeContext::new(Scopes::Country, key);
265 sc.define_name("target_country", Scopes::Country, key);
266 for input in required_inputs {
267 match input.as_str() {
268 "quantity" => sc.define_name("quantity", Scopes::Value, key),
269 "goods" => {
270 sc.define_name("goods", Scopes::Goods, key);
271 sc.define_name("market_goods", Scopes::MarketGoods, key);
272 }
273 "state" => sc.define_name("state", Scopes::State, key),
274 "strategic_region" => {
275 sc.define_name("region", Scopes::StrategicRegion, key);
276 }
277 "company" => sc.define_name("company", Scopes::Company, key),
278 "country" => sc.define_name("country", Scopes::Country, key),
279 "building_type" => sc.define_name("building", Scopes::Building, key),
281 "law_type" => sc.define_name("law", Scopes::Law, key),
282 "ship" => sc.define_name("ship", Scopes::Ship, key),
283 _ => {}
284 }
285 }
286 sc
287 });
288 }
289 });
290 }
291}
292
293fn build_input_sc(key: &Token, input: &Token) -> ScopeContext {
294 let mut sc = ScopeContext::new(Scopes::Country, key);
295 sc.define_name("other_country", Scopes::Country, key);
296 sc.define_name("article", Scopes::TreatyArticle, key);
297 let input_scope = match input.as_str() {
298 "goods" => Scopes::Goods,
299 "state" => Scopes::State,
300 "strategic_region" => Scopes::StrategicRegion,
301 "company" => Scopes::Company,
302 "building_type" => Scopes::BuildingType,
303 "law_type" => Scopes::LawType,
304 "country" => Scopes::Country,
305 _ => {
306 return sc;
307 }
308 };
309 sc.define_name("input", input_scope, key);
310
311 if input.is("goods") {
312 sc.define_name("market_goods", Scopes::MarketGoods, key);
313 }
314 sc
315}
316
317fn build_article_sc(key: &Token, is_directed: bool) -> ScopeContext {
318 let mut sc = ScopeContext::new(Scopes::Country, key);
319 sc.define_name("article", Scopes::TreatyArticle, key);
320
321 if is_directed {
322 sc.define_name("source_country", Scopes::Country, key);
323 sc.define_name("target_country", Scopes::Country, key);
324 } else {
325 sc.define_name("first_country", Scopes::Country, key);
326 sc.define_name("second_country", Scopes::Country, key);
327 }
328 sc
329}
330
331fn build_article_treaty_sc(key: &Token, is_directed: bool) -> ScopeContext {
332 let mut sc = build_article_sc(key, is_directed);
333 sc.define_name("treaty", Scopes::Treaty, key);
334 sc
335}
336
337fn validate_ai(block: &Block, data: &Everything, required_inputs: &[Token], is_directed: bool) {
338 let mut vd = Validator::new(block, data);
339 vd.field_script_value_rooted("evaluation_chance", Scopes::Country);
340
341 for input in required_inputs {
342 if input.is("quantity") {
343 vd.field_script_value_builder("quantity_input_value", |key| {
344 let mut sc = ScopeContext::new(Scopes::Country, key);
345 sc.define_name("other_country", Scopes::Country, key);
346 sc.define_name("article", Scopes::TreatyArticle, key);
347 sc
348 });
349 } else {
350 let filter = format!("{input}_input_filter");
351 vd.field_trigger_builder(&filter, Tooltipped::No, |key| build_input_sc(key, input));
352 }
353 }
354
355 vd.field_list_choice("article_ai_usage", &["offer", "request"]);
356 let categories = &[
357 "economy",
358 "trade",
359 "military",
360 "military_defense",
361 "ideology",
362 "expansion",
363 "power_bloc",
364 "other",
365 "none",
366 ];
367 vd.field_list_choice("treaty_categories", categories);
368 vd.field_integer("combined_acceptance_cap_max");
369 vd.field_integer("combined_acceptance_cap_min");
370 vd.field_script_value_builder("inherent_accept_score", |key| {
371 build_article_sc(key, is_directed)
372 });
373 vd.field_script_value_builder("contextual_accept_score", |key| {
374 build_article_treaty_sc(key, is_directed)
375 });
376 vd.field_script_value_builder("proposal_weight", |key| build_article_sc(key, is_directed));
377
378 vd.field_script_value_builder("wargoal_score_multiplier", |key| {
379 build_article_sc(key, is_directed)
380 });
381}