Skip to main content

banished_game/production/
hunt.rs

1//! The hunter's lodge and its work. See docs/JOBS.md, "Hunters".
2//!
3//! The lodge gathers hunting time while it is staffed. A hunter walks to a random empty cell in the
4//! radius, or waits there, with a `HuntAnimal` watching. Once the lodge has gathered enough time, a
5//! hunter who comes within 8 of a wild animal goes to it and takes it: its meat and hide are left
6//! where it fell.
7//!
8//! Original: `HuntComponent`; `FUN_140127c10`, `FUN_1400cd2b0`, `FUN_1400c1370`; the work is made
9//! by slot `FUN_140127b70`.
10// vtable 0x140169ba0; vtable 0x140167a40; +0x90
11
12use crate::behaviour::character::{Action, MoveTo};
13use crate::pathfind::WALKABLE;
14use crate::production::herbalist::HerbalistWork;
15use crate::scheduler::{seconds, ticks};
16use crate::town::Sim;
17use crate::work::jobs::{Work, WorkKind};
18
19impl Sim<'_, '_> {
20    /// Slot `+0x90` (`FUN_140127b70`).
21    pub(crate) fn hunt_work(&self, e: usize) -> Work {
22        let profession = self
23            .workforce
24            .workplaces
25            .get(&e)
26            .and_then(|w| w.profession)
27            .unwrap_or(self.professions.laborer);
28        Work::new(
29            e,
30            self.rect_cell(e),
31            profession,
32            WorkKind::Hunt(HerbalistWork::default()),
33        )
34    }
35
36    /// HuntComponent's scheduled update: the hunting time grows by the elapsed time times the
37    /// workers over the most allowed.
38    ///
39    /// Original: `FUN_140127c10`.
40    pub fn update_hunts(&mut self, visits: &[(usize, i64)]) {
41        for &(e, dt) in visits {
42            if !self.sites.get(&e).is_some_and(|s| s.hunt_time.is_some()) {
43                continue;
44            }
45            let workers = self
46                .workforce
47                .workplaces
48                .get(&e)
49                .map_or(0, |w| w.workers.len()) as f32;
50            let max = self.work_max(e) as f32;
51            // Deviation: no workers allowed would divide by zero in the original.
52            if max <= 0.0 {
53                continue;
54            }
55            let add = ticks(seconds(dt) * (workers / max));
56            if let Some(t) = self.sites.get_mut(&e).and_then(|s| s.hunt_time.as_mut()) {
57                *t += add;
58            }
59        }
60    }
61
62    /// Slot `+0x20` (`FUN_1400f44c0`) after the AutoPickup offers.
63    pub(crate) fn hunt_try(&mut self, work: &mut Work, c: usize) -> u8 {
64        let e = work.entity;
65        if self.states[c].carried.used(self.materials) >= 1 {
66            self.drop_off(c);
67            return 2;
68        }
69        let Some(site) = self.sites.get(&e) else {
70            return 0;
71        };
72        let t = &self.site_templates[site.template];
73        let Some(d) = t.hunt else { return 0 };
74        let radius = t.radius.unwrap_or(0);
75        let (count, limit) = self.stock[d.group.clamp(0, 20) as usize];
76        self.set_icon(e, crate::work::professions::icon::LIMIT, limit <= count);
77        if limit <= count {
78            return 1;
79        }
80        let WorkKind::Hunt(h) = &work.kind else {
81            return 0;
82        };
83        if self.work_max(e) < h.workers.len() as i32 {
84            return 1;
85        }
86        let n = self.size as i32;
87        let (cx, cy) = (work.cell % n, work.cell / n);
88        let (x, y) = loop {
89            let (dx, dy) = loop {
90                let dx = self.rng.rand_tol(radius);
91                let dy = self.rng.rand_tol(radius);
92                if dx * dx + dy * dy <= radius * radius {
93                    break (dx, dy);
94                }
95            };
96            // As in the gatherer's, only the row is kept on the map.
97            let (x, y) = (cx + dx, cy + dy);
98            if 0 <= y && y < n {
99                break (x, y);
100            }
101        };
102        let from = self.citizen_cell(c);
103        let entrance = self.entrance_cell(e);
104        let cell = n * y + x;
105        // Deviation: a column off the map's edge reads outside the cell array; no walk.
106        let walk = 0 <= cell
107            && cell < n * n
108            && entrance >= 0
109            && (1u32 << (self.cells.types[cell as usize].0 & 0x1f)) & 0x1e0 == 0
110            && self.geometry_rank(entrance, cell) <= radius * 2
111            && self.cells.objects[cell as usize].is_none()
112            && self.reachable(cell, from);
113        let point = walk.then(|| self.ground_at(cell));
114        self.hunt_sequence(c, work, e, point);
115        self.took_job(c, work);
116        if let WorkKind::Hunt(h) = &mut work.kind {
117            h.workers.insert(c);
118        }
119        2
120    }
121
122    /// `FUN_1400c1090`: walk to `point` (or work 5 s where it stands), watched by a
123    /// `HuntAnimal`, then report.
124    fn hunt_sequence(&mut self, c: usize, work: &mut Work, lodge: usize, point: Option<[f32; 3]>) {
125        self.clear_citizen(c, Some(work));
126        self.on_empty_think(c);
127        self.push(
128            c,
129            Action::CompleteTask {
130                work: work.id,
131                param: 0,
132            },
133        );
134        let action = match point {
135            Some(p) => Action::move_to(MoveTo::new(p, 1, 0, WALKABLE, 0, -1.0, self.rng)),
136            None => self.perform(c, -1000.0, 8, 5.0, true),
137        };
138        self.push(c, action);
139        self.characters[c]
140            .stack
141            .attach(Action::HuntAnimal { lodge, done: false });
142    }
143}