Skip to main content

banished_game/people/
citizens.rs

1//! Starting households: every profile row is a couple plus up to `extra` children, created on the
2//! game's shared random stream. See docs/CITIZEN-NEEDS-AND-HOUSEHOLDS.md and
3//! START-CONDITION-PROFILES.md.
4//!
5//! Original: `FUN_1400a62a0`, `FUN_1400a6510`, `FUN_14011c550`.
6
7use banished_formats::names::NameDatabase;
8
9use crate::people::names::citizen_name;
10use crate::random::Random;
11
12/// The `CitizenDescription` values household creation reads.
13#[derive(Debug, Clone, Copy)]
14pub struct CitizenRules {
15    // +0x44
16    /// Not identified yet.
17    pub adult_age: f32,
18    // +0x50, +0x54
19    /// Starting adults' ages are `[min, max / 2)`.
20    pub start_age: (f32, f32),
21    // +0x38, +0x3c
22    /// Lifespan base and range.
23    pub lifespan: (f32, f32),
24    // +0x118
25    /// Education granted to educated starting adults.
26    pub education: i32,
27}
28
29/// A citizen: name, sex, age, education, appearance and family.
30#[derive(Debug, Clone, PartialEq)]
31pub struct Citizen {
32    // +0x28
33    /// Not identified yet.
34    pub name: String,
35    // +0x38
36    /// 1 = male.
37    pub male: bool,
38    // +0x3c
39    /// The citizen's age, in the description's age units.
40    pub age: f32,
41    // +0x40
42    /// Not identified yet.
43    pub lifespan: f32,
44    // +0x34
45    /// Appearance variation `(Rand + Rand) & 7`, passed to the shader as instance `w`.
46    pub appearance: u32,
47    // +0x220; +0x210
48    /// Whether the citizen is educated.
49    pub educated: bool,
50    /// The education.
51    pub education: i32,
52    /// Map position (cells from the terrain centre) and height.
53    pub position: [f32; 3],
54    // +0x250
55    /// Spouse (index into the citizen list).
56    pub spouse: Option<usize>,
57    /// Household parent the child was added to.
58    ///
59    /// Original: `FUN_14011bd70`.
60    pub parent: Option<usize>,
61    // +0x208
62    /// School (none at New Game).
63    pub school: Option<usize>,
64    /// False once the entity is deleted (the port keeps its index).
65    pub alive: bool,
66}
67
68impl Citizen {
69    /// Whether the citizen is an adult.
70    pub fn is_adult(&self, rules: &CitizenRules) -> bool {
71        self.age >= rules.adult_age
72    }
73}
74
75/// The starting households of a new town, as the start condition makes them.
76pub struct Households<'a> {
77    /// The citizen rules.
78    pub rules: CitizenRules,
79    /// The men's names.
80    pub male_names: &'a NameDatabase,
81    /// The women's names.
82    pub female_names: &'a NameDatabase,
83    // MapGenerator +0x40
84    /// `MapGenerator`: spread of citizens around the start point.
85    pub spread: f32,
86    /// Start cell centre on the ground.
87    ///
88    /// Original: `FUN_140002080`.
89    pub start: [f32; 3],
90    /// The citizens made.
91    pub citizens: Vec<Citizen>,
92}
93
94/// `DAT_140178814`.
95const HALF: f32 = 0.5;
96
97impl Households<'_> {
98    /// `FUN_1400a6510` + `FUN_14011c550`: create and register one citizen.
99    fn create(&mut self, rng: &mut Random, age: f32, female: bool, educated: bool) -> usize {
100        // Entity construction: no component setup draws for the citizen template (its model has
101        // a fixed variant).
102        let child = age < self.rules.adult_age;
103        let educated = educated && !child;
104        let dy = rng.rand_float_tol();
105        let dx = rng.rand_float_tol();
106        let position = [
107            dx * self.spread + self.start[0],
108            dy * self.spread + self.start[1],
109            self.start[2] + 0.0,
110        ];
111        let male = !female;
112        let db = if male {
113            self.male_names
114        } else {
115            self.female_names
116        };
117        let name = citizen_name(db, rng);
118        let a = rng.rand();
119        let b = rng.rand();
120        let appearance = a.wrapping_add(b) & 7;
121        let lifespan = rng.rand_float_tol() * self.rules.lifespan.1 + self.rules.lifespan.0;
122        self.citizens.push(Citizen {
123            name,
124            male,
125            age,
126            lifespan,
127            appearance,
128            educated,
129            education: if educated { self.rules.education } else { 0 },
130            position,
131            spouse: None,
132            parent: None,
133            school: None,
134            alive: true,
135        });
136        self.citizens.len() - 1
137    }
138
139    /// One couple per row, then `Rand(extra + 1)` children whose sex alternates across all
140    /// households, starting female.
141    ///
142    /// Original: `FUN_1400a62a0`.
143    pub fn create_all(&mut self, rng: &mut Random, rows: &[(bool, i32)]) {
144        let (lo, hi) = self.rules.start_age;
145        let mut female_child = true;
146        for &(educated, extra) in rows {
147            let age = rng.rand_float() * (hi * HALF - lo) + lo;
148            let husband = self.create(rng, age, false, educated);
149            let age = rng.rand_float() * (hi * HALF - lo) + lo;
150            let wife = self.create(rng, age, true, educated);
151            self.citizens[husband].spouse = Some(wife);
152            self.citizens[wife].spouse = Some(husband);
153            let children = rng.rand_below((extra + 1) as u32);
154            for _ in 0..children {
155                let age = rng.rand_float() * self.rules.adult_age;
156                let child = self.create(rng, age, female_child, false);
157                female_child = !female_child;
158                self.citizens[child].parent = Some(wife);
159            }
160        }
161    }
162}
163
164/// A child born to `mother`: its sex, a name from that sex's database, the
165/// appearance, no education and a lifespan. Position, age and family are set by the caller.
166///
167/// Original: `FUN_14011c550`, called from `FUN_140117060`.
168pub fn newborn(
169    rng: &mut Random,
170    rules: &CitizenRules,
171    male_names: &NameDatabase,
172    female_names: &NameDatabase,
173    mother: &Citizen,
174    male: bool,
175) -> Citizen {
176    let name = citizen_name(if male { male_names } else { female_names }, rng);
177    let a = rng.rand();
178    let b = rng.rand();
179    let appearance = a.wrapping_add(b) & 7;
180    let lifespan = rng.rand_float_tol() * rules.lifespan.1 + rules.lifespan.0;
181    Citizen {
182        name,
183        male,
184        age: 0.0,
185        lifespan,
186        appearance,
187        educated: false,
188        education: 0,
189        position: mother.position,
190        spouse: None,
191        parent: None,
192        school: None,
193        alive: true,
194    }
195}
196
197/// Status Bar population: adults, students, children.
198///
199/// Original: `FUN_14007eda0`.
200pub fn population(citizens: &[Citizen], rules: &CitizenRules) -> (u32, u32, u32) {
201    let (mut adults, mut students, mut children) = (0, 0, 0);
202    for c in citizens.iter().filter(|c| c.alive) {
203        if c.age < rules.adult_age || c.school.is_some() {
204            if c.school.is_none() {
205                children += 1;
206            } else {
207                students += 1;
208            }
209        } else {
210            adults += 1;
211        }
212    }
213    (adults, students, children)
214}