tiger_lib/ck3/data/
decisions.rs

1use crate::block::{BV, Block};
2use crate::ck3::validate::validate_cost;
3use crate::context::ScopeContext;
4use crate::db::{Db, DbKind};
5use crate::desc::validate_desc;
6use crate::everything::Everything;
7use crate::game::GameFlags;
8use crate::item::{Item, ItemLoader};
9use crate::report::{ErrorKey, warn};
10use crate::scopes::Scopes;
11use crate::token::Token;
12use crate::tooltipped::Tooltipped;
13use crate::validate::{validate_duration, validate_modifiers_with_base};
14use crate::validator::Validator;
15
16#[derive(Clone, Debug)]
17pub struct Decision {}
18
19inventory::submit! {
20    ItemLoader::Normal(GameFlags::Ck3, Item::Decision, Decision::add)
21}
22
23impl Decision {
24    pub fn add(db: &mut Db, key: Token, block: Block) {
25        db.add(Item::Decision, key, block, Box::new(Self {}));
26    }
27}
28
29impl DbKind for Decision {
30    fn validate(&self, key: &Token, block: &Block, data: &Everything) {
31        let mut vd = Validator::new(block, data);
32        let mut sc = ScopeContext::new(Scopes::Character, key);
33
34        if let Some(block) = block.get_field_block("widget") {
35            // Add builtin scopes added by known widget controllers
36            if let Some(controller) = block.get_field_value("controller") {
37                if controller.is("decision_option_list_controller") {
38                    for block in block.get_field_blocks("item") {
39                        if let Some(token) = block.get_field_value("value") {
40                            sc.define_name(token.as_str(), Scopes::Bool, token);
41                        }
42                    }
43                } else if controller.is("create_holy_order") {
44                    sc.define_name("ruler", Scopes::Character, controller);
45                } else if controller.is("revoke_holy_order_lease") {
46                    sc.define_name("barony", Scopes::LandedTitle, controller);
47                }
48            }
49        }
50
51        vd.req_field_warn("picture");
52        vd.multi_field_validated_block("picture", |block, data| {
53            let mut vd = Validator::new(block, data);
54            vd.field_trigger_rooted("trigger", Tooltipped::No, Scopes::Character);
55            vd.field_item("reference", Item::File);
56            vd.field_item("soundeffect", Item::Sound);
57        });
58        vd.field_item("extra_picture", Item::File);
59        vd.advice_field("major", "Replaced with decision_group_type");
60        vd.field_item("decision_group_type", Item::DecisionGroup);
61        vd.field_script_value_rooted("progress", Scopes::Character);
62        vd.field_validated_block("advice", validate_advice);
63        vd.field_integer("sort_order");
64        vd.field_bool("is_invisible");
65        vd.field_bool("ai_goal");
66        vd.field_integer("ai_check_interval");
67        vd.field_validated_key_block("ai_check_interval_by_tier", |key, b, data| {
68            let mut vd = Validator::new(b, data);
69            for tier in &["barony", "county", "duchy", "kingdom", "empire", "hegemony"] {
70                vd.req_field(tier);
71                vd.field_integer(tier);
72            }
73            if block.has_key("ai_check_interval") {
74                let msg = "must not have both `ai_check_interval` and `ai_check_interval_by_tier`";
75                warn(ErrorKey::Validation).msg(msg).loc(key).push();
76            }
77        });
78        if block.get_field_bool("ai_goal").unwrap_or(false) {
79            vd.advice_field("ai_check_interval", "not needed if ai_goal = yes");
80        }
81        vd.field_validated_block_sc("cooldown", &mut sc, validate_duration);
82
83        vd.field_item("confirm_click_sound", Item::Sound);
84
85        if !vd.field_validated_sc("selection_tooltip", &mut sc, validate_desc) {
86            let loca = format!("{key}_tooltip");
87            data.verify_exists_implied(Item::Localization, &loca, key);
88            data.validate_localization_sc(&loca, &mut sc);
89        }
90
91        if !vd.field_validated_sc("title", &mut sc, validate_desc) {
92            data.verify_exists(Item::Localization, key);
93            data.validate_localization_sc(key.as_str(), &mut sc);
94        }
95
96        if !vd.field_validated_sc("desc", &mut sc, validate_desc) {
97            let loca = format!("{key}_desc");
98            data.verify_exists_implied(Item::Localization, &loca, key);
99            data.validate_localization_sc(&loca, &mut sc);
100        }
101
102        if !vd.field_validated_sc("confirm_text", &mut sc, validate_desc) {
103            let loca = format!("{key}_confirm");
104            data.verify_exists_implied(Item::Localization, &loca, key);
105            data.validate_localization_sc(&loca, &mut sc);
106        }
107
108        vd.field_trigger("is_shown", Tooltipped::No, &mut sc);
109        vd.field_trigger("is_valid_showing_failures_only", Tooltipped::FailuresOnly, &mut sc);
110        vd.field_trigger("is_valid", Tooltipped::Yes, &mut sc);
111
112        // cost can have multiple definitions and they will be combined
113        // however, two costs of the same type are not summed
114        vd.multi_field_validated_block("cost", |b, data| validate_cost(b, data, &mut sc));
115        check_cost(&block.get_field_blocks("cost"));
116        vd.multi_field_validated_block("minimum_cost", |b, data| validate_cost(b, data, &mut sc));
117        check_cost(&block.get_field_blocks("minimum_cost"));
118
119        vd.field_effect("effect", Tooltipped::Yes, &mut sc);
120        vd.field_trigger("ai_potential", Tooltipped::No, &mut sc);
121        vd.field_validated_block_sc("ai_will_do", &mut sc, validate_modifiers_with_base);
122        vd.field_trigger("should_create_alert", Tooltipped::No, &mut sc);
123        vd.field_validated("widget", |bv, data| {
124            match bv {
125                BV::Value(value) => {
126                    let filename =
127                        format!("gui/decision_view_widgets/decision_view_widget_{value}.gui");
128                    data.verify_exists_implied(Item::File, &filename, value);
129                }
130                BV::Block(block) => {
131                    let mut vd = Validator::new(block, data);
132                    vd.field_validated_value("gui", |key, mut vd| {
133                        let filename = format!("gui/decision_view_widgets/{vd}.gui");
134                        data.verify_exists_implied(Item::File, &filename, key);
135                        vd.accept();
136                    });
137
138                    // LAST UPDATED CK3 VERSION 1.12.3
139                    vd.field_choice(
140                        "controller",
141                        &[
142                            "default",
143                            "decision_option_list_controller",
144                            "create_holy_order",
145                            "revoke_holy_order_lease", // undocumented
146                        ],
147                    );
148                    vd.field_bool("show_from_start");
149
150                    // Undocumented
151                    vd.field_item("decision_to_second_step_button", Item::Localization);
152
153                    match block.get_field_value("controller").map(Token::as_str) {
154                        Some("decision_option_list_controller") => {
155                            vd.multi_field_validated_block("item", |block, data| {
156                                let mut vd = Validator::new(block, data);
157                                vd.field_value("value");
158                                vd.field_trigger_rooted(
159                                    "is_shown",
160                                    Tooltipped::No,
161                                    Scopes::Character,
162                                );
163                                vd.field_trigger_rooted(
164                                    "is_valid",
165                                    Tooltipped::FailuresOnly,
166                                    Scopes::Character,
167                                );
168                                vd.field_validated_rooted(
169                                    "current_description",
170                                    Scopes::Character,
171                                    validate_desc,
172                                );
173                                vd.field_validated_rooted(
174                                    "localization",
175                                    Scopes::Character,
176                                    validate_desc,
177                                );
178                                vd.field_bool("is_default");
179                                vd.field_item("icon", Item::File);
180                                vd.field_bool("flat");
181
182                                vd.field_script_value_no_breakdown_rooted(
183                                    "ai_chance",
184                                    Scopes::Character,
185                                );
186                            });
187                        }
188                        Some("create_holy_order" | "revoke_holy_order_lease") => {
189                            vd.field_trigger_builder("barony_valid", Tooltipped::No, |key| {
190                                let mut sc = ScopeContext::new(Scopes::LandedTitle, key);
191                                sc.define_name("ruler", Scopes::Character, key);
192                                sc
193                            });
194                        }
195                        _ => (),
196                    }
197                }
198            }
199        });
200    }
201}
202
203fn check_cost(blocks: &[&Block]) {
204    let mut seen_gold = false;
205    let mut seen_prestige = false;
206    let mut seen_piety = false;
207    if blocks.len() > 1 {
208        for cost in blocks {
209            if let Some(gold) = cost.get_field("gold") {
210                if seen_gold {
211                    let msg = "This value of the gold cost overrides the previously set cost.";
212                    warn(ErrorKey::Conflict).msg(msg).loc(gold).push();
213                }
214                seen_gold = true;
215            }
216            if let Some(prestige) = cost.get_field("prestige") {
217                if seen_prestige {
218                    let msg = "This value of the prestige cost overrides the previously set cost.";
219                    warn(ErrorKey::Conflict).msg(msg).loc(prestige).push();
220                }
221                seen_prestige = true;
222            }
223            if let Some(piety) = cost.get_field("piety") {
224                if seen_piety {
225                    let msg = "This value of the piety cost overrides the previously set cost.";
226                    warn(ErrorKey::Conflict).msg(msg).loc(piety).push();
227                }
228                seen_piety = true;
229            }
230        }
231    }
232}
233
234#[derive(Clone, Debug)]
235pub struct DecisionGroup {}
236
237inventory::submit! {
238    ItemLoader::Normal(GameFlags::Ck3, Item::DecisionGroup, DecisionGroup::add)
239}
240
241impl DecisionGroup {
242    pub fn add(db: &mut Db, key: Token, block: Block) {
243        db.add(Item::DecisionGroup, key, block, Box::new(Self {}));
244    }
245}
246
247impl DbKind for DecisionGroup {
248    fn validate(&self, key: &Token, block: &Block, data: &Everything) {
249        let mut vd = Validator::new(block, data);
250
251        let loca = format!("decision_group_type_{key}");
252        data.verify_exists_implied(Item::Localization, &loca, key);
253
254        vd.field_integer("sort_order");
255        vd.field_list("gui_tags");
256        vd.field_bool("important_decision_group");
257    }
258}
259
260fn validate_advice(block: &Block, data: &Everything) {
261    let mut vd = Validator::new(block, data);
262    vd.field_item("region", Item::Region);
263    vd.field_validated_key_block("per_title_hint", |key, block, data| {
264        let mut sc = ScopeContext::new(Scopes::Character, key);
265        sc.define_name("title", Scopes::LandedTitle, key);
266        sc.define_name("doing", Scopes::Bool, key);
267        let mut vd = Validator::new(block, data);
268        vd.field_trigger("is_valid", Tooltipped::No, &mut sc);
269        vd.field_trigger("is_doing", Tooltipped::No, &mut sc);
270        vd.field_script_value_no_breakdown("relevance", &mut sc);
271        vd.field_validated_sc("summary", &mut sc, validate_desc);
272        vd.field_validated_sc("description", &mut sc, validate_desc);
273    });
274}