Skip to main content

tiger_lib/vic3/data/
treaty_articles.rs

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            // undocumented
85            "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                            // TODO: verify whether X or X_type scopes
280                            "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}