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}