1use crate::block::Block;
2use crate::ck3::modif::ModifKinds;
3use crate::context::ScopeContext;
4use crate::db::{Db, DbKind};
5use crate::everything::Everything;
6use crate::game::GameFlags;
7use crate::item::{Item, ItemLoader, LoadAsFile, Recursive};
8use crate::modif::validate_modifs;
9use crate::pdxfile::PdxEncoding;
10use crate::report::{ErrorKey, warn};
11use crate::scopes::Scopes;
12use crate::script_value::validate_script_value;
13use crate::token::Token;
14use crate::tooltipped::Tooltipped;
15use crate::validate::validate_duration;
16use crate::validator::Validator;
17
18#[derive(Clone, Debug)]
19pub struct Struggle {}
20
21inventory::submit! {
22 ItemLoader::Normal(GameFlags::Ck3, Item::Struggle, Struggle::add)
23}
24
25impl Struggle {
26 pub fn add(db: &mut Db, key: Token, block: Block) {
27 db.add(Item::Struggle, key, block, Box::new(Self {}));
28 }
29}
30
31impl DbKind for Struggle {
32 fn add_subitems(&self, _key: &Token, block: &Block, db: &mut Db) {
33 if let Some(block) = block.get_field_block("phase_list") {
34 for (key, block) in block.iter_definitions() {
35 db.add_flag(Item::StrugglePhase, key.clone());
36 for field in &["war_effects", "culture_effects", "faith_effects", "other_effects"] {
37 if let Some(block) = block.get_field_block(field) {
38 for field in &[
39 "common_parameters",
40 "involved_parameters",
41 "interloper_parameters",
42 "uninvolved_parameters",
43 ] {
44 if let Some(block) = block.get_field_block(field) {
45 for (key, value) in block.iter_assignments() {
46 if value.is("yes") {
47 db.add_flag(Item::StrugglePhaseParameter, key.clone());
48 }
49 }
50 }
51 }
52 }
53 }
54 }
55 }
56 }
57
58 fn validate(&self, key: &Token, block: &Block, data: &Everything) {
59 let mut vd = Validator::new(block, data);
60 let mut sc = ScopeContext::new(Scopes::Struggle, key);
61
62 data.verify_exists(Item::Localization, key);
63 let loca = format!("{key}_desc");
64 data.verify_exists_implied(Item::Localization, &loca, key);
65
66 vd.field_item("illustration", Item::File);
67 vd.field_item("situation_group_type", Item::SituationGroupType);
68 vd.field_integer("sort_order");
69
70 vd.field_list_items("cultures", Item::Culture);
71 vd.field_list_items("faiths", Item::Faith);
72 vd.field_list_items("regions", Item::Region);
73
74 vd.field_validated_block_sc("transition_state_duration", &mut sc, validate_duration);
75 vd.field_numeric_range("involvement_prerequisite_percentage", 0.0..=1.0);
76
77 vd.req_field("phase_list");
78 vd.field_validated_block("phase_list", |block, data| {
79 let mut has_one = false;
80 let mut has_ending = false;
81 let phases = block.iter_definitions_warn().map(|(key, _)| key).collect::<Vec<_>>();
82 let mut vd = Validator::new(block, data);
83 vd.unknown_block_fields(|key, block| {
84 data.verify_exists(Item::Localization, key);
85 let loca = format!("{key}_desc");
86 data.verify_exists_implied(Item::Localization, &loca, key);
87 data.verify_icon("NGameIcons|STRUGGLE_PHASE_TYPE_ICON_PATH", key, ".dds");
88 has_one = true;
89 validate_phase(block, data, &phases);
90 if let Some(vec) = block.get_field_list("ending_decisions") {
91 has_ending |= !vec.is_empty();
92 }
93 });
94 if !has_one {
95 warn(ErrorKey::Validation).msg("must have at least one phase").loc(block).push();
96 }
97 if !has_ending {
99 let msg = "must have at least one phase with ending_decisions";
100 warn(ErrorKey::Validation).msg(msg).loc(block).push();
101 }
102 });
103
104 vd.req_field("start_phase");
105 vd.field_item("start_phase", Item::StrugglePhase);
106
107 vd.field_effect("on_start", Tooltipped::No, &mut sc);
108 vd.field_effect("on_end", Tooltipped::No, &mut sc); vd.field_effect("on_change_phase", Tooltipped::No, &mut sc); vd.field_effect_rooted("on_join", Tooltipped::No, Scopes::Character); vd.field_effect("on_monthly", Tooltipped::No, &mut sc);
112 }
113}
114
115fn validate_phase(block: &Block, data: &Everything, phases: &[&Token]) {
116 let mut vd = Validator::new(block, data);
117
118 if vd.field_block("on_start") {
120 vd.field_bool("save_progress");
122 vd.field_effect_rooted("on_start", Tooltipped::Yes, Scopes::Struggle);
123 vd.unknown_fields(|key, _| {
124 let msg = format!("ending phase should not have {key}, which will be ignored");
125 warn(ErrorKey::UnknownField).msg(msg).loc(key).push();
126 });
127 } else {
128 vd.field_validated_block_rooted("duration", Scopes::None, |block, data, sc| {
129 if let Some(bv) = block.get_field("points") {
130 if let Some(token) = bv.expect_value() {
131 token.expect_integer();
132 }
133 } else {
134 validate_duration(block, data, sc);
135 }
136 });
137
138 vd.field_item("background", Item::File);
139 vd.req_field("future_phases");
140 vd.field_validated_block("future_phases", |block, data| {
141 let mut vd = Validator::new(block, data);
142 let mut has_one = false;
143 vd.unknown_block_fields(|key, block| {
144 let mut vd = Validator::new(block, data);
145 has_one = true;
146 data.verify_exists(Item::StrugglePhase, key);
147 if !phases.contains(&key) {
148 let msg = format!("{key} is not a struggle phase of this struggle");
149 warn(ErrorKey::UnknownField).msg(msg).loc(key).push();
150 }
151 vd.field_bool("default");
152 vd.field_validated_block("catalysts", validate_catalyst_list);
153 });
154 if !has_one {
155 warn(ErrorKey::Validation)
156 .msg("must have at least one future phase")
157 .loc(block)
158 .push();
159 }
160 });
161
162 for field in &["war_effects", "culture_effects", "faith_effects", "other_effects"] {
163 vd.field_validated_block(field, validate_phase_effects);
164 }
165
166 vd.field_list_items("ending_decisions", Item::Decision);
167 }
168}
169
170fn validate_catalyst_list(block: &Block, data: &Everything) {
171 let mut vd = Validator::new(block, data);
172 vd.unknown_fields(|key, bv| {
173 if bv.expect_value().is_some() {
174 data.verify_exists(Item::Catalyst, key);
175 let mut sc = ScopeContext::new(Scopes::None, key);
176 validate_script_value(bv, data, &mut sc);
177 }
178 });
179}
180
181fn validate_phase_effects(block: &Block, data: &Everything) {
182 let mut vd = Validator::new(block, data);
183 vd.field_item("name", Item::Localization);
184 vd.field_validated_block("common_parameters", validate_struggle_parameters);
185 vd.field_validated_block("involved_parameters", validate_struggle_parameters);
186 vd.field_validated_block("interloper_parameters", validate_struggle_parameters);
187 vd.field_validated_block("uninvolved_parameters", validate_struggle_parameters);
188
189 for field in &["involved_character_modifier", "interloper_character_modifier"] {
190 vd.field_validated_block(field, |block, data| {
191 let vd = Validator::new(block, data);
192 validate_modifs(block, data, ModifKinds::Character, vd);
193 });
194 }
195
196 for field in &["involved_doctrine_character_modifier", "interloper_doctrine_character_modifier"]
197 {
198 vd.field_validated_block(field, |block, data| {
199 let mut vd = Validator::new(block, data);
200 vd.field_item("doctrine", Item::Doctrine);
201 validate_modifs(block, data, ModifKinds::Character, vd);
202 });
203 }
204
205 for field in &[
206 "all_county_modifier",
207 "involved_county_modifier",
208 "interloper_county_modifier",
209 "uninvolved_county_modifier",
210 ] {
211 vd.field_validated_block(field, |block, data| {
212 let vd = Validator::new(block, data);
213 validate_modifs(block, data, ModifKinds::County, vd);
214 });
215 }
216}
217
218fn validate_struggle_parameters(block: &Block, data: &Everything) {
219 let mut vd = Validator::new(block, data);
220 vd.unknown_value_fields(|key, value| {
221 if !value.is("yes") {
222 let msg = format!("expected `{key} = yes`");
223 warn(ErrorKey::Validation).msg(msg).loc(value).push();
224 }
225
226 let loca = format!("struggle_parameter_{key}");
227 data.verify_exists_implied(Item::Localization, &loca, key);
228 });
229}
230
231#[derive(Clone, Debug)]
232pub struct Catalyst {}
233
234inventory::submit! {
235 ItemLoader::Normal(GameFlags::Ck3, Item::Catalyst, Catalyst::add)
236}
237
238impl Catalyst {
239 pub fn add(db: &mut Db, key: Token, block: Block) {
240 db.add(Item::Catalyst, key, block, Box::new(Self {}));
241 }
242}
243
244impl DbKind for Catalyst {
245 fn validate(&self, _key: &Token, block: &Block, data: &Everything) {
246 let mut _vd = Validator::new(block, data);
247 }
248}
249
250#[derive(Clone, Debug)]
251pub struct StruggleHistory {}
252
253inventory::submit! {
254 ItemLoader::Full(GameFlags::Ck3, Item::StruggleHistory, PdxEncoding::Utf8Bom, ".txt", LoadAsFile::Yes, Recursive::No, StruggleHistory::add)
255}
256
257impl StruggleHistory {
258 pub fn add(db: &mut Db, key: Token, block: Block) {
259 db.add(Item::StruggleHistory, key, block, Box::new(Self {}));
260 }
261}
262
263impl DbKind for StruggleHistory {
264 fn validate(&self, key: &Token, block: &Block, data: &Everything) {
265 let mut vd = Validator::new(block, data);
266 let mut sc = ScopeContext::new(Scopes::None, key);
267
268 vd.unknown_block_fields(|key, block| {
269 key.expect_date();
270 let mut vd = Validator::new(block, data);
271 vd.field_effect("effect", Tooltipped::No, &mut sc);
272 });
273 }
274}