tiger_lib/vic3/data/
city_data.rs

1use crate::block::Block;
2use crate::db::{Db, DbKind};
3use crate::everything::Everything;
4use crate::game::GameFlags;
5use crate::item::{Item, ItemLoader};
6use crate::report::{ErrorKey, Severity, warn};
7use crate::scopes::Scopes;
8use crate::token::Token;
9use crate::tooltipped::Tooltipped;
10use crate::validator::{Validator, ValueValidator};
11use crate::vic3::validate::validate_locators;
12
13#[derive(Clone, Debug)]
14pub struct CityBuildingVfx {}
15#[derive(Clone, Debug)]
16pub struct CityCenterpiece {}
17#[derive(Clone, Debug)]
18pub struct CityGraphicsType {}
19#[derive(Clone, Debug)]
20pub struct CityVfx {}
21
22inventory::submit! {
23    ItemLoader::Normal(GameFlags::Vic3, Item::CityBuildingVfx, CityBuildingVfx::add)
24}
25inventory::submit! {
26    ItemLoader::Normal(GameFlags::Vic3, Item::CityCenterpiece, CityCenterpiece::add)
27}
28inventory::submit! {
29    ItemLoader::Normal(GameFlags::Vic3, Item::CityGraphicsType, CityGraphicsType::add)
30}
31inventory::submit! {
32    ItemLoader::Normal(GameFlags::Vic3, Item::CityVfx, CityVfx::add)
33}
34
35impl CityBuildingVfx {
36    pub fn add(db: &mut Db, key: Token, block: Block) {
37        db.add(Item::CityBuildingVfx, key, block, Box::new(Self {}));
38    }
39}
40impl CityCenterpiece {
41    pub fn add(db: &mut Db, key: Token, block: Block) {
42        db.add(Item::CityCenterpiece, key, block, Box::new(Self {}));
43    }
44}
45impl CityGraphicsType {
46    pub fn add(db: &mut Db, key: Token, block: Block) {
47        db.add(Item::CityGraphicsType, key, block, Box::new(Self {}));
48    }
49}
50impl CityVfx {
51    pub fn add(db: &mut Db, key: Token, block: Block) {
52        db.add(Item::CityVfx, key, block, Box::new(Self {}));
53    }
54}
55
56impl DbKind for CityBuildingVfx {
57    fn validate(&self, _key: &Token, block: &Block, data: &Everything) {
58        let mut vd = Validator::new(block, data);
59        vd.set_max_severity(Severity::Warning);
60
61        if let Some(particle) = vd.field_value("particle") {
62            let pathname = format!("gfx/particles/{particle}.particle2");
63            data.verify_exists_implied(Item::File, &pathname, particle);
64            let pathname = format!("gfx/particles/{particle}.editordata");
65            data.verify_exists_implied(Item::File, &pathname, particle);
66        }
67
68        vd.field_numeric("max_visible");
69        vd.field_numeric("max_distance");
70    }
71}
72
73impl DbKind for CityCenterpiece {
74    fn validate(&self, _key: &Token, block: &Block, data: &Everything) {
75        let mut vd = Validator::new(block, data);
76        vd.set_max_severity(Severity::Warning);
77
78        vd.req_field_one_of(&["city_type", "city_graphics_type"]);
79        vd.field_choice("city_type", &["none", "city", "farm", "mine", "port", "wood"]);
80        vd.multi_field_item("city_graphics_type", Item::CityGraphicsType);
81        vd.field_trigger_rooted("trigger", Tooltipped::No, Scopes::StateRegion);
82        vd.field_integer("priority");
83        vd.field_bool("should_update_on_pm_change");
84
85        vd.field_list_integers_exactly("grid_size", 2); // undocumented
86
87        let locator_names = validate_locators(&mut vd);
88
89        let mut composition_names = Vec::new();
90        vd.field_validated_block("composition_group", |block, data| {
91            let mut vd = Validator::new(block, data);
92            vd.field_list_items("building_types", Item::BuildingType);
93            let n = block.get_field_list("building_types").map_or(0, |v| v.len());
94            vd.field_validated_list("levels", |token, _| {
95                token.expect_integer();
96            });
97            #[allow(clippy::cast_possible_wrap)]
98            let m = block.get_field_list("levels").map_or(0, |v| v.len()) as i64;
99            vd.multi_field_validated_block("composition", |block, data| {
100                let mut vd = Validator::new(block, data);
101                vd.set_max_severity(Severity::Warning);
102                vd.req_field("name");
103                if let Some(name) = vd.field_value("name") {
104                    if let Some(other) = composition_names.iter().find(|n| n == &name) {
105                        warn(ErrorKey::DuplicateField)
106                            .msg(format!("duplicate composition name `{name}`"))
107                            .loc(name)
108                            .loc_msg(other, "previous composition")
109                            .push();
110                    } else {
111                        composition_names.push(name.clone());
112                    }
113                }
114                vd.req_field_one_of(&["levels", "ratios", "trigger"]);
115
116                vd.field_trigger_rooted("trigger", Tooltipped::No, Scopes::State);
117
118                let mut count = 0;
119                vd.field_validated_list("levels", |token, data| {
120                    let mut vvd = ValueValidator::new(token, data);
121                    vvd.set_max_severity(Severity::Warning);
122                    vvd.integer_range(0..=m);
123                    count += 1;
124                });
125                if let Some(levels) = block.get_key("levels") {
126                    if count != n {
127                        let msg = "length of `levels` list should be the same as `building_types`";
128                        warn(ErrorKey::Validation).msg(msg).loc(levels).push();
129                    }
130                }
131
132                let mut count = 0;
133                vd.field_validated_list("ratios", |token, _| {
134                    token.expect_integer();
135                    count += 1;
136                });
137                if let Some(ratios) = block.get_key("ratios") {
138                    if count != n {
139                        let msg = "length of `ratios` list should be the same as `building_types`";
140                        warn(ErrorKey::Validation).msg(msg).loc(ratios).push();
141                    }
142                }
143            });
144        });
145
146        let composition_names: Vec<_> = composition_names.into_iter().map(|n| n.as_str()).collect();
147        vd.multi_field_validated_block("attach", |block, data| {
148            let mut vd = Validator::new(block, data);
149            vd.set_max_severity(Severity::Warning);
150            vd.field_choice("locator", &locator_names);
151
152            vd.multi_field_validated_block("variant", |block, data| {
153                let mut vd = Validator::new(block, data);
154                vd.set_max_severity(Severity::Warning);
155                vd.req_field_one_of(&["entity", "is_power_bloc_statue"]);
156                vd.field_item("entity", Item::Entity);
157                vd.field_bool("is_power_bloc_statue");
158                vd.field_item("building_type", Item::BuildingType);
159                vd.field_list_choice("compositions", &composition_names);
160                vd.field_block("attach"); // TODO what is attach_body
161            });
162        });
163    }
164}
165
166impl DbKind for CityGraphicsType {
167    fn validate(&self, _key: &Token, block: &Block, data: &Everything) {
168        let mut vd = Validator::new(block, data);
169        vd.set_max_severity(Severity::Warning);
170
171        vd.field_trigger_rooted("trigger", Tooltipped::No, Scopes::StateRegion);
172        vd.field_numeric("weight");
173
174        vd.field_value("graphical_culture");
175        vd.field_choice("city_type", &["none", "city", "farm", "mine", "port", "wood"]);
176
177        vd.field_integer("min_residential_buildings");
178        vd.field_integer("max_residential_buildings");
179        vd.field_integer("max_residence_points");
180
181        for field in &["rich_building_meshes", "mid_building_meshes", "poor_building_meshes"] {
182            vd.field_list_items(field, Item::Pdxmesh);
183        }
184
185        for field in &[
186            "rich_building_offsets",
187            "mid_building_offsets",
188            "poor_building_offsets",
189            "building_offsets",
190        ] {
191            vd.field_validated_block(field, |block, data| {
192                let mut vd = Validator::new(block, data);
193                vd.set_max_severity(Severity::Warning);
194
195                vd.field_numeric("position");
196                vd.field_numeric("rotation");
197            });
198        }
199
200        vd.validate_item_key_blocks(Item::BuildingType, |_, block, data| {
201            let mut vd = Validator::new(block, data);
202            vd.set_max_severity(Severity::Warning);
203
204            for value in vd.values() {
205                data.verify_exists_max_sev(Item::Pdxmesh, value, Severity::Warning);
206            }
207        });
208    }
209}
210
211impl DbKind for CityVfx {
212    fn validate(&self, _key: &Token, block: &Block, data: &Everything) {
213        let mut vd = Validator::new(block, data);
214        vd.set_max_severity(Severity::Warning);
215        vd.field_trigger_rooted("visible", Tooltipped::No, Scopes::State);
216        vd.field_item("entity", Item::Entity);
217    }
218}