1use crate::block::{BV, Block};
2use crate::ck3::tables::misc::PROVINCE_FILTERS;
3use crate::ck3::validate::validate_cost;
4use crate::context::ScopeContext;
5use crate::db::{Db, DbKind};
6use crate::desc::validate_desc;
7use crate::everything::Everything;
8use crate::game::GameFlags;
9use crate::item::{Item, ItemLoader};
10use crate::report::{ErrorKey, warn};
11use crate::scopes::Scopes;
12use crate::token::Token;
13use crate::tooltipped::Tooltipped;
14use crate::validator::Validator;
15
16#[derive(Clone, Debug)]
17pub struct GreatProjectType {}
18
19inventory::submit! {
20 ItemLoader::Normal(GameFlags::Ck3, Item::GreatProjectType, GreatProjectType::add)
21}
22
23impl GreatProjectType {
24 pub fn add(db: &mut Db, key: Token, block: Block) {
25 db.add(Item::GreatProjectType, key, block, Box::new(Self {}));
26 }
27}
28
29impl DbKind for GreatProjectType {
30 fn add_subitems(&self, _key: &Token, block: &Block, db: &mut Db) {
31 if let Some(block) = block.get_field_block("project_contributions") {
32 for (key, _) in block.iter_definitions() {
33 db.add_flag(Item::ProjectContribution, key.clone());
34 }
35 }
36 }
37
38 fn validate(&self, key: &Token, block: &Block, data: &Everything) {
39 let loca = format!("great_project_type_{key}");
40 data.verify_exists_implied(Item::Localization, &loca, key);
41 let loca = format!("great_project_type_tooltip_{key}");
42 data.verify_exists_implied(Item::Localization, &loca, key);
43 let loca = format!("great_project_name_{key}");
44 data.localization.suggest(&loca, key);
45 let loca = format!("great_project_name_possessive_{key}");
46 data.localization.suggest(&loca, key);
47
48 let mut vd = Validator::new(block, data);
49
50 if !block.has_key("icon") {
51 data.verify_icon("NGameIcons|GREAT_PROJECT_TYPE_ICON_PATH", key, ".dds");
52 }
53
54 vd.multi_field_validated("icon", |bv, data| match bv {
55 BV::Value(value) => {
56 data.verify_exists(Item::File, value);
57 }
58 BV::Block(block) => {
59 let mut vd = Validator::new(block, data);
60 vd.field_trigger_builder("trigger", Tooltipped::No, |key| {
61 let mut sc = ScopeContext::new(Scopes::GreatProject, key);
62 sc.define_name("province", Scopes::Province, key);
63 sc.define_name("great_project", Scopes::GreatProject, key);
64 sc.define_name("owner", Scopes::Character, key);
65 sc.define_name("founder", Scopes::Character, key);
66 sc
67 });
68 vd.field_item("reference", Item::File);
69 }
70 });
71
72 vd.multi_field_validated_block("illustration", |block, data| {
73 let mut vd = Validator::new(block, data);
74 vd.field_trigger_builder("trigger", Tooltipped::No, |key| {
75 let mut sc = ScopeContext::new(Scopes::GreatProject, key);
76 sc.define_name("province", Scopes::Province, key);
77 sc.define_name("great_project", Scopes::GreatProject, key);
78 sc.define_name("owner", Scopes::Character, key);
79 sc.define_name("founder", Scopes::Character, key);
80 sc
81 });
82 vd.field_item("reference", Item::File);
83 });
84
85 vd.field_validated_key("name", |key, bv, data| {
87 let mut sc = ScopeContext::new(Scopes::Character, key);
88 validate_desc(bv, data, &mut sc);
89 });
90
91 vd.field_trigger_builder("is_shown", Tooltipped::No, |key| {
92 let mut sc = ScopeContext::new(Scopes::Character, key);
93 sc.define_name("province", Scopes::Province, key);
94 sc.define_name("founder", Scopes::Character, key);
95 sc
96 });
97
98 vd.field_choice("province_filter", PROVINCE_FILTERS);
99 if let Some(filter) = block.get_field_value("province_filter") {
100 if filter.is("landed_title") {
101 vd.field_item("province_filter_target", Item::Title);
102 } else if filter.is("geographical_region") {
103 vd.field_item("province_filter_target", Item::Region);
104 } else {
105 vd.ban_field(
106 "province_filter_target",
107 || "`landed_title` or `geographical_region` filters",
108 );
109 }
110 }
111
112 vd.field_trigger_builder("can_start_planning", Tooltipped::Yes, |key| {
113 let mut sc = ScopeContext::new(Scopes::Character, key);
114 sc.define_name("founder", Scopes::Character, key);
115 sc
116 });
117 vd.field_trigger_rooted("can_cancel", Tooltipped::Yes, Scopes::Character);
118 vd.field_trigger_builder("is_location_valid", Tooltipped::Yes, |key| {
119 let mut sc = ScopeContext::new(Scopes::Character, key);
120 sc.define_name("province", Scopes::Province, key);
121 sc.define_name("founder", Scopes::Character, key);
122 sc
123 });
124
125 vd.field_choice(
126 "owner",
127 &[
128 "province_owner_top_liege",
129 "province_owner",
130 "founder_primary_title_owner",
131 "founder_top_liege_title_owner",
132 ],
133 );
134 vd.field_trigger_builder("is_valid", Tooltipped::No, |key| {
135 let mut sc = ScopeContext::new(Scopes::GreatProject, key);
136 sc.define_name("province", Scopes::Province, key);
137 sc.define_name("great_project", Scopes::GreatProject, key);
138 sc.define_name("owner", Scopes::Character, key);
139 sc.define_name("founder", Scopes::Character, key);
140 sc
141 });
142 vd.field_validated_key_block("cost", |key, block, data| {
143 let mut sc = ScopeContext::new(Scopes::Character, key);
144 validate_cost(block, data, &mut sc);
145 });
146
147 vd.field_script_value_builder("construction_time", |key| {
148 let mut sc = ScopeContext::new(Scopes::Character, key);
149 sc.define_name("great_project", Scopes::GreatProject, key);
150 sc
151 });
152 vd.field_numeric("contribution_threshold");
153 vd.advice_field(
154 "investor_cooldown",
155 "docs say `investor_cooldown` but it's `contributor_cooldown`",
156 );
157 vd.field_script_value_builder("contributor_cooldown", |key| {
158 let mut sc = ScopeContext::new(Scopes::Character, key);
159 sc.define_name("great_project", Scopes::GreatProject, key);
160 sc
161 });
162
163 for field in
164 &["on_complete", "on_cancel", "on_plan_build", "on_start_build", "on_invalidated"]
165 {
166 vd.field_effect_builder(field, Tooltipped::Yes, |key| {
167 let mut sc = ScopeContext::new(Scopes::GreatProject, key);
168 sc.define_name("province", Scopes::Province, key);
169 sc.define_name("great_project", Scopes::GreatProject, key);
170 sc.define_name("owner", Scopes::Character, key);
171 sc.define_name("founder", Scopes::Character, key);
172 sc
173 });
174 }
175
176 vd.field_item("invite_interaction", Item::CharacterInteraction);
178
179 vd.field_validated_block("allowed_contributor_filter", validate_contributor_filter);
180
181 vd.field_validated_block("project_contributions", |block, data| {
182 let mut vd = Validator::new(block, data);
183 vd.unknown_block_fields(|k, block| {
184 validate_contribution(k, block, data, key);
185 });
186 });
187
188 vd.field_script_value_no_breakdown_rooted("ai_will_do", Scopes::Character);
189 vd.field_choice("ai_province_filter", PROVINCE_FILTERS);
190 vd.field_integer("ai_check_interval");
191 vd.field_validated_key_block("ai_check_interval_by_tier", |key, b, data| {
192 let mut vd = Validator::new(b, data);
193 for tier in &["barony", "county", "duchy", "kingdom", "empire", "hegemony"] {
194 vd.req_field(tier);
195 vd.field_integer(tier);
196 }
197 if block.has_key("ai_check_interval") {
198 let msg = "must not have both `ai_check_interval` and `ai_check_interval_by_tier`";
199 warn(ErrorKey::Validation).msg(msg).loc(key).push();
200 }
201 });
202
203 vd.field_validated_block("ai_target_quick_trigger", |block, data| {
204 let mut vd = Validator::new(block, data);
205 vd.field_bool("adult");
206 vd.field_choice(
207 "rank",
208 &["barony", "county", "duchy", "kingdom", "empire", "hegemony"],
209 );
210 vd.field_list_items("government_type", Item::GovernmentType);
211 });
212
213 vd.field_bool("show_in_list");
214 vd.field_bool("is_important");
215 vd.field_choice(
216 "target_title_tier",
217 &["barony", "county", "duchy", "kingdom", "empire", "hegemony"],
218 );
219
220 vd.field_value("group");
222
223 vd.field_item("completion_sound_effect", Item::Sound);
224 }
225}
226
227fn validate_contributor_filter(block: &Block, data: &Everything) {
228 let mut vd = Validator::new(block, data);
229 vd.field_bool("vassals");
230 vd.field_bool("tributaries");
231 vd.field_bool("liege");
232 vd.field_bool("top_liege");
233 vd.field_bool("owner");
234 vd.field_bool("allies");
235}
236
237fn validate_contribution(key: &Token, block: &Block, data: &Everything, gp_key: &Token) {
238 let loca = format!("great_project_type_{gp_key}_contribution_{key}");
239 data.verify_exists_implied(Item::Localization, &loca, key);
240 let loca = format!("great_project_type_{gp_key}_contribution_{key}_desc");
241 data.verify_exists_implied(Item::Localization, &loca, key);
242 let loca = format!("great_project_type_{gp_key}_contribution_name_{key}");
243 data.localization.suggest(&loca, key);
244
245 let sc_builder = |key: &Token| {
246 let mut sc = ScopeContext::new(Scopes::Character, key);
247 sc.define_name("province", Scopes::Province, key);
248 sc.define_name("great_project", Scopes::GreatProject, key);
249 sc.define_name("founder", Scopes::Character, key);
250 sc.define_name("owner", Scopes::Character, key);
251 sc
252 };
253
254 let mut vd = Validator::new(block, data);
255 vd.field_trigger_builder("is_shown", Tooltipped::No, sc_builder);
256 vd.field_bool("show_in_planning_phase");
257 vd.field_trigger_builder("contributor_is_valid", Tooltipped::Yes, sc_builder);
260 vd.field_validated_block("allowed_contributor_filter", validate_contributor_filter);
261 vd.field_trigger_builder("context_allows_contributions", Tooltipped::Yes, sc_builder);
262 vd.field_validated_key_block("cost", |key, block, data| {
263 let mut sc = sc_builder(key);
264 validate_cost(block, data, &mut sc);
265 });
266 vd.field_script_value_builder("contributor_cooldown", sc_builder);
267 vd.field_bool("is_required");
268 vd.field_effect_builder("on_contribution_funded", Tooltipped::Yes, sc_builder);
269 vd.field_effect_builder("on_complete", Tooltipped::Yes, sc_builder);
270 vd.field_script_value_builder("ai_will_do", sc_builder);
271 vd.field_integer("ai_check_interval");
272 vd.field_validated_key_block("ai_check_interval_by_tier", |key, b, data| {
273 let mut vd = Validator::new(b, data);
274 for tier in &["barony", "county", "duchy", "kingdom", "empire", "hegemony"] {
275 vd.req_field(tier);
276 vd.field_integer(tier);
277 }
278 if block.has_key("ai_check_interval") {
279 let msg = "must not have both `ai_check_interval` and `ai_check_interval_by_tier`";
280 warn(ErrorKey::Validation).msg(msg).loc(key).push();
281 }
282 });
283}