Skip to main content

banished_formats/game/
people.rs

1//! Citizens and their lives: citizens, diseases, happiness, population, immigration, nomads, cemeteries and tombstones.
2
3use super::*;
4
5/// `Disease` (version 2, `Game/Disease/Diseases.rsc` lists them).
6///
7/// Original: `FUN_1400987a0`.
8#[derive(Debug, Clone, PartialEq)]
9pub struct Disease {
10    // +0x20; +0x28
11    /// The string table of the name.
12    pub table: Option<String>,
13    /// The name's key.
14    pub name: String,
15    // +0x30
16    /// Spread interval in 300 s units.
17    ///
18    /// Original: `FUN_140117ca0`.
19    pub spread_interval: f32,
20    // +0x34
21    /// Chance a spread attempt infects.
22    ///
23    /// Original: `FUN_140117940`.
24    pub spread_chance: f32,
25    // +0x38
26    /// Spread radius.
27    pub spread_radius: f32,
28    // +0x3c
29    /// Weight when an outbreak picks a disease.
30    ///
31    /// Original: `FUN_140117af0`.
32    pub weight: f32,
33    // +0x40
34    /// How fast a sick citizen recovers: a citizen recovers when a random float is greater than
35    /// `1 / recovery / doctor`.
36    ///
37    /// Original: used by `FUN_140117880`.
38    pub recovery: f32,
39}
40
41/// Read a [`Disease`] from the bytes of its resource.
42pub fn disease(data: &[u8]) -> Result<Disease> {
43    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
44        if h.type_id != type_id("Disease") {
45            return format_err(format!("expected Disease, found {:#010x}", h.type_id));
46        }
47        r.block(|r, _| {
48            Ok(Disease {
49                table: r.external_ref()?,
50                name: r.string()?,
51                spread_interval: r.f32()?,
52                spread_chance: r.f32()?,
53                spread_radius: r.f32()?,
54                weight: r.f32()?,
55                recovery: r.f32()?,
56            })
57        })
58    })?;
59    Ok(value)
60}
61
62// +0x20
63/// `AttendanceDescription`: the capacity (Chapel 200, DoctorHouse 30, SchoolHouse 20).
64///
65/// Original: `FUN_140045010`.
66pub fn attendance_description(data: &[u8]) -> Result<i32> {
67    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
68        if h.type_id != type_id("AttendanceDescription") {
69            return format_err(format!(
70                "expected AttendanceDescription, found {:#010x}",
71                h.type_id
72            ));
73        }
74        r.block(|r, _| r.i32())
75    })?;
76    Ok(value)
77}
78
79/// `HappinessDescription`.
80///
81/// Original: `FUN_14004b610`.
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
83pub struct HappinessDescription {
84    // +0x20
85    /// The kind, which picks the map layer (0 well, 1 market and trading post, 2 tavern, 3 chapel
86    /// and cemetery, 4 doctor, 5 the unhappy mines and quarry).
87    pub kind: i32,
88    // +0x24
89    /// Only while someone works there.
90    pub needs_workers: bool,
91    // +0x25
92    /// Only while the storage holds goods matching `mask`, one consumed per visit.
93    pub needs_stock: bool,
94    // +0x28
95    /// Raw-material flags.
96    pub mask: u32,
97    // +0x2c
98    /// Cells around the building a loitering citizen stands in.
99    pub pad: i32,
100}
101
102/// Read a [`HappinessDescription`] from the bytes of its resource.
103pub fn happiness_description(data: &[u8]) -> Result<HappinessDescription> {
104    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
105        if h.type_id != type_id("HappinessDescription") {
106            return format_err(format!(
107                "expected HappinessDescription, found {:#010x}",
108                h.type_id
109            ));
110        }
111        r.block(|r, _| {
112            Ok(HappinessDescription {
113                kind: r.i32()?,
114                needs_workers: r.bool()?,
115                needs_stock: r.bool()?,
116                mask: r.u32()?,
117                pad: r.i32()?,
118            })
119        })
120    })?;
121    Ok(value)
122}
123
124/// `PopulationDescription`: the wildlife manager's grid and the herds it starts.
125///
126/// Original: `FUN_140055140`.
127#[derive(Debug, Clone, PartialEq, Eq)]
128pub struct PopulationDescription {
129    // +0x20
130    /// The display name.
131    pub name: String,
132    // +0x28
133    /// Map cells per population cell (a side).
134    pub cell: i32,
135    // +0x2c
136    /// Map cells per herd block (a side).
137    pub herd_block: i32,
138    // +0x30
139    /// Cell types that count as habitat.
140    pub habitat: u32,
141    // +0x34
142    /// Cell types that rule a population cell out.
143    pub forbidden: u32,
144    // +0x38
145    /// Map occupants rule a population cell out.
146    pub no_occupants: bool,
147    // +0x3c
148    /// A habitable cell's capacity.
149    pub capacity: i32,
150    // +0x40
151    /// Regrowth per 300 s.
152    pub regrowth: i32,
153    // +0x44
154    /// Not identified yet.
155    pub value_44: i32,
156    // +0x48
157    /// Recount a sixteenth of the cells every update.
158    pub refresh: bool,
159    // +0x50
160    /// The herd entity template.
161    pub herd: Option<String>,
162}
163
164/// Read a [`PopulationDescription`] from the bytes of its resource.
165pub fn population_description(data: &[u8]) -> Result<PopulationDescription> {
166    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
167        if h.type_id != type_id("PopulationDescription") {
168            return format_err(format!(
169                "expected PopulationDescription, found {:#010x}",
170                h.type_id
171            ));
172        }
173        r.block(|r, _| {
174            let cell = r.i32()?;
175            let herd_block = r.i32()?;
176            let habitat = r.u32()?;
177            let forbidden = r.u32()?;
178            let no_occupants = r.bool()?;
179            let capacity = r.i32()?;
180            let regrowth = r.i32()?;
181            let value_44 = r.i32()?;
182            let refresh = r.bool()?;
183            let herd = r.external_ref()?;
184            let name = r.string()?;
185            Ok(PopulationDescription {
186                name,
187                cell,
188                herd_block,
189                habitat,
190                forbidden,
191                no_occupants,
192                capacity,
193                regrowth,
194                value_44,
195                refresh,
196                herd,
197            })
198        })
199    })?;
200    Ok(value)
201}
202
203/// A `DiseaseDescription`: blight on fields, orchards and pastures. Times are in months of 300
204/// seconds.
205///
206/// Original: `FUN_1400497a0`.
207#[derive(Debug, Clone, Copy, Default, PartialEq)]
208pub struct DiseaseDescription {
209    // +0x20..=+0x24
210    /// The months in which it can start, both included.
211    pub months: (i32, i32),
212    // +0x28
213    /// The odds of a start: `1 / (12 / check × odds)` at each check.
214    pub odds: f32,
215    // +0x2c
216    /// The odds instead of `odds` when the cell grew the same crop last time.
217    pub odds_same: f32,
218    // +0x30
219    /// The time between checks.
220    pub check: f32,
221    // +0x34
222    /// The time between kills of a crop, a tree or an animal.
223    pub kill: f32,
224    // +0x38
225    /// The time between spreads.
226    pub spread: f32,
227    // +0x3c
228    /// How long the blight lasts.
229    pub duration: f32,
230    // +0x40
231    /// How far it spreads, in cells.
232    pub radius: i32,
233}
234
235/// Read a [`DiseaseDescription`] from the bytes of its resource.
236pub fn disease_description(data: &[u8]) -> Result<DiseaseDescription> {
237    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
238        if h.type_id != type_id("DiseaseDescription") {
239            return format_err(format!(
240                "expected DiseaseDescription, found {:#010x}",
241                h.type_id
242            ));
243        }
244        r.block(|r, _| {
245            Ok(DiseaseDescription {
246                months: (r.i32()?, r.i32()?),
247                odds: r.f32()?,
248                odds_same: r.f32()?,
249                check: r.f32()?,
250                kill: r.f32()?,
251                spread: r.f32()?,
252                duration: r.f32()?,
253                radius: r.i32()?,
254            })
255        })
256    })?;
257    Ok(value)
258}
259
260// +0x20
261/// `CemeteryDescription`: the tombstone template.
262///
263/// Original: `FUN_140048160`.
264pub fn cemetery_description(data: &[u8]) -> Result<Option<String>> {
265    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
266        if h.type_id != type_id("CemeteryDescription") {
267            return format_err(format!(
268                "expected CemeteryDescription, found {:#010x}",
269                h.type_id
270            ));
271        }
272        r.block(|r, _| r.external_ref())
273    })?;
274    Ok(value)
275}
276
277/// An `ImmigrationDescription`: when nomads come to the town. Two values are not serialised:
278/// the month in which nomads leave (7) and the margin from the map's edge (10).
279///
280/// Original: `FUN_14004f150`, `FUN_14004f300`.
281#[derive(Debug, Clone, Default, PartialEq)]
282pub struct ImmigrationDescription {
283    // +0x20
284    /// The nomad template.
285    pub nomad: Option<String>,
286    // +0x28
287    /// The population the town needs.
288    pub population: i32,
289    // +0x30
290    /// The templates the town must have (for example a town hall).
291    pub required: Vec<Option<String>>,
292    // +0x40/+0x44
293    /// The band's size as a share of the population.
294    pub share: (f32, f32),
295    // +0x48/+0x4c
296    /// The months between bands: base and random range.
297    pub wait: (f32, f32),
298    // +0x50/+0x54
299    /// The months in which a band may come.
300    pub months: (i32, i32),
301    // +0x60
302    /// The risk that visitors bring disease.
303    pub risk: f32,
304    // +0x64
305    /// The months of that risk.
306    pub risk_months: i32,
307}
308
309/// Read a [`ImmigrationDescription`] from the bytes of its resource.
310pub fn immigration_description(data: &[u8]) -> Result<ImmigrationDescription> {
311    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
312        if h.type_id != type_id("ImmigrationDescription") {
313            return format_err(format!(
314                "expected ImmigrationDescription, found {:#010x}",
315                h.type_id
316            ));
317        }
318        r.block(|r, _| {
319            let nomad = r.external_ref()?;
320            let population = r.i32()?;
321            let required = (0..r.count()?)
322                .map(|_| r.external_ref())
323                .collect::<Result<_>>()?;
324            let share = (r.f32()?, r.f32()?);
325            let wait = (r.f32()?, r.f32()?);
326            let months = (r.i32()?, r.i32()?);
327            let (risk, risk_months) = (r.f32()?, r.i32()?);
328            Ok(ImmigrationDescription {
329                nomad,
330                population,
331                required,
332                share,
333                wait,
334                months,
335                risk,
336                risk_months,
337            })
338        })
339    })?;
340    Ok(value)
341}
342
343// +0x20; +0x28; +0x30. FUN_140050990 puts the material in the model's material 0.
344/// A `NomadDescription`: the citizen template nomads become, and the men's and women's
345/// material instance (put in their model's material 0).
346///
347/// Original: `FUN_140050880`.
348pub fn nomad_description(data: &[u8]) -> Result<(Option<String>, [Option<String>; 2])> {
349    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
350        if h.type_id != type_id("NomadDescription") {
351            return format_err(format!(
352                "expected NomadDescription, found {:#010x}",
353                h.type_id
354            ));
355        }
356        r.block(|r, _| {
357            let citizen = r.external_ref()?;
358            let men = r.external_ref()?;
359            let women = r.external_ref()?;
360            Ok((citizen, [men, women]))
361        })
362    })?;
363    Ok(value)
364}
365
366// (+0x20 + RandFloat × +0x24)
367/// `TombstoneDescription`: a grave lasts `(+ RandFloat ×)` × 300 s.
368///
369/// Original: `FUN_140058380`, `FUN_1400581c0`.
370pub fn tombstone_description(data: &[u8]) -> Result<(f32, f32)> {
371    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
372        if h.type_id != type_id("TombstoneDescription") {
373            return format_err(format!(
374                "expected TombstoneDescription, found {:#010x}",
375                h.type_id
376            ));
377        }
378        r.block(|r, _| Ok((r.f32()?, r.f32()?)))
379    })?;
380    Ok(value)
381}
382
383/// `CitizenDescription`. The class has ~60 scalar fields; they are kept keyed by their runtime
384/// offset (as the original code reads them) with named getters for the ones whose consumers are
385/// traced.
386///
387/// Original: `FUN_140119dd0`.
388#[derive(Debug, Clone)]
389pub struct CitizenDescription {
390    /// Scalar fields by runtime offset.
391    pub fields: std::collections::BTreeMap<u32, Scalar>,
392    // +0x138
393    /// External references.
394    ///
395    /// Original: `FUN_14001c620`.
396    pub list: Vec<Option<String>>,
397    // +0x20; +0x28; +0x30
398    /// Not identified yet.
399    pub refs: [Option<String>; 3],
400}
401
402/// A value of the citizen description: a float or an integer.
403#[derive(Debug, Clone, Copy, PartialEq)]
404pub enum Scalar {
405    /// A float.
406    F32(f32),
407    /// An integer.
408    I32(i32),
409}
410
411/// Read a [`CitizenDescription`] from the bytes of its resource.
412pub fn citizen_description(data: &[u8]) -> Result<CitizenDescription> {
413    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
414        if h.type_id != type_id("CitizenDescription") {
415            return format_err(format!(
416                "expected CitizenDescription, found {:#010x}",
417                h.type_id
418            ));
419        }
420        r.block(|r, _| {
421            use Scalar::*;
422            let mut fields = std::collections::BTreeMap::new();
423            let mut f = |r: &mut Reader, o: u32| -> Result<()> {
424                fields.insert(o, F32(r.f32()?));
425                Ok(())
426            };
427            for o in [
428                0x44, 0x6c, 0x70, 0x74, 0x78, 0x7c, 0x80, 0x84, 0x88, 0x8c, 0x90, 0x38, 0x3c, 0x40,
429                0xc0, 0xd8, 0xdc, 0xe0, 0xe4, 0x48, 0x4c, 0x50, 0x54, 0x58, 0x5c, 0x60, 0xc4, 0xd4,
430                0xc8, 0xcc, 0xd0, 0xe8, 0xec, 0xf0, 0xf4,
431            ] {
432                f(r, o)?;
433            }
434            fields.insert(0x110, I32(r.i32()?));
435            let list = (0..r.count()?)
436                .map(|_| r.external_ref())
437                .collect::<Result<_>>()?;
438            fields.insert(0x114, F32(r.f32()?));
439            fields.insert(0x118, I32(r.i32()?));
440            for o in [0x120, 0x11c, 0x128, 0x124, 0x130, 0xa4] {
441                fields.insert(o, F32(r.f32()?));
442            }
443            fields.insert(0xa8, I32(r.i32()?));
444            fields.insert(0xac, I32(r.i32()?));
445            fields.insert(0xa0, F32(r.f32()?));
446            fields.insert(0xb0, I32(r.i32()?));
447            fields.insert(0xb4, F32(r.f32()?));
448            fields.insert(0xb8, F32(r.f32()?));
449            fields.insert(0xbc, I32(r.i32()?));
450            let refs = [r.external_ref()?, r.external_ref()?, r.external_ref()?];
451            fields.insert(0x64, F32(r.f32()?));
452            fields.insert(0x68, F32(r.f32()?));
453            for o in [0xf8, 0xfc, 0x100, 0x104, 0x108] {
454                fields.insert(o, I32(r.i32()?));
455            }
456            fields.insert(0x10c, F32(r.f32()?));
457            Ok(CitizenDescription { fields, list, refs })
458        })
459    })?;
460    Ok(value)
461}
462
463impl CitizenDescription {
464    /// The float at an offset of the description (0 when it is not a float).
465    pub fn f(&self, offset: u32) -> f32 {
466        match self.fields.get(&offset) {
467            Some(Scalar::F32(v)) => *v,
468            Some(Scalar::I32(v)) => *v as f32,
469            None => 0.0,
470        }
471    }
472    /// The integer at an offset of the description (0 when it is not an integer).
473    pub fn i(&self, offset: u32) -> i32 {
474        match self.fields.get(&offset) {
475            Some(Scalar::I32(v)) => *v,
476            Some(Scalar::F32(v)) => *v as i32,
477            None => 0,
478        }
479    }
480    // +0x44
481    /// Age at which a citizen counts as an adult.
482    pub fn adult_age(&self) -> f32 {
483        self.f(0x44)
484    }
485    // +0x50, +0x54; +0x54 / 2
486    /// The age range of the starting adults. The game uses half of the second value.
487    ///
488    /// Original: used by `FUN_1400a62a0`.
489    pub fn start_age(&self) -> (f32, f32) {
490        (self.f(0x50), self.f(0x54))
491    }
492    // +0x38, +0x3c
493    /// Lifespan base and random range.
494    ///
495    /// Original: `FUN_14011c550`.
496    pub fn lifespan(&self) -> (f32, f32) {
497        (self.f(0x38), self.f(0x3c))
498    }
499}