Skip to main content

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}