Skip to main content

banished_formats/game/
nature.rs

1//! Natural resources, growth, spawning, herds, animals and fruit.
2
3use super::*;
4
5/// `NaturalResourceDescription`. Field offsets are in the runtime object; see
6/// docs/RESOURCE-SEEDING.md and GAME-LOOP-STEP-14.md.
7///
8/// Original: `FUN_14012b2e0`.
9#[derive(Debug, Clone)]
10pub struct NaturalResourceDescription {
11    // +0x20
12    /// The raw material it yields.
13    pub raw_material: Option<String>,
14    // +0x28, +0x2c
15    /// The placement noise's persistence.
16    pub noise_persistence: f32,
17    /// The placement noise's octaves.
18    pub noise_octaves: f32,
19    // +0x30
20    /// Placement noise scale (sampled at `cell / scale`).
21    pub noise_scale: f32,
22    // +0x34
23    /// Minimum `noise · 0.5 + 0.5`.
24    pub noise_min: f32,
25    // +0x38
26    /// `RandFloat` must be at most this.
27    pub chance: f32,
28    // +0x48
29    /// Choose the model variant by noise clusters.
30    pub cluster: bool,
31    // +0x4c, +0x50
32    /// The cluster noise's persistence.
33    pub cluster_persistence: f32,
34    /// The cluster noise's octaves.
35    pub cluster_octaves: f32,
36    // +0x54, +0x58
37    /// The cluster noise's scale.
38    pub cluster_scale: f32,
39    /// The cluster noise's threshold.
40    pub cluster_threshold: f32,
41    // +0x5c
42    /// 1-in-N chance of a random variant instead.
43    pub random_variant_one_in: i32,
44    // +0x3c, +0x40
45    /// The lowest ground height.
46    pub height_min: f32,
47    /// The highest ground height.
48    pub height_max: f32,
49    // +0x44
50    /// Minimum ground normal z.
51    pub normal_min: f32,
52    // +0x64
53    /// Transition flag (GAME-LOOP-STEP-14).
54    pub flag: bool,
55    // +0x68, +0x70
56    /// A time in microseconds; not identified yet.
57    pub time_a: i64,
58    /// A time in microseconds; not identified yet.
59    pub time_b: i64,
60    // +0x80
61    /// Not identified yet.
62    pub value: u32,
63    // +0x78
64    /// Not identified yet.
65    pub related: Option<String>,
66    // +0x60
67    /// Used by the positioning component.
68    pub position_scale: f32,
69}
70
71/// Read a [`NaturalResourceDescription`] from the bytes of its resource.
72pub fn natural_resource_description(data: &[u8]) -> Result<NaturalResourceDescription> {
73    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
74        if h.type_id != type_id("NaturalResourceDescription") {
75            return format_err(format!(
76                "expected NaturalResourceDescription, found {:#010x}",
77                h.type_id
78            ));
79        }
80        r.block(|r, _| {
81            Ok(NaturalResourceDescription {
82                raw_material: r.external_ref()?,
83                noise_persistence: r.f32()?,
84                noise_octaves: r.f32()?,
85                noise_scale: r.f32()?,
86                noise_min: r.f32()?,
87                chance: r.f32()?,
88                cluster: r.bool()?,
89                cluster_persistence: r.f32()?,
90                cluster_octaves: r.f32()?,
91                cluster_scale: r.f32()?,
92                cluster_threshold: r.f32()?,
93                random_variant_one_in: r.i32()?,
94                height_min: r.f32()?,
95                height_max: r.f32()?,
96                normal_min: r.f32()?,
97                flag: r.bool()?,
98                time_a: r.i64()?,
99                time_b: r.i64()?,
100                value: r.u32()?,
101                related: r.external_ref()?,
102                position_scale: r.f32()?,
103            })
104        })
105    })?;
106    Ok(value)
107}
108
109/// `GrowthDescription`. See GAME-LOOP-STEP-11/12 and RESOURCE-SEEDING.md for the consumers.
110///
111/// Original: `FUN_140125e50`.
112#[derive(Debug, Clone)]
113pub struct GrowthDescription {
114    // +0x20 / +0x24
115    /// Maximum growth `= RandFloatTol · range + base` when base > 0; otherwise −1. Also the spawn
116    /// step.
117    ///
118    /// Original: `FUN_140125bc0`, `FUN_140056620`.
119    pub max_base: f32,
120    /// The random range of the maximum growth.
121    pub max_range: f32,
122    /// The growing time, in months of 300 seconds.
123    pub f28: f32,
124    /// Whether the temperature changes the growth rate.
125    pub b2c: bool,
126    /// The best temperature.
127    pub f30: f32,
128    /// The range of temperatures around the best one.
129    pub f34: f32,
130    /// Not identified yet.
131    pub b38: bool,
132    /// The map-byte factor when a resource of the same template is removed.
133    pub f3c: f32,
134    /// The map-byte factor otherwise, and when growth ends.
135    pub f40: f32,
136    /// Whether it spawns children from time to time.
137    pub b44: bool,
138    // +0x4c
139    /// Spawn radius in cells (`RandTol`).
140    pub spawn_radius: i32,
141    // +0x50
142    /// Growth timer scale (`RandFloat · value · 300` seconds).
143    pub timer_months: i32,
144    // +0x48
145    /// Spawn chance is `1 / value`.
146    pub spawn_one_in: i32,
147    // +0x54
148    /// Not identified yet.
149    pub threshold: f32,
150    // +0x58
151    /// Maximum occupied neighbours.
152    ///
153    /// Original: `FUN_140126100`.
154    pub max_neighbours: i32,
155    // +0x5c
156    /// Maximum neighbours of the same growth type.
157    pub max_same: i32,
158    /// The smallest visible scale.
159    pub f60: f32,
160    // +0x64
161    /// Scale shape: false = uniform growth, true = `(g/0.15, g/0.15, g, 1)` clamped.
162    pub tall: bool,
163}
164
165/// Read a [`GrowthDescription`] from the bytes of its resource.
166pub fn growth_description(data: &[u8]) -> Result<GrowthDescription> {
167    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
168        if h.type_id != type_id("GrowthDescription") {
169            return format_err(format!(
170                "expected GrowthDescription, found {:#010x}",
171                h.type_id
172            ));
173        }
174        r.block(|r, _| {
175            Ok(GrowthDescription {
176                max_base: r.f32()?,
177                max_range: r.f32()?,
178                f28: r.f32()?,
179                b2c: r.bool()?,
180                f30: r.f32()?,
181                f34: r.f32()?,
182                b38: r.bool()?,
183                f3c: r.f32()?,
184                f40: r.f32()?,
185                b44: r.bool()?,
186                spawn_radius: r.i32()?,
187                timer_months: r.i32()?,
188                spawn_one_in: r.i32()?,
189                threshold: r.f32()?,
190                max_neighbours: r.i32()?,
191                max_same: r.i32()?,
192                f60: r.f32()?,
193                tall: r.bool()?,
194            })
195        })
196    })?;
197    Ok(value)
198}
199
200/// One `SpawnDescription` entry: a template spawned while the parent's growth is in `[lo, hi]`.
201#[derive(Debug, Clone)]
202pub struct SpawnEntry {
203    /// The template spawned.
204    pub template: Option<String>,
205    /// The lowest growth of the parent.
206    pub lo: f32,
207    /// The highest growth of the parent.
208    pub hi: f32,
209}
210
211/// `SpawnDescription`.
212///
213/// Original: `FUN_140056a10`.
214pub fn spawn_description(data: &[u8]) -> Result<Vec<SpawnEntry>> {
215    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
216        if h.type_id != type_id("SpawnDescription") {
217            return format_err(format!(
218                "expected SpawnDescription, found {:#010x}",
219                h.type_id
220            ));
221        }
222        r.block(|r, _| {
223            (0..r.count()?)
224                .map(|_| {
225                    Ok(SpawnEntry {
226                        template: r.external_ref()?,
227                        lo: r.f32()?,
228                        hi: r.f32()?,
229                    })
230                })
231                .collect()
232        })
233    })?;
234    Ok(value)
235}
236
237/// `HerdDescription`.
238///
239/// Original: `FUN_1401276b0`.
240#[derive(Debug, Clone, PartialEq)]
241pub struct HerdDescription {
242    // +0x20
243    /// The animal template.
244    pub animal: Option<String>,
245    // +0x28, +0x2c
246    /// The most animals.
247    pub max: i32,
248    /// The fewest animals.
249    pub min: i32,
250    // +0x30, +0x34
251    /// The rest between moves (× 300 s): the base.
252    pub rest: f32,
253    /// The random part of the rest.
254    pub rest_range: f32,
255    // +0x38
256    /// The side of the herd's area in cells.
257    pub area: i32,
258    // +0x3c
259    /// The regrowth interval (× 300 s).
260    pub regrowth: f32,
261}
262
263/// Read a [`HerdDescription`] from the bytes of its resource.
264pub fn herd_description(data: &[u8]) -> Result<HerdDescription> {
265    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
266        if h.type_id != type_id("HerdDescription") {
267            return format_err(format!(
268                "expected HerdDescription, found {:#010x}",
269                h.type_id
270            ));
271        }
272        r.block(|r, _| {
273            Ok(HerdDescription {
274                animal: r.external_ref()?,
275                max: r.i32()?,
276                min: r.i32()?,
277                rest: r.f32()?,
278                rest_range: r.f32()?,
279                area: r.i32()?,
280                regrowth: r.f32()?,
281            })
282        })
283    })?;
284    Ok(value)
285}
286
287// +0x40
288/// `AnimalDescription` (the idle time, is not stored and keeps the constructor's 4).
289///
290/// Original: `FUN_14010c520`.
291#[derive(Debug, Clone, PartialEq)]
292pub struct AnimalDescription {
293    // +0x48
294    /// The raw materials a hunted animal leaves.
295    pub materials: Vec<Option<String>>,
296    // +0x20
297    /// The side of the animal's home area in cells.
298    pub area: i32,
299    // +0x24
300    /// Map cells a pasture gives each animal.
301    ///
302    /// Original: `FUN_14012da40`.
303    pub space: i32,
304    // +0x28, +0x2c
305    /// The lifespan in years: the base.
306    pub life: f32,
307    /// The tolerance of the lifespan.
308    pub life_range: f32,
309    // +0x30
310    /// The age at full size (years).
311    pub adult: f32,
312    // +0x34; +0x38
313    /// A breeding male's years between young (and as much again at random).
314    ///
315    /// Original: `FUN_14010b8f0`.
316    pub breed: f32,
317    /// Not identified yet.
318    pub value_38: f32,
319    // +0x3c
320    /// Turn to a random heading when idling.
321    pub turn: bool,
322    // +0x40
323    /// The idle time in seconds (and as much again at random).
324    pub idle: f32,
325}
326
327/// Read a [`AnimalDescription`] from the bytes of its resource.
328pub fn animal_description(data: &[u8]) -> Result<AnimalDescription> {
329    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
330        if h.type_id != type_id("AnimalDescription") {
331            return format_err(format!(
332                "expected AnimalDescription, found {:#010x}",
333                h.type_id
334            ));
335        }
336        r.block(|r, _| {
337            let n = r.i32()?;
338            let materials = (0..n.max(0))
339                .map(|_| r.external_ref())
340                .collect::<Result<Vec<_>>>()?;
341            Ok(AnimalDescription {
342                materials,
343                area: r.i32()?,
344                space: r.i32()?,
345                life: r.f32()?,
346                life_range: r.f32()?,
347                adult: r.f32()?,
348                breed: r.f32()?,
349                value_38: r.f32()?,
350                turn: r.bool()?,
351                idle: 4.0,
352            })
353        })
354    })?;
355    Ok(value)
356}
357
358// +0x20; +0x24; +0x28; +0x30
359/// `FruitDescription`: ripening time (× 300 s), the best temperature, its range, the fruit's raw
360/// material.
361///
362/// Original: `FUN_14004ab40`.
363pub fn fruit_description(data: &[u8]) -> Result<(f32, f32, f32, Option<String>)> {
364    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
365        if h.type_id != type_id("FruitDescription") {
366            return format_err(format!(
367                "expected FruitDescription, found {:#010x}",
368                h.type_id
369            ));
370        }
371        r.block(|r, _| Ok((r.f32()?, r.f32()?, r.f32()?, r.external_ref()?)))
372    })?;
373    Ok(value)
374}