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 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 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 vd.field_choice(
140 "controller",
141 &[
142 "default",
143 "decision_option_list_controller",
144 "create_holy_order",
145 "revoke_holy_order_lease", ],
147 );
148 vd.field_bool("show_from_start");
149
150 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}