tiger_lib/ck3/data/
buildings.rs

1use crate::block::{BV, Block};
2use crate::ck3::modif::ModifKinds;
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::modif::validate_modifs;
11use crate::scopes::Scopes;
12use crate::script_value::validate_non_dynamic_script_value;
13use crate::token::Token;
14use crate::tooltipped::Tooltipped;
15use crate::validate::validate_modifiers_with_base;
16use crate::validator::Validator;
17
18#[derive(Clone, Debug)]
19pub struct Building {
20    is_upgrade: bool,
21}
22
23inventory::submit! {
24    ItemLoader::Normal(GameFlags::Ck3, Item::Building, Building::add)
25}
26
27impl Building {
28    pub fn new() -> Self {
29        Self { is_upgrade: false }
30    }
31
32    pub fn add(db: &mut Db, key: Token, block: Block) {
33        db.add(Item::Building, key, block, Box::new(Self::new()));
34    }
35
36    pub fn finalize(db: &mut Db) {
37        let mut upgrades = Vec::new();
38        for (_, block) in db.iter_key_block(Item::Building) {
39            if let Some(token) = block.get_field_value("next_building") {
40                upgrades.push(token.to_string());
41            }
42        }
43        for upgrade in upgrades {
44            db.set_property(Item::Building, &upgrade, "is_upgrade");
45        }
46    }
47}
48
49impl DbKind for Building {
50    fn add_subitems(&self, key: &Token, block: &Block, db: &mut Db) {
51        for token in block.get_field_values("flag") {
52            db.add_flag(Item::BuildingFlag, token.clone());
53        }
54        if block.field_value_is("type", "special") {
55            db.add_flag(Item::SpecialBuilding, key.clone());
56        }
57    }
58
59    fn validate(&self, key: &Token, block: &Block, data: &Everything) {
60        fn sc_builder(key: &Token) -> ScopeContext {
61            let mut sc = ScopeContext::new(Scopes::Province, key);
62            sc.define_name("holder", Scopes::Character, key);
63            sc.define_name("county", Scopes::LandedTitle, key);
64            sc
65        }
66
67        let mut vd = Validator::new(block, data);
68
69        let graphical_only = block.get_field_bool("is_graphical_background").unwrap_or(false);
70        if !graphical_only {
71            let loca = format!("building_{key}");
72            data.verify_exists_implied(Item::Localization, &loca, key);
73            let loca = format!("building_{key}_desc");
74            data.verify_exists_implied(Item::Localization, &loca, key);
75            if !self.is_upgrade {
76                let loca = format!("building_type_{key}");
77                data.verify_exists_implied(Item::Localization, &loca, key);
78                let loca = format!("building_type_{key}_desc");
79                data.verify_exists_implied(Item::Localization, &loca, key);
80            }
81        }
82
83        vd.field_icon("type_icon", "NGameIcons|BUILDING_TYPE_ICON_PATH", "");
84
85        // TODO: int
86        vd.field_validated("levy", validate_non_dynamic_script_value);
87        // TODO: int
88        vd.field_validated("max_garrison", validate_non_dynamic_script_value);
89        // TODO: 0..1
90        vd.field_validated("garrison_reinforcement_factor", validate_non_dynamic_script_value);
91        // TODO: int
92        vd.field_validated("construction_time", validate_non_dynamic_script_value);
93        vd.field_choice("type", &["regular", "special", "duchy_capital", "great_building"]);
94
95        vd.multi_field_validated_block("asset", validate_asset);
96
97        vd.field_trigger_builder(
98            "is_enabled",
99            if graphical_only { Tooltipped::No } else { Tooltipped::FailuresOnly },
100            sc_builder,
101        );
102
103        // TODO: only for Great Buildings
104        vd.field_trigger_builder("can_rebuild", Tooltipped::Yes, sc_builder);
105
106        // For buildings that are upgrades, can_construct_potential is added to can_construct_showing_failures_only so it will be tooltipped
107        vd.field_trigger_builder(
108            "can_construct_potential",
109            if block.get_field_bool("show_disabled").unwrap_or(false) || self.is_upgrade {
110                Tooltipped::FailuresOnly
111            } else {
112                Tooltipped::No
113            },
114            sc_builder,
115        );
116
117        vd.field_trigger_builder(
118            "can_construct_showing_failures_only",
119            Tooltipped::FailuresOnly,
120            sc_builder,
121        );
122
123        vd.field_trigger_builder("can_construct", Tooltipped::Yes, sc_builder);
124        vd.field_bool("show_disabled");
125
126        vd.field_script_value_rooted("cost_gold", Scopes::Character);
127        vd.field_script_value_rooted("cost_piety", Scopes::Character);
128        vd.field_script_value_rooted("cost_prestige", Scopes::Character);
129        vd.field_validated_block_rooted("cost", Scopes::Character, validate_cost);
130
131        vd.field_item("next_building", Item::Building);
132        vd.field_validated_rooted("effect_desc", Scopes::None, validate_desc);
133
134        let is_duchy_capital = block.get_field_value("type").is_some_and(|t| t.is("duchy_capital"));
135        validate_modifiers(&mut vd, is_duchy_capital);
136        vd.field_validated_block("fallback", |block, data| {
137            let mut vd = Validator::new(block, data);
138            validate_modifiers(&mut vd, is_duchy_capital);
139        });
140
141        vd.multi_field_value("flag");
142
143        vd.field_validated_key("ai_value", |key, bv, data| match bv {
144            BV::Value(token) => {
145                token.expect_integer();
146            }
147            BV::Block(block) => {
148                let mut sc = ScopeContext::new(Scopes::Province, key);
149                // TODO: docs say scope:character but script uses holder
150                sc.define_name("holder", Scopes::Character, key);
151                sc.define_name("holding", Scopes::HoldingType, key);
152                validate_modifiers_with_base(block, data, &mut sc);
153            }
154        });
155
156        vd.field_bool("is_graphical_background");
157
158        for field in ["on_start", "on_cancelled", "on_complete"] {
159            vd.field_effect_builder(field, Tooltipped::No, |key| {
160                let mut sc = ScopeContext::new(Scopes::Province, key);
161                sc.define_name("character", Scopes::Character, key);
162                sc.define_name("holding", Scopes::HoldingType, key);
163                sc
164            });
165        }
166
167        // TODO: only for Great Buildings
168        vd.field_item("great_project_type", Item::GreatProjectType);
169
170        // undocumented
171
172        // TODO: only for Great Buildings
173        vd.field_validated_block_rooted("rebuild_cost", Scopes::Character, validate_cost);
174        vd.field_bool("is_mandala_capital");
175    }
176
177    fn set_property(&mut self, _key: &Token, _block: &Block, property: &str) {
178        if property == "is_upgrade" {
179            self.is_upgrade = true;
180        }
181    }
182}
183
184fn validate_asset(block: &Block, data: &Everything) {
185    let mut vd = Validator::new(block, data);
186    vd.req_field("type");
187    vd.field_choice("type", &["pdxmesh", "entity"]);
188
189    let meshes = block.field_value_is("type", "pdxmesh");
190    let itype = if meshes { Item::Pdxmesh } else { Item::Entity };
191
192    vd.multi_field_item("name", itype);
193    vd.field_list_items("names", itype);
194
195    vd.field_item("domicile_building", Item::DomicileBuilding);
196
197    vd.field_item("illustration", Item::File);
198
199    vd.multi_field_validated("soundeffect", |bv, data| {
200        match bv {
201            BV::Value(token) => data.verify_exists(Item::Sound, token),
202            BV::Block(block) => {
203                let mut vd = Validator::new(block, data);
204                vd.req_field("soundeffect");
205                vd.field_item("soundeffect", Item::Sound);
206                vd.field_block("soundparameter"); // TODO
207            }
208        }
209    });
210
211    vd.field_list_items("graphical_cultures", Item::BuildingGfx);
212    vd.field_list_items("graphical_faiths", Item::GraphicalFaith);
213    vd.field_list_items("graphical_regions", Item::Region); // TODO check that it's a graphical region
214    vd.field_list_items("provinces", Item::Province);
215    vd.field_list_items("governments", Item::GovernmentType);
216
217    vd.field_item("requires_dlc_flag", Item::DlcFeature);
218}
219
220fn validate_modifiers(vd: &mut Validator, is_duchy_capital: bool) {
221    vd.multi_field_validated_block("character_modifier", |block, data| {
222        let vd = Validator::new(block, data);
223        validate_modifs(block, data, ModifKinds::Character, vd);
224    });
225    vd.multi_field_validated_block("character_culture_modifier", |block, data| {
226        let mut vd = Validator::new(block, data);
227        vd.req_field("parameter");
228        vd.field_item("parameter", Item::CultureParameter);
229        validate_modifs(block, data, ModifKinds::Character, vd);
230    });
231    vd.multi_field_validated_block("character_dynasty_modifier", |block, data| {
232        let mut vd = Validator::new(block, data);
233        vd.req_field("county_holder_dynasty_perk");
234        vd.field_item("county_holder_dynasty_perk", Item::DynastyPerk);
235        validate_modifs(block, data, ModifKinds::Character, vd);
236    });
237    vd.multi_field_validated_block("character_faith_modifier", |block, data| {
238        let mut vd = Validator::new(block, data);
239        vd.req_field("parameter");
240        vd.field_item("parameter", Item::DoctrineParameter);
241        validate_modifs(block, data, ModifKinds::Character, vd);
242    });
243
244    vd.multi_field_validated_block("province_modifier", |block, data| {
245        let vd = Validator::new(block, data);
246        validate_modifs(block, data, ModifKinds::Province, vd);
247    });
248    vd.multi_field_validated_block("province_culture_modifier", |block, data| {
249        let mut vd = Validator::new(block, data);
250        vd.req_field("parameter");
251        vd.field_item("parameter", Item::CultureParameter);
252        validate_modifs(block, data, ModifKinds::Province, vd);
253    });
254    vd.multi_field_validated_block("province_faith_modifier", |block, data| {
255        let mut vd = Validator::new(block, data);
256        vd.req_field("parameter");
257        vd.field_item("parameter", Item::DoctrineParameter);
258        validate_modifs(block, data, ModifKinds::Province, vd);
259    });
260    vd.multi_field_validated_block("province_terrain_modifier", |block, data| {
261        let mut vd = Validator::new(block, data);
262        vd.field_item("parameter", Item::CultureParameter);
263        vd.field_item("terrain", Item::Terrain);
264        vd.field_bool("is_coastal");
265        validate_modifs(block, data, ModifKinds::Province, vd);
266    });
267    vd.multi_field_validated_block("province_dynasty_modifier", |block, data| {
268        let mut vd = Validator::new(block, data);
269        vd.req_field("county_holder_dynasty_perk");
270        vd.field_item("county_holder_dynasty_perk", Item::DynastyPerk);
271        validate_modifs(block, data, ModifKinds::Province, vd);
272    });
273
274    vd.multi_field_validated_block("county_modifier", |block, data| {
275        let vd = Validator::new(block, data);
276        validate_modifs(block, data, ModifKinds::County, vd);
277    });
278    vd.multi_field_validated_block("county_culture_modifier", |block, data| {
279        let mut vd = Validator::new(block, data);
280        vd.req_field("parameter");
281        vd.field_item("parameter", Item::CultureParameter);
282        validate_modifs(block, data, ModifKinds::County, vd);
283    });
284    vd.multi_field_validated_block("county_faith_modifier", |block, data| {
285        let mut vd = Validator::new(block, data);
286        vd.req_field("parameter");
287        vd.field_item("parameter", Item::DoctrineParameter);
288        validate_modifs(block, data, ModifKinds::County, vd);
289    });
290
291    if is_duchy_capital {
292        vd.multi_field_validated_block("duchy_capital_county_modifier", |block, data| {
293            let vd = Validator::new(block, data);
294            validate_modifs(block, data, ModifKinds::County, vd);
295        });
296        vd.multi_field_validated_block("duchy_capital_county_culture_modifier", |block, data| {
297            let mut vd = Validator::new(block, data);
298            vd.req_field("parameter");
299            vd.field_item("parameter", Item::CultureParameter);
300            validate_modifs(block, data, ModifKinds::County, vd);
301        });
302        vd.multi_field_validated_block("duchy_capital_county_faith_modifier", |block, data| {
303            let mut vd = Validator::new(block, data);
304            vd.req_field("parameter");
305            vd.field_item("parameter", Item::DoctrineParameter);
306            validate_modifs(block, data, ModifKinds::County, vd);
307        });
308
309        vd.multi_field_validated_block("duchy_capital_county_situation_modifier", |block, data| {
310            let mut vd = Validator::new(block, data);
311            vd.req_field("parameter");
312            vd.field_item("parameter", Item::SituationPhaseParameter);
313            validate_modifs(block, data, ModifKinds::County, vd);
314        });
315    } else {
316        vd.ban_field("duchy_capital_county_modifier", || "duchy_capital buildings");
317        vd.ban_field("duchy_capital_county_culture_modifier", || "duchy_capital buildings");
318        vd.ban_field("duchy_capital_county_situation_modifier", || "duchy_capital buildings");
319    }
320
321    vd.multi_field_validated_block("county_situation_modifier", |block, data| {
322        let mut vd = Validator::new(block, data);
323        vd.req_field("parameter");
324        vd.field_item("parameter", Item::SituationPhaseParameter);
325        validate_modifs(block, data, ModifKinds::County, vd);
326    });
327
328    vd.multi_field_validated_block("province_situation_modifier", |block, data| {
329        let mut vd = Validator::new(block, data);
330        vd.req_field("parameter");
331        vd.field_item("parameter", Item::SituationPhaseParameter);
332        validate_modifs(block, data, ModifKinds::Province, vd);
333    });
334
335    vd.multi_field_validated_block("character_situation_modifier", |block, data| {
336        let mut vd = Validator::new(block, data);
337        vd.req_field("parameter");
338        vd.field_item("parameter", Item::SituationPhaseParameter);
339        validate_modifs(block, data, ModifKinds::Character, vd);
340    });
341
342    vd.multi_field_validated_block("county_holding_modifier", |block, data| {
343        let mut vd = Validator::new(block, data);
344        vd.req_field("holding");
345        vd.field_item("holding", Item::HoldingType);
346        validate_modifs(block, data, ModifKinds::County, vd);
347    });
348    vd.multi_field_validated_block("county_dynasty_modifier", |block, data| {
349        let mut vd = Validator::new(block, data);
350        vd.req_field("county_holder_dynasty_perk");
351        vd.field_item("county_holder_dynasty_perk", Item::DynastyPerk);
352        validate_modifs(block, data, ModifKinds::County, vd);
353    });
354    vd.multi_field_validated_block("county_holder_character_modifier", |block, data| {
355        let vd = Validator::new(block, data);
356        validate_modifs(block, data, ModifKinds::Character, vd);
357    });
358
359    vd.multi_field_validated_block("province_government_modifier", |block, data| {
360        let mut vd = Validator::new(block, data);
361        vd.req_field("parameter");
362        vd.field_item("parameter", Item::GovernmentFlag);
363        validate_modifs(block, data, ModifKinds::Province, vd);
364    });
365
366    vd.multi_field_validated_block("character_government_modifier", |block, data| {
367        let mut vd = Validator::new(block, data);
368        vd.req_field("parameter");
369        vd.field_item("parameter", Item::GovernmentFlag);
370        validate_modifs(block, data, ModifKinds::Character, vd);
371    });
372}