Skip to main content

banished_game/people/
happiness.rs

1//! Happiness buildings: wells, markets, the tavern,
2//! chapel, cemetery, doctor and trading post make the homes around them happier; mines and the
3//! quarry make them less happy. See docs/CITIZEN-LIFE.md "Happiness buildings".
4//!
5//! While active, a building is registered with the town manager and counts on its kind's
6//! map layer within its radius. A citizen's happiness reads the layers at its home's entrance;
7//! loitering citizens pick a registered building nearby.
8//!
9//! Original: `HappinessComponent` (controller interface vtable `0x14015f6a0`), `FUN_14004b570`.
10// Game +0xf0 → +0x50
11
12use crate::town::Sim;
13
14impl Sim<'_, '_> {
15    /// `FUN_14004aeb0`: the unhappy kind always counts; one that needs workers counts while
16    /// its workplace is staffed and switched on; one that needs stock while its storage holds
17    /// a matching raw material.
18    pub(crate) fn happiness_active(&self, e: usize) -> bool {
19        let Some(site) = self.sites.get(&e) else {
20            return false;
21        };
22        let Some(d) = self.site_templates[site.template].happiness else {
23            return false;
24        };
25        if d.kind == 5 {
26            return true;
27        }
28        if d.needs_workers {
29            if let Some(w) = self.workforce.workplaces.get(&e) {
30                if w.workers.is_empty() || !w.enabled {
31                    return false;
32                }
33            }
34        }
35        if !d.needs_stock {
36            return true;
37        }
38        self.storages[site.storage]
39            .items
40            .iter()
41            .any(|i| d.mask & self.materials[i.material].flags != 0)
42    }
43
44    /// `FUN_14004ac60`: add `delta` to the kind's layer (kinds above 4 count as 5) within the
45    /// radius around the map rectangle's centre.
46    fn happiness_layer(&mut self, e: usize, delta: i32) {
47        let Some(site) = self.sites.get(&e) else {
48            return;
49        };
50        let t = &self.site_templates[site.template];
51        let (Some(d), radius) = (t.happiness, t.radius.unwrap_or(0)) else {
52            return;
53        };
54        let layer = if (0..=4).contains(&d.kind) {
55            d.kind as usize
56        } else {
57            5
58        };
59        let (x, y) = self.placed[e].cell;
60        let (w, h) = self.placed[e].footprint;
61        self.cells
62            .add_layer(layer, (w >> 1) + x, (h >> 1) + y, radius, delta);
63    }
64
65    /// `FUN_14004ae50`.
66    fn happiness_register(&mut self, e: usize) {
67        self.happiness_layer(e, 1);
68        self.happiness.push(e);
69        if let Some(s) = self.sites.get_mut(&e) {
70            s.happiness_registered = true;
71        }
72    }
73
74    /// `FUN_14004ae00` (and `FUN_14004b2a0` / `FUN_14004b380` when registered).
75    fn happiness_unregister(&mut self, e: usize) {
76        if let Some(i) = self.happiness.iter().position(|&x| x == e) {
77            self.happiness.remove(i);
78        }
79        self.happiness_layer(e, -1);
80        if let Some(s) = self.sites.get_mut(&e) {
81            s.happiness_registered = false;
82        }
83    }
84
85    fn happiness_registered(&self, e: usize) -> bool {
86        self.sites.get(&e).is_some_and(|s| s.happiness_registered)
87    }
88
89    /// State 2 and 0xd enter (`FUN_14004b350`).
90    pub(crate) fn happiness_enter(&mut self, e: usize) {
91        if !self.happiness_registered(e) && self.happiness_active(e) {
92            self.happiness_register(e);
93        }
94    }
95
96    /// State 2 and 0xd scheduled update (`FUN_14004b300`).
97    pub(crate) fn happiness_update(&mut self, e: usize) {
98        let active = self.happiness_active(e);
99        if !self.happiness_registered(e) {
100            if active {
101                self.happiness_register(e);
102            }
103        } else if !active {
104            self.happiness_unregister(e);
105        }
106    }
107
108    /// State 3 enter (`FUN_14004b2a0`) and the component's removal (`FUN_14004b380`).
109    pub(crate) fn happiness_leave(&mut self, e: usize) {
110        if self.happiness_registered(e) {
111            self.happiness_unregister(e);
112        }
113    }
114
115    // +0xc of its slot
116    /// Message 0, a citizen's visit: one unit of the first stored raw material that matches and is
117    /// not reserved is used up, and counted.
118    ///
119    /// Original: `FUN_14004b0e0`.
120    pub fn happiness_visit(&mut self, e: usize) {
121        let Some(site) = self.sites.get(&e) else {
122            return;
123        };
124        let Some(d) = self.site_templates[site.template].happiness else {
125            return;
126        };
127        if !d.needs_stock {
128            return;
129        }
130        let storage = &mut self.storages[site.storage];
131        let found = storage
132            .items
133            .iter()
134            .find(|i| d.mask & self.materials[i.material].flags != 0 && 0 < i.count - i.reserved);
135        if let Some(m) = found.map(|i| i.material) {
136            storage.remove(m, 1);
137            self.count_used(m, 1);
138        }
139    }
140}