banished_formats/game/world.rs
1//! Map generation, terrain and climate: the New Game profiles and the map description.
2
3use super::*;
4
5/// `TerrainGenerator`: a New Game terrain profile.
6///
7/// Original: `FUN_1400b6b60`.
8#[derive(Debug, Clone)]
9pub struct TerrainGenerator {
10 /// The string table.
11 pub string_table: Option<String>,
12 /// The name's key.
13 pub name: String,
14 /// Whether it is listed in the New Game dialog (*inferred*, as in [`ClimateHeader`]).
15 pub flag: bool,
16 /// Terrain, river and lake material instances.
17 pub materials: [String; 3],
18 /// Small / Medium / Large size records, 17 integers each in serialized (wire) order.
19 pub sizes: Vec<[i32; 17]>,
20}
21
22/// Read a [`TerrainGenerator`] from the bytes of its resource.
23pub fn terrain_generator(data: &[u8]) -> Result<TerrainGenerator> {
24 let (_, value) = crs::parse(data, |r: &mut Reader, h| {
25 if h.type_id != type_id("TerrainGenerator") {
26 return format_err(format!(
27 "expected TerrainGenerator, found {:#010x}",
28 h.type_id
29 ));
30 }
31 r.block(|r, _| {
32 let string_table = r.external_ref()?;
33 let name = r.string()?;
34 let flag = r.bool()?;
35 let materials = [r.string()?, r.string()?, r.string()?];
36 let mut sizes = Vec::new();
37 for _ in 0..r.count()? {
38 let mut rec = [0i32; 17];
39 for v in &mut rec {
40 *v = r.i32()?;
41 }
42 sizes.push(rec);
43 }
44 Ok(TerrainGenerator {
45 string_table,
46 name,
47 flag,
48 materials,
49 sizes,
50 })
51 })
52 })?;
53 Ok(value)
54}
55
56// +0x38 + 0x20·month. Interpolated by FUN_140098f90, applied by FUN_140098da0.
57/// One month of `EnvironmentDescription` lighting (32 bytes at `+ 0x20·month`).
58///
59/// The game interpolates between the current and the next month, then calls
60/// `SetFog(fog_start, fog_end, fog_color with alpha fog_max)` and
61/// `SunLight::Set(FromYawPitch(sun_yaw, sun_pitch), sun_color, ambient_color)`. Colours are
62/// `0xAARRGGBB`.
63#[derive(Debug, Clone, Copy, PartialEq)]
64pub struct EnvironmentMonth {
65 /// The ambient colour (`0xAARRGGBB`).
66 pub ambient_color: u32,
67 /// The sun's colour (`0xAARRGGBB`).
68 pub sun_color: u32,
69 /// The fog's colour (`0xAARRGGBB`).
70 pub fog_color: u32,
71 /// Where the fog starts.
72 pub fog_start: f32,
73 /// Where the fog ends.
74 pub fog_end: f32,
75 /// The fog's highest density.
76 pub fog_max: f32,
77 /// Degrees.
78 pub sun_yaw: f32,
79 /// Degrees above the horizon.
80 pub sun_pitch: f32,
81}
82
83/// `EnvironmentDescription`: weather and monthly lighting.
84///
85/// Original: `FUN_140099510`.
86#[derive(Debug, Clone)]
87pub struct EnvironmentDescription {
88 /// The template.
89 pub template: Option<String>,
90 /// Weather effects: (environment resource, ambient audio).
91 pub weather: Vec<(Option<String>, Option<String>)>,
92 /// The values of each month.
93 pub months: Vec<EnvironmentMonth>,
94}
95
96/// Read a [`EnvironmentDescription`] from the bytes of its resource.
97pub fn environment_description(data: &[u8]) -> Result<EnvironmentDescription> {
98 let (_, value) = crs::parse(data, |r: &mut Reader, h| {
99 if h.type_id != type_id("EnvironmentDescription") {
100 return format_err(format!(
101 "expected EnvironmentDescription, found {:#010x}",
102 h.type_id
103 ));
104 }
105 r.block(|r, _| {
106 let template = r.external_ref()?;
107 let mut weather = Vec::new();
108 for _ in 0..r.count()? {
109 weather.push((r.external_ref()?, r.external_ref()?));
110 }
111 let mut months = Vec::with_capacity(12);
112 for _ in 0..12 {
113 months.push(EnvironmentMonth {
114 ambient_color: r.u32()?,
115 sun_color: r.u32()?,
116 fog_color: r.u32()?,
117 fog_start: r.f32()?,
118 fog_end: r.f32()?,
119 fog_max: r.f32()?,
120 sun_yaw: r.f32()?,
121 sun_pitch: r.f32()?,
122 });
123 }
124 Ok(EnvironmentDescription {
125 template,
126 weather,
127 months,
128 })
129 })
130 })?;
131 Ok(value)
132}
133
134/// `ClimateGenerator`: monthly weather thresholds used by the environment loop.
135///
136/// Original: `FUN_140098470`.
137#[derive(Debug, Clone)]
138pub struct ClimateGenerator {
139 /// The string table.
140 pub string_table: Option<String>,
141 /// The name's key.
142 pub name: String,
143 /// Whether it is listed in the New Game dialog (*inferred*, as in [`ClimateHeader`]).
144 pub enabled: bool,
145 // +0x34..+0x40
146 /// Four climate settings (not identified yet).
147 pub settings: [f32; 4],
148 // +0x44
149 /// Four values for each month; the weather selector reads the first three as limits.
150 pub monthly_thresholds: [[f32; 4]; 12],
151 // +0x104..+0x10c
152 /// Three more climate settings (not identified yet).
153 pub trailing_settings: [f32; 3],
154}
155
156/// Read a [`ClimateGenerator`] from the bytes of its resource.
157pub fn climate_generator(data: &[u8]) -> Result<ClimateGenerator> {
158 let (_, value) = crs::parse(data, |r: &mut Reader, h| {
159 if h.type_id != type_id("ClimateGenerator") {
160 return format_err(format!(
161 "expected ClimateGenerator, found {:#010x}",
162 h.type_id
163 ));
164 }
165 r.block(|r, _| {
166 let string_table = r.external_ref()?;
167 let name = r.string()?;
168 let enabled = r.bool()?;
169 let mut settings = [0.0; 4];
170 for value in &mut settings {
171 *value = r.f32()?;
172 }
173 let mut monthly_thresholds = [[0.0; 4]; 12];
174 for month in &mut monthly_thresholds {
175 for value in month {
176 *value = r.f32()?;
177 }
178 }
179 let mut trailing_settings = [0.0; 3];
180 for value in &mut trailing_settings {
181 *value = r.f32()?;
182 }
183 Ok(ClimateGenerator {
184 string_table,
185 name,
186 enabled,
187 settings,
188 monthly_thresholds,
189 trailing_settings,
190 })
191 })
192 })?;
193 Ok(value)
194}
195
196// A FUN_14001c020 record.
197/// A building placed relative to the start point of a new town.
198///
199/// Original: `FUN_1400a7490`.
200#[derive(Debug, Clone, PartialEq, Eq)]
201pub struct PlacedStructure {
202 /// The template.
203 pub template: String,
204 /// Offset from the start point in cells.
205 pub x: i32,
206 /// The offset from the start point in cells, along y.
207 pub y: i32,
208 /// Not identified yet.
209 pub a: i32,
210 /// Not identified yet.
211 pub b: i32,
212 /// Rotation in degrees.
213 pub rotation: i32,
214}
215
216/// `MapGenerator`: a New Game starting-condition profile. Field names follow their confirmed
217/// consumers; offsets are the in-memory record's.
218///
219/// Original: `FUN_1400a7e50`.
220#[derive(Debug, Clone)]
221pub struct MapGenerator {
222 /// The string table (*inferred*, as in the other profiles).
223 pub list: Option<String>,
224 /// The name's key.
225 pub name: String,
226 /// The description's key.
227 pub description: String,
228 /// Whether it is listed in the New Game dialog (*inferred*, as in [`ClimateHeader`]).
229 pub flag: bool,
230 // +0x3c
231 /// Radius of the clear-ground start area.
232 ///
233 /// Original: `FUN_1400a7bf0`.
234 pub start_radius: i32,
235 /// Not identified yet.
236 pub value_40: f32,
237 /// The citizen template.
238 pub citizen: String,
239 // +0x60
240 /// The household groups: a flag, and the most extra members (`(flag, extra members up to)`).
241 pub households: Vec<(bool, i32)>,
242 // +0x70
243 /// Templates seeded over the map: rock, ore, tree.
244 ///
245 /// Original: `FUN_1400a6780`.
246 pub natural_resources: Vec<String>,
247 // +0x80
248 /// Not identified yet.
249 pub structures: Vec<PlacedStructure>,
250 // +0x90
251 /// Population managers (deer).
252 pub populations: Vec<String>,
253 // +0xa0
254 /// Starting raw materials.
255 pub raw_materials: Vec<String>,
256 // +0xb0
257 /// Crop templates; `crop_picks` are chosen at random.
258 pub crops: Vec<String>,
259 // +0xc0
260 /// Orchard tree templates; `tree_picks` are chosen at random.
261 pub orchard_trees: Vec<String>,
262 // +0xd0
263 /// Livestock templates; `livestock_picks` are chosen at random.
264 pub livestock: Vec<String>,
265 /// How many crops are chosen.
266 pub crop_picks: i32,
267 /// How many orchard trees are chosen.
268 pub tree_picks: i32,
269 /// How many livestock kinds are chosen.
270 pub livestock_picks: i32,
271 // +0xec..+0xf4
272 /// Not identified yet.
273 pub livestock_floats: [f32; 3],
274 // +0xf8; +0xf8..+0x114
275 /// `livestock_count` then seven more integers.
276 pub ints: [i32; 8],
277 /// Not identified yet.
278 pub string_118: String,
279 /// Not identified yet.
280 pub string_138: String,
281 /// Not identified yet.
282 pub float_130: f32,
283 /// Not identified yet.
284 pub int_150: i32,
285 /// Not identified yet.
286 pub string_158: String,
287 /// Not identified yet.
288 pub float_170: f32,
289}
290
291/// Read a [`MapGenerator`] from the bytes of its resource.
292pub fn map_generator(data: &[u8]) -> Result<MapGenerator> {
293 let (_, value) = crs::parse(data, |r: &mut Reader, h| {
294 if h.type_id != type_id("MapGenerator") {
295 return format_err(format!("expected MapGenerator, found {:#010x}", h.type_id));
296 }
297 r.block(|r, _| {
298 let strings = |r: &mut Reader| -> Result<Vec<String>> {
299 (0..r.count()?).map(|_| r.string()).collect()
300 };
301 let list = r.external_ref()?;
302 let name = r.string()?;
303 let description = r.string()?;
304 let flag = r.bool()?;
305 let start_radius = r.i32()?;
306 let value_40 = r.f32()?;
307 let citizen = r.string()?;
308 let households = (0..r.count()?)
309 .map(|_| Ok((r.bool()?, r.i32()?)))
310 .collect::<Result<_>>()?;
311 let natural_resources = strings(r)?;
312 let structures = (0..r.count()?)
313 .map(|_| {
314 Ok(PlacedStructure {
315 template: r.string()?,
316 x: r.i32()?,
317 y: r.i32()?,
318 a: r.i32()?,
319 b: r.i32()?,
320 rotation: r.i32()?,
321 })
322 })
323 .collect::<Result<_>>()?;
324 let populations = strings(r)?;
325 let raw_materials = strings(r)?;
326 let crops = strings(r)?;
327 let orchard_trees = strings(r)?;
328 let livestock = strings(r)?;
329 let (crop_picks, tree_picks, livestock_picks) = (r.i32()?, r.i32()?, r.i32()?);
330 let livestock_floats = [r.f32()?, r.f32()?, r.f32()?];
331 let mut ints = [0; 8];
332 for v in &mut ints {
333 *v = r.i32()?;
334 }
335 let string_118 = r.string()?;
336 let string_138 = r.string()?;
337 let float_130 = r.f32()?;
338 let int_150 = r.i32()?;
339 let string_158 = r.string()?;
340 let float_170 = r.f32()?;
341 Ok(MapGenerator {
342 list,
343 name,
344 description,
345 flag,
346 start_radius,
347 value_40,
348 citizen,
349 households,
350 natural_resources,
351 structures,
352 populations,
353 raw_materials,
354 crops,
355 orchard_trees,
356 livestock,
357 crop_picks,
358 tree_picks,
359 livestock_picks,
360 livestock_floats,
361 ints,
362 string_118,
363 string_138,
364 float_130,
365 int_150,
366 string_158,
367 float_170,
368 })
369 })
370 })?;
371 Ok(value)
372}
373
374/// An `ExternalList`: an ordered list of resource references, for example
375/// `Game/Terrain/TerrainList.rsc`, which fills the terrain selector of the New Game dialog.
376///
377/// Original: the New Game lists are loaded by `FUN_14009f280`.
378pub fn external_list(data: &[u8]) -> Result<Vec<String>> {
379 let (_, value) = crs::parse(data, |r: &mut Reader, h| {
380 if h.type_id != type_id("ExternalList") {
381 return format_err(format!("expected ExternalList, found {:#010x}", h.type_id));
382 }
383 r.block(|r, _| {
384 (0..r.count()?)
385 .map(|_| Ok(r.external_ref()?.unwrap_or_default()))
386 .collect()
387 })
388 })?;
389 Ok(value)
390}
391
392// +0x38
393/// The first fields of a `ClimateGenerator` (a climate profile of the New Game dialog): the string
394/// table, the name key and the "listed" flag. The climate values that follow are not decoded
395/// here.
396#[derive(Debug, Clone)]
397pub struct ClimateHeader {
398 /// The string table.
399 pub string_table: Option<String>,
400 /// The name's key.
401 pub name: String,
402 /// Whether it is listed in the New Game dialog.
403 pub listed: bool,
404}
405
406/// Read a [`ClimateHeader`] from the bytes of its resource.
407pub fn climate_header(data: &[u8]) -> Result<ClimateHeader> {
408 let (_, value) = crs::parse(data, |r: &mut Reader, h| {
409 if h.type_id != type_id("ClimateGenerator") {
410 return format_err(format!(
411 "expected ClimateGenerator, found {:#010x}",
412 h.type_id
413 ));
414 }
415 r.block(|r, _| {
416 let header = ClimateHeader {
417 string_table: r.external_ref()?,
418 name: r.string()?,
419 listed: r.bool()?,
420 };
421 r.skip_rest();
422 Ok(header)
423 })
424 })?;
425 Ok(value)
426}
427
428/// `MapDescription`: how an entity stamps the gameplay map.
429///
430/// Original: `FUN_140129a30`.
431#[derive(Debug, Clone)]
432pub struct MapDescription {
433 // +0x20
434 /// Cell type written under the footprint (4 for natural resources).
435 pub cell_type: u32,
436 // +0x28
437 /// Footprint rows (`#` own type, `_` ground, `.` untouched); empty = whole area.
438 pub footprint: String,
439 // +0x30
440 /// Stamp on activation.
441 pub stamp: bool,
442 // +0x31
443 /// Never stamp.
444 pub no_stamp: bool,
445 // +0x32
446 /// Draw on the minimap with `color`.
447 pub minimap: bool,
448 /// The minimap colour.
449 pub color: u32,
450}
451
452/// Read a [`MapDescription`] from the bytes of its resource.
453pub fn map_description(data: &[u8]) -> Result<MapDescription> {
454 let (_, value) = crs::parse(data, |r: &mut Reader, h| {
455 if h.type_id != type_id("MapDescription") {
456 return format_err(format!(
457 "expected MapDescription, found {:#010x}",
458 h.type_id
459 ));
460 }
461 r.block(|r, _| {
462 Ok(MapDescription {
463 cell_type: r.u32()?,
464 footprint: r.string()?,
465 stamp: r.bool()?,
466 no_stamp: r.bool()?,
467 minimap: r.bool()?,
468 color: r.u32()?,
469 })
470 })
471 })?;
472 Ok(value)
473}