1use crate::block::{BV, Block};
2use crate::context::ScopeContext;
3use crate::db::{Db, DbKind};
4use crate::everything::Everything;
5use crate::game::GameFlags;
6use crate::item::{Item, ItemLoader, LoadAsFile, Recursive};
7use crate::modif::validate_modifs;
8use crate::pdxfile::PdxEncoding;
9use crate::report::{ErrorKey, untidy, warn};
10use crate::scopes::Scopes;
11use crate::token::Token;
12use crate::util::SmartJoin;
13use crate::validator::Validator;
14use crate::vic3::modif::ModifKinds;
15use crate::vic3::tables::misc::TERRAIN_KEYS;
16
17#[derive(Clone, Debug)]
18pub struct Terrain {}
19
20inventory::submit! {
21 ItemLoader::Normal(GameFlags::Vic3, Item::Terrain, Terrain::add)
22}
23
24impl Terrain {
25 pub fn add(db: &mut Db, key: Token, block: Block) {
26 db.add(Item::Terrain, key, block, Box::new(Self {}));
27 }
28}
29
30impl DbKind for Terrain {
31 fn validate(&self, key: &Token, block: &Block, data: &Everything) {
32 let mut vd = Validator::new(block, data);
33
34 data.verify_exists(Item::Localization, key);
35 vd.multi_field_item("label", Item::TerrainLabel);
36
37 vd.field_script_value_rooted("weight", Scopes::Province);
38 vd.multi_field_validated_key_block("textures", |key, block, data| {
39 let mut vd = Validator::new(block, data);
40 vd.validated_blocks(|block, data| {
41 let mut vd = Validator::new(block, data);
42 let mut sc = ScopeContext::new(Scopes::Province, key);
43 sc.define_name("state", Scopes::State, key);
44 sc.define_name("region", Scopes::StateRegion, key);
45 vd.field_script_value("weight", &mut sc);
46 vd.field_item("path", Item::File);
47 vd.field_item("effect", Item::Entity);
48 });
49 });
50
51 vd.field_numeric("combat_width");
52 vd.field_numeric("risk");
53
54 vd.field_validated_block("materials", |block, data| {
55 let mut vd = Validator::new(block, data);
56 vd.unknown_value_fields(|key, value| {
57 data.verify_exists(Item::TerrainMaterial, key);
58 value.expect_number();
59 });
60 });
61 vd.field_numeric("pollution_mask_strength");
62 vd.field_numeric("devastation_mask_strength");
63
64 vd.field("debug_color");
66
67 vd.field_item("created_material", Item::TerrainMaterial);
69 }
70}
71
72#[derive(Clone, Debug)]
73pub struct TerrainLabel {}
74
75inventory::submit! {
76 ItemLoader::Normal(GameFlags::Vic3, Item::TerrainLabel, TerrainLabel::add)
77}
78
79impl TerrainLabel {
80 pub fn add(db: &mut Db, key: Token, block: Block) {
81 db.add(Item::TerrainLabel, key, block, Box::new(Self {}));
82 }
83}
84
85impl DbKind for TerrainLabel {
86 fn validate(&self, key: &Token, block: &Block, data: &Everything) {
87 let mut vd = Validator::new(block, data);
88
89 data.verify_exists(Item::Localization, key);
90 let loca = format!("{key}_desc");
91 data.verify_exists_implied(Item::Localization, &loca, key);
92
93 vd.field_choice("type", &["terrain"]);
95 vd.field_choice("modifier_key", TERRAIN_KEYS);
96 vd.replaced_field("modifiers", "modifier");
97 vd.field_validated_block("modifier", |block, data| {
98 let vd = Validator::new(block, data);
99 validate_modifs(block, data, ModifKinds::UnitCombat, vd);
100 });
101 }
102}
103
104#[derive(Clone, Debug)]
105pub struct TerrainManipulator {}
106
107inventory::submit! {
108 ItemLoader::Normal(GameFlags::Vic3, Item::TerrainManipulator, TerrainManipulator::add)
109}
110
111impl TerrainManipulator {
112 pub fn add(db: &mut Db, key: Token, block: Block) {
113 if key.loc.pathname().components().count() > 3 {
116 return;
117 }
118 db.add(Item::TerrainManipulator, key, block, Box::new(Self {}));
119 }
120}
121
122impl DbKind for TerrainManipulator {
123 fn validate(&self, _key: &Token, block: &Block, data: &Everything) {
124 let mut vd = Validator::new(block, data);
125
126 vd.field_item("created_terrain", Item::Terrain);
127 vd.field_item("terrain_mask", Item::TerrainMask);
128 vd.field_item("preferred_terrain", Item::Terrain);
129 vd.field_choice("city_type", &["none", "city", "farm", "mine", "port", "wood"]);
131 vd.field_bool("coastal");
132
133 vd.field_validated_key_block("toggle_map_object_layers", |key, block, data| {
134 let mut vd = Validator::new(block, data);
135 if let Some(array) =
136 data.get_defined_array_warn(key, "NGraphics|DYNAMIC_MAP_OBJECT_LAYERS")
137 {
138 for layer in array.iter_values_warn() {
139 vd.field_validated(layer.as_str(), validate_layer);
140 }
141 }
142 });
143 }
144}
145
146#[derive(Clone, Debug)]
147pub struct TerrainMaterial {}
148
149inventory::submit! {
150 ItemLoader::Full(GameFlags::Vic3, Item::TerrainMaterial, PdxEncoding::Utf8OptionalBom, ".settings", LoadAsFile::Yes, Recursive::No, TerrainMaterial::add)
151}
152
153impl TerrainMaterial {
154 #[allow(clippy::needless_pass_by_value)] pub fn add(db: &mut Db, _key: Token, block: Block) {
157 for block in block.iter_blocks_warn() {
159 for block in block.iter_blocks_warn() {
160 if let Some(name) = block.get_field_value("name") {
163 db.add(Item::TerrainMaterial, name.clone(), block.clone(), Box::new(Self {}));
164 } else {
165 untidy(ErrorKey::FieldMissing).msg("texture with no name").loc(block).push();
166 }
167 }
168 }
169 }
170}
171
172impl DbKind for TerrainMaterial {
173 fn validate(&self, _key: &Token, block: &Block, data: &Everything) {
174 let mut vd = Validator::new(block, data);
175
176 vd.field_value("name");
177 vd.field_value("id");
178
179 for field in &["diffuse", "normal", "material"] {
180 vd.req_field(field);
181 if let Some(token) = vd.field_value(field) {
182 let path = block.loc.pathname().smart_join_parent(token.as_str());
183 data.verify_exists_implied(Item::File, &path.to_string_lossy(), token);
184 }
185 }
186
187 vd.req_field("mask");
188 vd.field_value("mask");
189 }
190}
191
192#[derive(Clone, Debug)]
193pub struct TerrainMask {}
194
195impl TerrainMask {
196 #[allow(clippy::needless_pass_by_value)] pub fn add_json(db: &mut Db, block: Block) {
198 let mut count = 0;
201 for block in block.iter_blocks_warn() {
202 count += 1;
203 if count == 2 {
204 warn(ErrorKey::Validation).msg("expected only one block").loc(block).push();
205 }
206 if let Some(block) = block.get_field_block("masks") {
207 for block in block.iter_blocks_warn() {
208 if let Some(token) = block.get_field_value("key") {
209 db.add(Item::TerrainMask, token.clone(), block.clone(), Box::new(Self {}));
210 } else {
211 warn(ErrorKey::FieldMissing).msg("mask with no key").loc(block).push();
212 }
213 }
214 }
215 }
216 }
217}
218
219impl DbKind for TerrainMask {
220 fn validate(&self, key: &Token, block: &Block, data: &Everything) {
221 let mut vd = Validator::new(block, data);
222 vd.field_value("key");
223 vd.req_field("filename");
224 if let Some(token) = vd.field_value("filename") {
225 let path = key.loc.pathname().smart_join_parent(token.as_str());
226 data.verify_exists_implied(Item::File, &path.to_string_lossy(), token);
227 }
228 }
229}
230
231fn validate_layer(bv: &BV, data: &Everything) {
232 match bv {
233 BV::Value(token) => {
234 if !token.is("show_above_default") && !token.is("show_below_default") {
235 let msg = "unknown layer position `{token}`";
236 warn(ErrorKey::UnknownField).msg(msg).loc(token).push();
237 }
238 }
239 BV::Block(block) => {
240 let mut vd = Validator::new(block, data);
241 vd.req_field_one_of(&["show_above", "show_below"]);
242 vd.field_numeric("show_above");
243 vd.field_numeric("show_below");
244 }
245 }
246}