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); 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"); });
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}