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 ];
87 vd.field_list_choice("flags", flags);
88
89 vd.field_choice(
90 "usage_limit",
91 &["once_per_treaty", "once_per_side", "once_per_side_with_same_inputs"],
92 );
93
94 if is_directed {
95 vd.req_field("maintenance_paid_by");
96 vd.field_choice("maintenance_paid_by", &["target_country", "source_country"]);
97 } else {
98 vd.ban_field("maintenance_paid_by", || "directed articles");
99 }
100
101 let required_inputs = &[
102 "quantity",
103 "goods",
104 "state",
105 "strategic_region",
106 "company",
107 "building_type",
108 "law_type",
109 "country",
110 "ship",
111 ];
112 vd.field_list_choice("required_inputs", required_inputs);
113
114 let required_inputs = block.get_field_list("required_inputs");
115 let required_inputs = required_inputs.as_ref().map_or(&[][..], Vec::as_slice);
116 for input in required_inputs {
117 if input.is("quantity") {
118 fn build_quantity_sc(key: &Token, is_directed: bool) -> ScopeContext {
119 let mut sc = build_article_sc(key, is_directed);
120 sc.define_name("other_country", Scopes::Country, key);
121 sc
122 }
123
124 vd.field_script_value_builder("quantity_min_value", |key| {
125 build_quantity_sc(key, is_directed)
126 });
127 vd.field_script_value_builder("quantity_max_value", |key| {
128 build_quantity_sc(key, is_directed)
129 });
130 } else {
131 let valid_trigger = format!("{input}_valid_trigger");
132 vd.field_trigger_builder(&valid_trigger, Tooltipped::No, |key| {
133 build_input_sc(key, input)
134 });
135 }
136 }
137
138 vd.field_list_items("mutual_exclusions", Item::TreatyArticle);
139 vd.field_list_items("unlocked_by_technologies", Item::Technology);
140 vd.field_list_items("automatically_support", Item::DiplomaticPlay);
141 vd.field_validated_key_block("non_fulfillment", |key, block, data| {
142 let mut vd = Validator::new(block, data);
143 vd.field_choice("consequences", &["none", "withdraw", "freeze"]);
144 let is_consequences_none =
145 block.get_field_value("consequences").is_none_or(|t| t.is("none"));
146 if is_consequences_none {
147 vd.ban_field("max_consecutive_contraventions", || "withdraw/freeze");
148 vd.ban_field("conditions", || "withdraw/freeze");
149 } else if block
150 .get_field_block("conditions")
151 .is_none_or(|b| b.num_items() == 0 || b.field_value_is("always", "no"))
152 {
153 let msg = "at least one of the conditions triggers must be non-empty";
154 err(ErrorKey::Validation).msg(msg).loc(key).push();
155 }
156
157 vd.field_integer_range("max_consecutive_contraventions", 0..);
158 vd.field_validated_block("conditions", |block, data| {
159 let mut vd = Validator::new(block, data);
160 for interval in &["weekly", "monthly", "yearly"] {
161 vd.field_trigger_builder(interval, Tooltipped::No, |key| {
162 let mut sc = ScopeContext::new(Scopes::Country, key);
163 sc.define_name("article", Scopes::TreatyArticle, key);
164 sc
165 });
166 }
167 });
168 });
169
170 if !is_directed {
171 vd.ban_field("source_modifier", || "directed");
172 vd.ban_field("target_modifier", || "directed");
173 }
174
175 for modifier in &["source_modifier", "target_modifier", "mutual_modifier"] {
176 vd.multi_field_validated_block(modifier, |block, data| {
177 let vd = Validator::new(block, data);
178 validate_modifs(block, data, ModifKinds::Country, vd);
179 });
180 }
181
182 vd.field_trigger_rooted("visible", Tooltipped::No, Scopes::Country);
183 vd.field_trigger_builder("possible", Tooltipped::Yes, |key| {
184 let mut sc = ScopeContext::new(Scopes::Country, key);
185 sc.define_name("other_country", Scopes::Country, key);
186 sc
187 });
188 vd.multi_field_validated_block("requirement_to_maintain", |block, data| {
189 let mut vd = Validator::new(block, data);
190 vd.field_trigger_builder("trigger", Tooltipped::Yes, |key| {
191 build_article_treaty_sc(key, is_directed)
192 });
193 vd.field_trigger_builder("show_about_to_break_warning", Tooltipped::No, |key| {
194 build_article_treaty_sc(key, is_directed)
195 });
196 });
197 vd.field_trigger_builder("can_ratify", Tooltipped::Yes, |key| {
198 build_article_treaty_sc(key, is_directed)
199 });
200
201 for active in &["on_entry_into_force", "on_enforced"] {
202 vd.field_effect_builder(active, Tooltipped::Yes, |key| {
203 let mut sc = ScopeContext::new(Scopes::None, key);
204 sc.define_name("treaty_options", Scopes::TreatyOptions, key);
205 sc.define_name("article_options", Scopes::TreatyArticleOptions, key);
206 sc
207 });
208 }
209
210 vd.field_trigger_builder("can_withdraw", Tooltipped::Yes, |key| {
211 let mut sc = ScopeContext::new(Scopes::None, key);
212 sc.define_name("withdrawing_country", Scopes::Country, key);
213 sc.define_name("non_withdrawing_country", Scopes::Country, key);
214
215 if is_directed {
216 sc.define_name("source_country", Scopes::Country, key);
217 sc.define_name("target_country", Scopes::Country, key);
218 } else {
219 sc.define_name("first_country", Scopes::Country, key);
220 sc.define_name("second_country", Scopes::Country, key);
221 }
222 sc
223 });
224
225 for deactive in &["on_withdrawal", "on_break"] {
226 vd.field_effect_builder(deactive, Tooltipped::Yes, |key| {
227 let mut sc = ScopeContext::new(Scopes::None, key);
228 sc.define_name("treaty_options", Scopes::TreatyOptions, key);
229 sc.define_name("article", Scopes::TreatyArticle, key);
230 sc.define_name("withdrawing_country", Scopes::Country, key);
231 sc.define_name("non_withdrawing_country", Scopes::Country, key);
232 sc
233 });
234 }
235
236 vd.field_validated_block("ai", |block, data| {
237 validate_ai(block, data, required_inputs, is_directed);
238 });
239
240 vd.field_validated_block("wargoal", |block, data| {
241 let mut vd = Validator::new(block, data);
242 vd.field_integer("execution_priority");
243 let contestion_types = &[
244 "control_target_state",
245 "control_target_country_capital",
246 "control_any_target_country_state",
247 "control_own_state",
248 "control_own_capital",
249 "control_all_own_states",
250 "control_all_target_country_claims",
251 "control_any_target_incorporated_state",
252 ];
253 vd.field_choice("contestion_type", contestion_types);
254
255 for script_value in &["maneuvers", "infamy"] {
256 vd.field_script_value_builder(script_value, |key| {
257 let mut sc = ScopeContext::new(Scopes::Country, key);
258 sc.define_name("target_country", Scopes::Country, key);
259 for input in required_inputs {
260 match input.as_str() {
261 "quantity" => sc.define_name("quantity", Scopes::Value, key),
262 "goods" => {
263 sc.define_name("goods", Scopes::Goods, key);
264 sc.define_name("market_goods", Scopes::MarketGoods, key);
265 }
266 "state" => sc.define_name("state", Scopes::State, key),
267 "strategic_region" => {
268 sc.define_name("region", Scopes::StrategicRegion, key);
269 }
270 "company" => sc.define_name("company", Scopes::Company, key),
271 "country" => sc.define_name("country", Scopes::Country, key),
272 "building_type" => sc.define_name("building", Scopes::Building, key),
274 "law_type" => sc.define_name("law", Scopes::Law, key),
275 "ship" => sc.define_name("ship", Scopes::Ship, key),
276 _ => {}
277 }
278 }
279 sc
280 });
281 }
282 });
283 }
284}
285
286fn build_input_sc(key: &Token, input: &Token) -> ScopeContext {
287 let mut sc = ScopeContext::new(Scopes::Country, key);
288 sc.define_name("other_country", Scopes::Country, key);
289 sc.define_name("article", Scopes::TreatyArticle, key);
290 let input_scope = match input.as_str() {
291 "goods" => Scopes::Goods,
292 "state" => Scopes::State,
293 "strategic_region" => Scopes::StrategicRegion,
294 "company" => Scopes::Company,
295 "building_type" => Scopes::BuildingType,
296 "law_type" => Scopes::LawType,
297 "country" => Scopes::Country,
298 _ => {
299 return sc;
300 }
301 };
302 sc.define_name("input", input_scope, key);
303
304 if input.is("goods") {
305 sc.define_name("market_goods", Scopes::MarketGoods, key);
306 }
307 sc
308}
309
310fn build_article_sc(key: &Token, is_directed: bool) -> ScopeContext {
311 let mut sc = ScopeContext::new(Scopes::Country, key);
312 sc.define_name("article", Scopes::TreatyArticle, key);
313
314 if is_directed {
315 sc.define_name("source_country", Scopes::Country, key);
316 sc.define_name("target_country", Scopes::Country, key);
317 } else {
318 sc.define_name("first_country", Scopes::Country, key);
319 sc.define_name("second_country", Scopes::Country, key);
320 }
321 sc
322}
323
324fn build_article_treaty_sc(key: &Token, is_directed: bool) -> ScopeContext {
325 let mut sc = build_article_sc(key, is_directed);
326 sc.define_name("treaty", Scopes::Treaty, key);
327 sc
328}
329
330fn validate_ai(block: &Block, data: &Everything, required_inputs: &[Token], is_directed: bool) {
331 let mut vd = Validator::new(block, data);
332 vd.field_script_value_rooted("evaluation_chance", Scopes::Country);
333
334 for input in required_inputs {
335 if input.is("quantity") {
336 vd.field_script_value_builder("quantity_input_value", |key| {
337 let mut sc = ScopeContext::new(Scopes::Country, key);
338 sc.define_name("other_country", Scopes::Country, key);
339 sc.define_name("article", Scopes::TreatyArticle, key);
340 sc
341 });
342 } else {
343 let filter = format!("{input}_input_filter");
344 vd.field_trigger_builder(&filter, Tooltipped::No, |key| build_input_sc(key, input));
345 }
346 }
347
348 vd.field_list_choice("article_ai_usage", &["offer", "request"]);
349 let categories = &[
350 "economy",
351 "trade",
352 "military",
353 "military_defense",
354 "ideology",
355 "expansion",
356 "power_bloc",
357 "other",
358 "none",
359 ];
360 vd.field_list_choice("treaty_categories", categories);
361 vd.field_integer("combined_acceptance_cap_max");
362 vd.field_integer("combined_acceptance_cap_min");
363 vd.field_script_value_builder("inherent_accept_score", |key| {
364 build_article_sc(key, is_directed)
365 });
366 vd.field_script_value_builder("contextual_accept_score", |key| {
367 build_article_treaty_sc(key, is_directed)
368 });
369 vd.field_script_value_builder("proposal_weight", |key| build_article_sc(key, is_directed));
370
371 vd.field_script_value_builder("wargoal_score_multiplier", |key| {
372 build_article_sc(key, is_directed)
373 });
374}