1use crate::block::Block;
2use crate::context::ScopeContext;
3use crate::db::{Db, DbKind};
4use crate::everything::Everything;
5use crate::game::GameFlags;
6use crate::item::{Item, ItemLoader};
7use crate::modif::validate_modifs;
8use crate::report::{ErrorKey, err};
9use crate::scopes::Scopes;
10use crate::script_value::validate_non_dynamic_script_value;
11use crate::token::Token;
12use crate::tooltipped::Tooltipped;
13use crate::validator::Validator;
14use crate::vic3::data::production_methods::ProductionMethodGroup;
15use crate::vic3::modif::ModifKinds;
16use crate::vic3::tables::modifs::maybe_warn_modifiable_capitalization;
17
18#[derive(Clone, Debug)]
19pub struct BuildingType {}
20
21inventory::submit! {
22 ItemLoader::Normal(GameFlags::Vic3, Item::BuildingType, BuildingType::add)
23}
24
25impl BuildingType {
26 pub fn add(db: &mut Db, key: Token, block: Block) {
27 db.add(Item::BuildingType, key, block, Box::new(Self {}));
28 }
29
30 #[allow(clippy::unused_self)]
31 pub fn validate_production_method(
32 &self,
33 pm: &Token,
34 building: &Token,
35 block: &Block,
36 data: &Everything,
37 ) {
38 for group in block.get_multi_field_list("production_method_groups") {
39 if let Some((_, block, group_item)) =
40 data.get_item::<ProductionMethodGroup>(Item::ProductionMethodGroup, group.as_str())
41 && group_item.contains_production_method(pm, block, data)
42 {
43 return;
44 }
45 }
46 let msg = format!("production method `{pm}` not valid for `{building}`");
47 err(ErrorKey::Validation).msg(msg).loc(pm).push();
48 }
49
50 pub fn is_discoverable(block: &Block, data: &Everything) -> bool {
51 let mut seen = Vec::new();
52 if let Some(group) = block.get_field_value("building_group") {
53 let mut group = group.as_str();
54 loop {
55 seen.push(group);
56 if let Some((_, block)) = data.get_key_block(Item::BuildingGroup, group) {
57 if block.get_field_bool("discoverable_resource").unwrap_or(false) {
58 return true;
59 }
60 if let Some(parent) = block.get_field_value("parent_group") {
61 if seen.contains(&parent.as_str()) {
62 let msg = "cycle in building groups";
63 let info =
64 format!("building group `{parent}` ends up being its own parent");
65 err(ErrorKey::Loop).msg(msg).info(info).loc(parent).push();
66 break;
67 }
68 group = parent.as_str();
69 } else {
70 break;
71 }
72 } else {
73 break;
74 }
75 }
76 }
77 false
78 }
79}
80
81impl DbKind for BuildingType {
82 fn add_subitems(&self, _key: &Token, block: &Block, db: &mut Db) {
83 for token in block.get_multi_field_list("aliases") {
84 db.add_flag(Item::BuildingType, token.clone());
85 }
86 }
87
88 fn has_property(
89 &self,
90 _key: &Token,
91 block: &Block,
92 property: &str,
93 _data: &Everything,
94 ) -> bool {
95 if property == "max_level" {
96 return block.get_field_bool("has_max_level").unwrap_or(false);
97 }
98 false
99 }
100
101 fn validate(&self, key: &Token, block: &Block, data: &Everything) {
102 let mut vd = Validator::new(block, data);
103 let mut sc = ScopeContext::new(Scopes::Country, key);
104 let mut building_sc = ScopeContext::new(Scopes::Building, key);
105 let mut state_sc = ScopeContext::new(Scopes::State, key);
106 state_sc.define_name("num_levels", Scopes::Value, key);
107
108 maybe_warn_modifiable_capitalization(key);
109
110 data.verify_exists(Item::Localization, key);
111 if block.get_field_bool("expandable").unwrap_or(true) {
112 let loca = format!("{key}_lens_option");
113 data.mark_used(Item::Localization, &loca);
115 }
116
117 if BuildingType::is_discoverable(block, data) {
118 let loca = format!("{key}_discovered_resource");
119 data.verify_exists_implied(Item::Localization, &loca, key);
120 }
121
122 vd.multi_field_list_items("aliases", Item::BuildingType);
123
124 vd.field_item("building_group", Item::BuildingGroup);
125 vd.replaced_field("texture", "icon");
126 vd.field_item("icon", Item::File);
127
128 vd.field_bool("buildable");
129 vd.field_bool("expandable");
130 vd.field_bool("downsizeable");
131 vd.field_bool("unique");
132 vd.field_bool("has_max_level");
133 vd.field_bool("ignore_stateregion_max_level");
134 vd.field_bool("enable_air_connection");
135 vd.field_bool("port");
136
137 if block.get_field_bool("has_max_level").unwrap_or(false) {
138 let modif = format!("state_{key}_max_level_add");
139 data.verify_exists_implied(Item::ModifierTypeDefinition, &modif, key);
140 }
141
142 vd.replaced_field("recruits_combat_unit", "recruits_combat_units = yes");
143 vd.field_bool("recruits_combat_units");
144 vd.field_bool("recruits_sailors");
145
146 vd.field_bool("company_headquarter");
147
148 vd.multi_field_list_items("unlocking_technologies", Item::Technology);
149 vd.field_trigger("potential", Tooltipped::No, &mut state_sc);
150 vd.field_trigger("possible", Tooltipped::No, &mut state_sc);
151 vd.field_trigger("can_build", Tooltipped::Yes, &mut state_sc);
152 vd.field_trigger("can_build_government", Tooltipped::Yes, &mut state_sc);
153 vd.field_trigger("can_build_private", Tooltipped::Yes, &mut state_sc);
154 vd.field_trigger_rooted("can_have_country_monopoly", Tooltipped::Yes, Scopes::Country);
155
156 vd.field_integer("construction_points");
157 vd.field_validated_block("construction_modifier", |block, data| {
158 let vd = Validator::new(block, data);
159 validate_modifs(block, data, ModifKinds::Building, vd);
160 });
161 vd.field_validated("required_construction", validate_non_dynamic_script_value);
162
163 vd.field_item("owners", Item::PopType);
164 vd.field_numeric_range("economic_contribution", 0.0..=1.0);
165 vd.field_numeric("min_raise_to_hire");
166
167 vd.field_bool("naval");
168 vd.field_item("strait", Item::StraitDefinition);
169
170 vd.field_script_value_rooted("ai_value", Scopes::State);
171 vd.field_numeric("ai_subsidies_weight");
172 vd.advice_field(
173 "ai_privatization_desire",
174 "docs say ai_privatization_desire but it's ai_nationalization_desire",
175 );
176 vd.field_script_value("ai_nationalization_desire", &mut sc);
177
178 vd.field_item("slaves_role", Item::PopType);
179
180 vd.multi_field_list_items("production_method_groups", Item::ProductionMethodGroup);
182
183 vd.field_trigger("should_auto_expand", Tooltipped::Yes, &mut building_sc);
184
185 vd.field_choice("city_type", &["none", "city", "farm", "mine", "port", "wood"]);
186 vd.field_bool("generates_residences");
187 vd.field_item("terrain_manipulator", Item::TerrainManipulator);
188 vd.field_integer("levels_per_mesh");
189 vd.field_integer("residence_points_per_level");
190 vd.advice_field("override_centerpiece_mesh", "removed in 1.9");
191 vd.advice_field("centerpiece_mesh_weight", "removed in 1.9");
192 vd.field_bool("statue");
193
194 vd.field_list("meshes"); vd.field_list("entity_not_constructed"); vd.field_list("entity_under_construction"); vd.field_list("entity_constructed"); vd.field_value("locator"); vd.field_value("lens"); vd.field_choice("ownership_type", &["no_ownership", "self", "other"]);
202 vd.field_item("background", Item::File);
203
204 vd.field_validated_block("city_gfx_interactions", |block, data| {
207 let mut vd = Validator::new(block, data);
208 vd.field_bool("clear_collision_mesh_area");
209 vd.field_bool("clear_size_area");
210 vd.field_integer("size");
211 });
212
213 vd.replaced_field("cannot_switch_owner", "can_switch_owner");
214 vd.field_bool("can_switch_owner");
215
216 vd.field_validated_block("investment_scores", |block, data| {
217 let mut vd = Validator::new(block, data);
218 vd.unknown_block_fields(|_, block| {
219 let mut vd = Validator::new(block, data);
220 vd.field_item("group", Item::BuildingGroup);
221 vd.field_script_value_no_breakdown_rooted("score", Scopes::Country);
222 });
223 });
224 }
225}
226
227#[derive(Clone, Debug)]
228pub struct BuildingGroup {}
229
230inventory::submit! {
231 ItemLoader::Normal(GameFlags::Vic3, Item::BuildingGroup, BuildingGroup::add)
232}
233
234impl BuildingGroup {
235 pub fn add(db: &mut Db, key: Token, block: Block) {
236 db.add(Item::BuildingGroup, key, block, Box::new(Self {}));
237 }
238}
239
240impl DbKind for BuildingGroup {
241 fn validate(&self, key: &Token, block: &Block, data: &Everything) {
242 let mut vd = Validator::new(block, data);
243
244 maybe_warn_modifiable_capitalization(key);
245
246 data.verify_exists(Item::Localization, key);
247
248 vd.field_item("parent_group", Item::BuildingGroup);
249 vd.field_item("texture", Item::File);
250
251 vd.field_bool("always_possible");
252 vd.field_bool("economy_of_scale");
253 vd.field_bool("is_subsistence");
254 vd.field_bool("auto_place_buildings");
255 vd.field_bool("capped_by_resources");
256 vd.field_bool("discoverable_resource");
257 vd.field_bool("depletable_resource");
258 vd.field_bool("can_use_slaves");
259 vd.field_bool("inheritable_construction");
260 vd.field_bool("stateregion_max_level");
261 vd.field_bool("subsidized");
262 vd.field_bool("pays_taxes"); vd.advice_field("created_by_trade_routes", "removed in 1.9"); vd.field_bool("always_self_owning"); vd.field_bool("has_trade_revenue"); vd.field_bool("company_headquarter"); vd.field_bool("regional_company_headquarter"); vd.field_choice("category", &["urban", "rural", "development"]);
271 vd.field_choice("land_usage", &["urban", "rural"]);
272
273 vd.field_item("default_building", Item::BuildingType);
275 vd.field_value("lens"); vd.field_numeric("cash_reserves_max");
278 vd.field_numeric("urbanization");
279
280 vd.field_numeric("hiring_rate");
281 vd.field_numeric("min_hiring_rate");
282 vd.field_numeric("max_hiring_rate");
283 vd.field_numeric("proportionality_limit");
284 vd.field_bool("hires_unemployed_only");
285 vd.field_bool("ignores_productivity_when_hiring");
286
287 vd.field_trigger_rooted("should_auto_expand", Tooltipped::No, Scopes::Building);
288
289 vd.field_numeric("economy_of_scale_ai_factor");
292 vd.field_numeric("foreign_investment_ai_factor");
293 vd.field_numeric("infrastructure_usage_per_level");
294 vd.field_numeric("min_productivity_to_hire");
295 vd.field_bool("fired_pops_become_radical");
296 vd.field_bool("is_military");
297 vd.field_bool("is_government_funded");
298 vd.field_bool("owns_other_buildings");
299 vd.field_bool("is_shown_in_outliner");
300 vd.field_bool("construction_efficiency_modifier");
301 vd.field_bool("self_investment_chance_modifier");
302 }
303}