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