Skip to main content

banished_game/behaviour/
activities.rs

1//! What each activity of the citizens' AI does. See docs/CITIZEN-AI.md. Only the parts of the town
2//! that the activities ask about are modelled here, in [`TownView`].
3//!
4//! Original: the activities run by `FUN_1401146c0`.
5
6use crate::pathfind::{WALKABLE, WATER};
7use crate::random::Random;
8use crate::sectors::SectorGraph;
9use crate::terrain::CellType;
10use crate::work::stock::StorageSearchMap;
11
12// MapComponent +0x28..+0x2e
13/// `MapComponent`: an entity's map rectangle `(x, y, w, h)`.
14pub type MapRect = (i32, i32, i32, i32);
15
16/// What activities need to know about one entity.
17#[derive(Debug, Clone, Default)]
18pub struct EntityView {
19    /// Whether it is still in the world.
20    pub alive: bool,
21    /// Has a MapComponent: its rectangle.
22    pub rect: Option<MapRect>,
23    /// InteractComponent "use" points in world space (category 0).
24    pub use_points: Vec<[f32; 3]>,
25    /// Whether it has an InteractComponent.
26    pub has_interact: bool,
27    /// The position.
28    pub position: [f32; 3],
29    /// Whether it has a model.
30    pub has_model: bool,
31    // +0x2c; usable when FUN_14004b080 finds description type != 5 and enabled.
32    /// For a building where citizens may loiter (a happiness component): the description's loiter
33    /// padding.
34    pub happiness: Option<i32>,
35}
36
37/// The geometry called by the town's storage search. Uses the same sector cache as the AI.
38pub struct StorageGeometry<'a, 'b> {
39    /// A view of the town.
40    pub town: &'a TownView<'b>,
41    /// The sector graph.
42    pub sectors: &'a mut SectorGraph,
43    /// The ground point of a cell.
44    pub ground: &'a dyn Fn(i32) -> [f32; 3],
45}
46
47impl StorageSearchMap for StorageGeometry<'_, '_> {
48    fn entrance(&mut self, entity: usize, rng: &mut Random) -> i32 {
49        self.town.entrance(entity, rng)
50    }
51
52    /// FUN_140008830: path rank, or map width plus truncated Euclidean cell distance.
53    fn rank(&mut self, a: i32, b: i32) -> i32 {
54        let n = self.town.size as i32;
55        self.sectors.rebuild(self.town.types);
56        let d = self.sectors.distance(
57            a,
58            b,
59            self.town.reachable(WALKABLE, a, b),
60            true,
61            self.town.types,
62        );
63        if d >= 0 {
64            d
65        } else {
66            let (dx, dy) = ((a % n - b % n) as f32, (a / n - b / n) as f32);
67            n + (dx * dx + dy * dy).sqrt() as i32
68        }
69    }
70
71    /// Port of FUN_14004f710: draw a use point (including Rand(1)), then test reachability.
72    fn market_position(
73        &mut self,
74        entity: usize,
75        from: i32,
76        rng: &mut Random,
77    ) -> Option<(i32, [f32; 3])> {
78        let points = &self.town.entities[entity].use_points;
79        let k = rng.rand_below(points.len() as u32) as usize;
80        let p = *points.get(k)?;
81        let cell = self.town.cell_of(p);
82        self.town
83            .reachable(WALKABLE, from, cell)
84            .then(|| (self.rank(from, cell), p))
85    }
86
87    /// Port of FUN_140132aa0: nearest reachable use point, or scan the grown map rectangle.
88    /// The rectangle branch includes one extra row at its upper end, as the x64 loop does.
89    fn storage_position(&mut self, entity: usize, from: i32) -> Option<(i32, [f32; 3])> {
90        let e = &self.town.entities[entity];
91        let mut best = i32::MAX;
92        let mut target = None;
93        if e.has_interact {
94            for p in &e.use_points {
95                let cell = self.town.cell_of(*p);
96                if self.town.reachable(WALKABLE, from, cell) {
97                    let d = self.rank(from, cell);
98                    if d >= 0 && d < best {
99                        best = d;
100                        target = Some(*p);
101                    }
102                }
103            }
104        } else if let Some((x, y, w, h)) = e.rect {
105            let n = self.town.size as i32;
106            for cy in y - 1..=y + h + 1 {
107                for cx in x - 1..x + w + 1 {
108                    if cx < 0 || cy < 0 || cx >= n || cy >= n {
109                        continue;
110                    }
111                    let cell = cy * n + cx;
112                    if (1u32 << (self.town.types[cell as usize].0 & 31)) & 0xe07 != 0
113                        && self.town.reachable(WALKABLE, from, cell)
114                    {
115                        let d = self.rank(cell, from);
116                        if d < best {
117                            best = d;
118                            target = Some((self.ground)(cell));
119                        }
120                    }
121                }
122            }
123        }
124        target.map(|p| (best, p))
125    }
126}
127
128/// The map and entities activities read.
129pub struct TownView<'a> {
130    /// The map's size in cells.
131    pub size: usize,
132    /// The type of each cell.
133    pub types: &'a [CellType],
134    /// The walkable region of each cell.
135    pub regions: &'a [u16],
136    // +0x00; +0x08
137    /// The occupant of each cell.
138    pub occupants: &'a [Option<usize>],
139    /// The object on each cell.
140    pub objects: &'a [Option<usize>],
141    // +0x2d
142    /// The bridges' floors (and the floor scene), for the ground under a point.
143    pub floors: &'a crate::world::floor::Floors,
144    /// A view of each entity.
145    pub entities: &'a [EntityView],
146    // +0x50; Game +0xf0
147    /// The town manager's HappinessComponent registry (`Game`).
148    pub happiness: &'a [usize],
149}
150
151impl TownView<'_> {
152    /// The map cell that holds a position.
153    pub fn cell_of(&self, p: [f32; 3]) -> i32 {
154        let n = self.size as i32;
155        let half = (n >> 1) as f32;
156        let x = ((p[0] + half) as i32).clamp(0, n - 1);
157        let y = ((p[1] + half) as i32).clamp(0, n - 1);
158        n * y + x
159    }
160
161    /// Can a walker with `mask` get from `a` to `b` (water types skip the region test)?
162    ///
163    /// Original: `FUN_1400a55c0`.
164    pub fn reachable(&self, mask: u32, a: i32, b: i32) -> bool {
165        let (ba, bb) = (
166            1u32 << (self.types[a as usize].0 & 0x1f),
167            1u32 << (self.types[b as usize].0 & 0x1f),
168        );
169        let mask = mask | ba | bb;
170        if mask & ba == 0 || mask & bb == 0 {
171            return false;
172        }
173        if mask & WATER != 0 {
174            return true;
175        }
176        let (ra, rb) = (self.regions[a as usize], self.regions[b as usize]);
177        ra != 0 && rb != 0 && ra == rb
178    }
179
180    /// The cell a citizen walks to at an entity: its first use point (the original draws
181    /// `Rand(count)` when there are several, but always takes point 0), else the centre of its map
182    /// rectangle.
183    ///
184    /// Original: `FUN_14008d480(entity, 0)`.
185    pub fn entrance(&self, entity: usize, rng: &mut Random) -> i32 {
186        let e = &self.entities[entity];
187        if !e.use_points.is_empty() {
188            if e.use_points.len() > 1 {
189                rng.rand_below(e.use_points.len() as u32);
190            }
191            return self.cell_of(e.use_points[0]);
192        }
193        match e.rect {
194            Some((x, y, w, h)) => (y + (h >> 1)) * self.size as i32 + (w >> 1) + x,
195            None => -1,
196        }
197    }
198
199    /// `Interact::PointPosition(entity, 0)` as a cell (no draw): the first use point, else
200    /// the map rectangle's centre.
201    pub fn point_cell(&self, entity: usize) -> i32 {
202        let e = &self.entities[entity];
203        if let Some(p) = e.use_points.first() {
204            return self.cell_of(*p);
205        }
206        match e.rect {
207            Some((x, y, w, h)) => (y + (h >> 1)) * self.size as i32 + (w >> 1) + x,
208            None => -1,
209        }
210    }
211
212    /// Is `entity` reachable from `cell`?
213    ///
214    /// Original: `FUN_14011ada0`.
215    pub fn can_reach(&self, cell: i32, entity: usize, rng: &mut Random) -> bool {
216        let e = self.entrance(entity, rng);
217        e >= 0 && self.reachable(WALKABLE, cell, e)
218    }
219
220    /// Object on a cell as the scans read it: the occupant, else the stamped object.
221    fn object(&self, cell: usize) -> Option<usize> {
222        self.occupants[cell].or(self.objects[cell])
223    }
224}
225
226/// The workplace searches' view of the town ([`crate::work::professions::Search`]).
227pub struct TownSearch<'a, 'b> {
228    /// A view of the town.
229    pub town: &'a TownView<'b>,
230    /// A citizen's map cell.
231    pub cell: &'a dyn Fn(usize) -> i32,
232    /// A profession works anywhere.
233    pub anywhere: &'a dyn Fn(usize) -> bool,
234    /// A workplace's work priority.
235    pub priority: &'a dyn Fn(usize) -> i32,
236    /// The game's random numbers.
237    pub rng: &'a mut Random,
238}
239
240impl crate::work::professions::Search for TownSearch<'_, '_> {
241    fn cell(&self, c: usize) -> i32 {
242        (self.cell)(c)
243    }
244    fn anywhere(&self, profession: usize) -> bool {
245        (self.anywhere)(profession)
246    }
247    fn priority(&self, workplace: usize) -> i32 {
248        (self.priority)(workplace)
249    }
250    fn reach(&mut self, cell: i32, workplace: usize) -> bool {
251        self.town.can_reach(cell, workplace, self.rng)
252    }
253    fn rng(&mut self) -> &mut Random {
254        self.rng
255    }
256}
257
258/// A loiter: walk to `target`, idle for `duration` seconds, then notify `happiness` (when set).
259///
260/// Original: `FUN_14010fa30`, `FUN_1400bfb80`.
261#[derive(Debug, Clone, PartialEq)]
262pub struct Loiter {
263    /// Where to walk.
264    pub target: [f32; 3],
265    /// How long to idle, in seconds.
266    pub duration: f32,
267    /// The building to notify after, if any.
268    pub happiness: Option<usize>,
269}
270
271// CitizenDescription +0xbc
272/// Activity 12: choose a spot near something to stand at. `cell` is the citizen's cell, `home` its
273/// home, `range` the citizen description's range. `ground(cell)` returns a cell's centre on the
274/// ground; `rank(a, b)` is the path-rank distance.
275///
276/// Original: `FUN_14010fa30`; the distance is `FUN_140008830`.
277pub fn loiter(
278    town: &TownView,
279    cell: i32,
280    home: Option<usize>,
281    range: i32,
282    rng: &mut Random,
283    ground: impl Fn(i32) -> [f32; 3],
284    rank: &mut dyn FnMut(i32, i32) -> i32,
285) -> Option<Loiter> {
286    let n = town.size as i32;
287    let home_cell = home.map_or(-1, |h| town.entrance(h, rng));
288    let mut chosen = town.occupants[cell as usize];
289    if rng.rand_float() < 0.25 {
290        chosen = None;
291    }
292    // The occupant only counts when it is a usable happiness entity reachable from here
293    // through its first use point.
294    chosen = chosen.filter(|&e| {
295        let v = &town.entities[e];
296        v.happiness.is_some()
297            && v.use_points
298                .first()
299                .is_some_and(|&p| town.reachable(WALKABLE, cell, town.cell_of(p)))
300    });
301    if chosen.is_none() {
302        chosen = nearest_happiness(town, cell, range, rng, rank);
303        if chosen.is_none() && home_cell >= 0 {
304            chosen = nearest_happiness(town, home_cell, range, rng, rank);
305        }
306    }
307    let mut pad = chosen.and_then(|e| town.entities[e].happiness).unwrap_or(6);
308    if chosen.is_none() {
309        // Scan 13 x 13 cells around the citizen (rows outer) for reachable map objects, then
310        // the home; pick one from the set. The original orders the set by object address; the
311        // port uses creation order.
312        let mut set = std::collections::BTreeSet::new();
313        let (cx, cy) = (cell % n, cell / n);
314        for y in cy - 6..=cy + 6 {
315            for x in cx - 6..=cx + 6 {
316                if x < 0 || x >= n || y < 0 || y >= n {
317                    continue;
318                }
319                let Some(e) = town.object((n * y + x) as usize) else {
320                    continue;
321                };
322                if town.entities[e].rect.is_some() && town.can_reach(cell, e, rng) {
323                    set.insert(e);
324                }
325            }
326        }
327        pad = 6;
328        if let Some(h) = home {
329            if town.can_reach(cell, h, rng) {
330                set.insert(h);
331            }
332        }
333        if !set.is_empty() {
334            let k = rng.rand_below(set.len() as u32) as usize;
335            chosen = set.iter().nth(k).copied();
336        }
337    }
338    let (happy, (x0, y0, w, h)) = match chosen {
339        None => (None, (cell % n, cell / n, 1, 1)),
340        Some(e) => {
341            let v = &town.entities[e];
342            (
343                v.happiness.map(|_| e),
344                v.rect.unwrap_or((cell % n, cell / n, 1, 1)),
345            )
346        }
347    };
348    // Walkable ground (types 0-2) in the rectangle grown by `pad` and reachable from here,
349    // as an ordered set of cell indices.
350    let mut cells = std::collections::BTreeSet::new();
351    for y in y0 - pad..y0 - pad + h + 2 * pad {
352        for x in x0 - pad..x0 - pad + w + 2 * pad {
353            if x < 0 || x >= n || y < 0 || y >= n {
354                continue;
355            }
356            let c = n * y + x;
357            if town.types[c as usize].0 < 3 && town.reachable(WALKABLE, cell, c) {
358                cells.insert(c);
359            }
360        }
361    }
362    if cells.is_empty() {
363        return None;
364    }
365    let k = rng.rand_below(cells.len() as u32) as usize;
366    let spot = *cells.iter().nth(k).unwrap();
367    let base = ground(spot);
368    // `DAT_14017880c` = 0.33; the y offset is drawn first.
369    let dy = rng.rand_float_tol() * 0.33;
370    let dx = rng.rand_float_tol() * 0.33;
371    // `DAT_140178898` = 5, `DAT_1401788b0` = 10.
372    let duration = rng.rand_float_tol() * 5.0 + 10.0;
373    Some(Loiter {
374        target: [base[0] + dx, base[1] + dy, base[2]],
375        duration,
376        happiness: happy,
377    })
378}
379
380/// `FUN_14008eea0`: a random registered HappinessComponent owner (not the unhappy kind) whose
381/// first use point is within path rank `2 * range` of `cell` and reachable, in registry
382/// order, picked with `Rand(count)`. Nothing is drawn without candidates.
383fn nearest_happiness(
384    town: &TownView,
385    cell: i32,
386    range: i32,
387    rng: &mut Random,
388    rank: &mut dyn FnMut(i32, i32) -> i32,
389) -> Option<usize> {
390    let mut found = Vec::new();
391    for &e in town.happiness {
392        let point = town.point_cell(e);
393        if point < 0 {
394            continue;
395        }
396        if rank(cell, point) < range * 2
397            && town.reachable(WALKABLE, cell, point)
398            && town.entities[e].happiness.is_some()
399        {
400            found.push(e);
401        }
402    }
403    if found.is_empty() {
404        return None;
405    }
406    Some(found[rng.rand_below(found.len() as u32) as usize])
407}