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        ];
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                            // TODO: verify whether X or X_type scopes
273                            "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}