Skip to main content

banished_game/production/
pasture.rs

1//! Pastures: PastureComponent and its work. The animals themselves (livestock, breeding, products)
2//! live in [`crate::nature::wildlife`]. See docs/FARMING.md "Pastures".
3//!
4//! A built pasture takes waiting livestock of its kind until full, has the young its breeding males
5//! sire born, and turns what its animals give (eggs, wool) into piles at the shelter. Its ranchers
6//! cull the oldest grown animal while it holds more than the player allows, pick up the products,
7//! or walk the pasture, and each visit lets a random animal breed.
8//!
9//! Original: `FUN_14012d660`, `FUN_1400f8250`.
10// vtable 0x14016a360; vtable 0x140167bc0
11
12use crate::behaviour::character::{Action, Component, Message, MoveTo};
13use crate::nature::wildlife::{Wild, HUNTED};
14use crate::pathfind::{nearest_neighbour, WALKABLE};
15use crate::production::herbalist::HerbalistWork;
16use crate::scheduler::EntityScheduler;
17use crate::terrain::CellType;
18use crate::town::Sim;
19use crate::work::jobs::{Jobs, Work, WorkKind, NESTED};
20
21impl Sim<'_, '_> {
22    /// The livestock's context. Animals born here join the entity manager's buckets with the
23    /// iteration's other new entities.
24    pub(crate) fn with_wild<R>(&mut self, f: impl FnOnce(&mut Wild) -> R) -> R {
25        let mut scheduler = EntityScheduler::default();
26        let mut wild = Wild {
27            wildlife: &mut *self.wildlife,
28            placed: &mut *self.placed,
29            templates: self.templates,
30            cells: &*self.cells,
31            terrain: self.terrain,
32            rng: &mut *self.rng,
33            scheduler: &mut scheduler,
34        };
35        f(&mut wild)
36    }
37
38    /// State 2 enter (`FUN_14012c590`): take waiting livestock, join the town's pastures, and
39    /// the workplace waits for the player to choose a kind.
40    pub(crate) fn pasture_enter2(&mut self, e: usize) {
41        self.with_wild(|w| w.fill(e));
42        self.wildlife.pasture_list.push(e);
43        self.set_pasture_workplace(e, false);
44    }
45
46    /// State 2 update (`FUN_14012bcb0`): waiting livestock, births, then the products: every
47    /// pile's worth gathered becomes a pile on the first free shelter point while the stock
48    /// is under its limit (else it is lost).
49    pub(crate) fn pasture_update2(&mut self, jobs: &mut Jobs, e: usize) {
50        self.with_wild(|w| {
51            w.fill(e);
52            w.births(e);
53        });
54        let Some(kind) = self.wildlife.pastures.get(&e).and_then(|p| p.kind) else {
55            return;
56        };
57        // `FUN_1400a4c40`: the pasture's cells remember the kind.
58        let n = self.size as i32;
59        let area = self.wildlife.pastures[&e].area;
60        for y in area[1] as i32..(area[1] + area[3]) as i32 {
61            for x in area[0] as i32..(area[0] + area[2]) as i32 {
62                self.cells.previous_template[(y * n + x) as usize] = Some(kind);
63            }
64        }
65        let Some(lk) = self.wildlife.livestock_kinds.get(&kind).cloned() else {
66            return;
67        };
68        let Some(product) = lk.product else { return };
69        let Some(material) = self.templates[product].pile_material else {
70            return;
71        };
72        let amount = self.materials[material].pile_amount;
73        let (count, limit) = self.stock[lk.group.clamp(0, 20) as usize];
74        // Deviation: a zero pile size would never leave the loop in the original.
75        while 0 < amount && amount < self.wildlife.pastures[&e].produced {
76            self.wildlife.pastures.get_mut(&e).unwrap().produced -= amount;
77            if limit <= count {
78                continue;
79            }
80            for cell in self.pasture_points(e) {
81                let i = cell as usize;
82                if let Some(o) = self.cells.objects[i] {
83                    if self.piles.contains_key(&o) {
84                        continue;
85                    }
86                    // Anything else there goes (the cell becomes plain ground).
87                    self.cells.set_type(i, CellType(2));
88                    self.cells.objects[i] = None;
89                }
90                let yaw = self.rng.rand_tol(2) as f32 * 90.0;
91                let pile = self.spawn_pile(product, (cell % n, cell / n), amount, yaw, None);
92                // The pasture's tracker and the stock manager's count (`FUN_140059930`,
93                // `FUN_14008e460`).
94                self.count_made(Some(e), true, material, amount);
95                let auto = self
96                    .sites
97                    .get(&e)
98                    .and_then(|s| s.auto_pickup.as_ref())
99                    .is_some();
100                if auto {
101                    if let Some(p) = self.piles.get_mut(&pile) {
102                        p.auto_pickup = Some(e);
103                    }
104                }
105                // `FUN_1400c9d70`: registered (its Pickup work) and handed to the AutoPickup.
106                self.activate_piles(jobs);
107                break;
108            }
109        }
110    }
111
112    /// State 3 enter (`FUN_14012c310`) and the removal slot (`FUN_14012ccb0`): the animals go
113    /// back to wait for another pasture.
114    pub(crate) fn pasture_leave(&mut self, e: usize) {
115        self.with_wild(|w| w.release(e));
116    }
117
118    /// Slot `+0x60` with event 4 (`FUN_14012ca10`, sent when building ends, `FUN_14010cad0`):
119    /// the shelter's points become plain ground for the products.
120    pub(crate) fn pasture_built(&mut self, e: usize) {
121        for cell in self.pasture_points(e) {
122            let i = cell as usize;
123            self.cells.set_type(i, CellType(2));
124            self.cells.objects[i] = None;
125        }
126    }
127
128    /// The InteractComponent's create points (kind 2, `FUN_140128540` / `FUN_1401282f0`): its
129    /// own, then its children's (the shelter's), as cells.
130    pub(crate) fn pasture_points(&self, e: usize) -> Vec<i32> {
131        let mut cells = Vec::new();
132        let parts = self
133            .sites
134            .get(&e)
135            .map(|s| s.parts.clone())
136            .unwrap_or_default();
137        for x in std::iter::once(e).chain(parts) {
138            let count = self.templates[self.placed[x].template].create_points.len();
139            cells.extend((0..count).map(|i| self.create_cell(x, i)));
140        }
141        cells
142    }
143
144    /// `FUN_1401350d0` on the pasture's workplace.
145    fn set_pasture_workplace(&mut self, e: usize, on: bool) {
146        self.enable_workplace(e, on);
147    }
148
149    /// The pasture's message 0: a rancher's visit.
150    ///
151    /// Original: `FUN_14012cbf0`.
152    pub fn pasture_message(&mut self, e: usize, message: i32) {
153        if message == 0 {
154            self.with_wild(|w| w.mark_breeder(e));
155        }
156    }
157
158    // +0x58
159    /// PastureUI slot: the kind of animal; choosing the first one switches the work on. The most it
160    /// keeps becomes its room.
161    ///
162    /// Original: `FUN_1400ba110`.
163    pub fn set_pasture_kind(&mut self, jobs: &mut Jobs, e: usize, kind: Option<usize>) {
164        let Some(old) = self.wildlife.pastures.get(&e).map(|p| p.kind) else {
165            return;
166        };
167        if old.is_none() && kind.is_some() {
168            self.wildlife.pastures.get_mut(&e).unwrap().kind = kind;
169            self.pasture_action(jobs, e, 2);
170        }
171        self.wildlife.pastures.get_mut(&e).unwrap().kind = kind;
172        let cap = self.wildlife.capacity(e);
173        self.wildlife.pastures.get_mut(&e).unwrap().limit = cap;
174    }
175
176    // +0x48
177    /// PastureUI slot: the slider sets the most animals kept.
178    ///
179    /// Original: `FUN_1400ba2f0`.
180    pub fn set_pasture_limit(&mut self, e: usize, limit: i32) {
181        if let Some(p) = self.wildlife.pastures.get_mut(&e) {
182            p.limit = limit;
183        }
184    }
185
186    // +0x68
187    /// Whether the window's button `query` is available. 0 (send every animal away) needs room for
188    /// all of them in other pastures of the kind, 1 (send half) ten animals and room for five; 2 is
189    /// the work toggle's state (bit 2 on).
190    ///
191    /// Original: `FUN_14012d2a0`.
192    pub fn pasture_query(&self, e: usize, query: i32) -> u8 {
193        let Some(p) = self.wildlife.pastures.get(&e) else {
194            return 0;
195        };
196        let mut free = 0;
197        for &o in &self.wildlife.pasture_list {
198            if o != e
199                && self
200                    .wildlife
201                    .pastures
202                    .get(&o)
203                    .is_some_and(|x| x.kind == p.kind)
204            {
205                free +=
206                    self.wildlife.capacity(o) - self.wildlife.pastures[&o].livestock.len() as i32;
207            }
208        }
209        let has = u8::from(p.kind.is_some());
210        let count = p.livestock.len() as i32;
211        match query {
212            0 if 0 < count && count <= free => has,
213            1 if 9 < count && 4 < free => has,
214            2 => {
215                let on = self.workforce.workplaces.get(&e).is_some_and(|w| w.enabled);
216                if on {
217                    2 | has
218                } else {
219                    has
220                }
221            }
222            _ => 0,
223        }
224    }
225
226    // +0x70
227    /// 2 switches the work on or off; 0 sends every animal (1 half of them, rounded down) to other
228    /// pastures of the kind, the roomiest first.
229    ///
230    /// Original: `FUN_14012cf30`.
231    pub fn pasture_action(&mut self, jobs: &mut Jobs, e: usize, action: i32) {
232        if self.pasture_query(e, action) & 1 == 0 {
233            return;
234        }
235        if action == 2 {
236            let on = self.workforce.workplaces.get(&e).is_some_and(|w| w.enabled);
237            self.set_pasture_workplace(e, !on);
238            if on {
239                self.remove_work(jobs, e);
240                self.set_pasture_workplace(e, false);
241            } else {
242                let work = self.pasture_work(e);
243                self.set_work(jobs, e, work, 1, true);
244                self.set_pasture_workplace(e, true);
245            }
246            return;
247        }
248        let count = self.wildlife.pastures[&e].livestock.len() as i32;
249        let keep = if action == 0 { 0 } else { count - (count >> 1) };
250        let kind = self.wildlife.pastures[&e].kind;
251        let room = |w: &crate::nature::wildlife::Wildlife, o: usize| {
252            w.capacity(o) - w.pastures[&o].livestock.len() as i32
253        };
254        let others: Vec<usize> = self
255            .wildlife
256            .pasture_list
257            .iter()
258            .copied()
259            .filter(|&o| {
260                o != e
261                    && self
262                        .wildlife
263                        .pastures
264                        .get(&o)
265                        .is_some_and(|x| x.kind == kind)
266            })
267            .collect();
268        let mut best = None;
269        let mut most = 0;
270        for &o in &others {
271            let r = room(self.wildlife, o);
272            if most < r {
273                most = r;
274                best = Some(o);
275            }
276        }
277        let send = |sim: &mut Self, o: usize| {
278            while keep < sim.wildlife.pastures[&e].livestock.len() as i32
279                && 0 < room(sim.wildlife, o)
280            {
281                let a = sim.wildlife.pastures[&e].livestock[0];
282                sim.with_wild(|w| w.assign(a, Some(o)));
283            }
284        };
285        if let Some(b) = best {
286            send(self, b);
287        }
288        for o in others {
289            if Some(o) != best {
290                send(self, o);
291            }
292        }
293    }
294
295    /// The work (`FUN_1400f8250`), made when the player switches it on.
296    pub(crate) fn pasture_work(&self, e: usize) -> Work {
297        let profession = self
298            .workforce
299            .workplaces
300            .get(&e)
301            .and_then(|w| w.profession)
302            .unwrap_or(self.professions.laborer);
303        Work::new(
304            e,
305            self.rect_cell(e),
306            profession,
307            WorkKind::Pasture(HerbalistWork::default()),
308        )
309    }
310
311    /// Slot `+0x20` (`FUN_1400f7840`) after the AutoPickup offers. With room in the stock: over
312    /// the limit, cull the oldest grown animal; else offer the products' pickups at the
313    /// shelter (`pasture_stage` marks the rest of the try).
314    pub(crate) fn pasture_try(&mut self, work: &mut Work, c: usize) -> u8 {
315        if std::mem::take(&mut self.pasture_stage) {
316            return self.pasture_walk(work, c);
317        }
318        let e = work.entity;
319        let WorkKind::Pasture(h) = &work.kind else {
320            return 0;
321        };
322        if self.work_max(e) < h.workers.len() as i32 {
323            return 1;
324        }
325        let group = self
326            .sites
327            .get(&e)
328            .and_then(|s| self.site_templates[s.template].pasture)
329            .unwrap_or(5);
330        let (count, limit) = self.stock[group.clamp(0, 20) as usize];
331        self.set_icon(e, crate::work::professions::icon::LIMIT, limit <= count);
332        if limit <= count {
333            return 1;
334        }
335        let Some(p) = self.wildlife.pastures.get(&e) else {
336            return 0;
337        };
338        let here = self.citizen_cell(c);
339        if p.limit < p.livestock.len() as i32 {
340            let Some(a) = self.cull_choice(e) else {
341                return 1;
342            };
343            let animal = &self.wildlife.animals[&a];
344            if animal.flags & HUNTED != 0 {
345                return 1;
346            }
347            let at = self.cell_of(animal.character.position);
348            let Some(nb) = nearest_neighbour(self.size, &self.cells.types, at, here, WALKABLE)
349            else {
350                return 1;
351            };
352            if !self.reachable(nb, here) {
353                return 1;
354            }
355            let point = self.ground_at(nb);
356            self.cull_sequence(c, work, a, point, at);
357            self.took_job(c, work);
358            return 2;
359        }
360        let n = self.size as i32;
361        let offers: Vec<i32> = self
362            .pasture_points(e)
363            .into_iter()
364            .filter(|&cell| 0 <= cell && cell < n * n)
365            .filter_map(|cell| self.cells.objects[cell as usize])
366            .filter_map(|o| self.piles.get(&o).map(|p| p.work))
367            .collect();
368        if !offers.is_empty() {
369            self.offers = offers;
370            self.offers_again = true;
371            self.pasture_stage = true;
372            return NESTED;
373        }
374        self.pasture_walk(work, c)
375    }
376
377    /// The try's end: walk to a random cell of the pasture and tend there (`FUN_1400c0570`
378    /// with the pasture's message 0); the rancher joins the work's set.
379    fn pasture_walk(&mut self, work: &mut Work, c: usize) -> u8 {
380        let e = work.entity;
381        let Some(area) = self.wildlife.pastures.get(&e).map(|p| p.area) else {
382            return 0;
383        };
384        let n = self.size as i32;
385        let ry = self.rng.rand_below(area[3] as u32) as i32;
386        let rx = self.rng.rand_below(area[2] as u32) as i32;
387        let cell = (area[1] as i32 + ry) * n + rx + area[0] as i32;
388        let here = self.citizen_cell(c);
389        let Some(nb) = nearest_neighbour(self.size, &self.cells.types, cell, here, WALKABLE) else {
390            return 0;
391        };
392        if !self.reachable(here, nb) {
393            return 0;
394        }
395        let at = self.ground_at(cell);
396        let stand = self.ground_at(nb);
397        let message = Message {
398            component: Component::Pasture,
399            message: 0,
400            entity: Some(e),
401            a: 0,
402            b: 0,
403        };
404        self.tend_sequence(c, work, stand, at, message);
405        self.took_job(c, work);
406        if let WorkKind::Pasture(h) = &mut work.kind {
407            h.workers.insert(c);
408        }
409        2
410    }
411
412    /// `FUN_14012d820`: the oldest animal past its grown age that no one has taken.
413    fn cull_choice(&self, e: usize) -> Option<usize> {
414        let mut best = None;
415        let mut oldest = 0.0f32;
416        for &a in &self.wildlife.pastures.get(&e)?.livestock {
417            let animal = &self.wildlife.animals[&a];
418            if oldest < animal.age && animal.flags & HUNTED == 0 {
419                let adult = self.wildlife.animal_kinds[&animal.template].desc.adult;
420                if adult < animal.age {
421                    oldest = animal.age;
422                    best = Some(a);
423                }
424            }
425        }
426        best
427    }
428
429    /// `FUN_1400c1370` with clearing: the rancher walks next to the animal, works 4 s, removes
430    /// it and leaves its raw materials where it stood; the animal is taken (`FUN_14010c730`).
431    fn cull_sequence(&mut self, c: usize, work: &mut Work, a: usize, point: [f32; 3], at: i32) {
432        self.clear_citizen(c, Some(work));
433        self.on_empty_think(c);
434        self.push(
435            c,
436            Action::CompleteTask {
437                work: work.id,
438                param: a as i32,
439            },
440        );
441        let template = self.wildlife.animals[&a].template;
442        let materials = self
443            .wildlife
444            .animal_kinds
445            .get(&template)
446            .map(|k| k.materials.clone())
447            .unwrap_or_default();
448        let batch = self.states[c].carried.capacity;
449        let workplace = self.states[c].workplace;
450        for m in materials {
451            self.push(
452                c,
453                Action::CreateRawMaterial {
454                    template: m,
455                    cell: at,
456                    batch,
457                    workplace,
458                    factor: 1.0,
459                },
460            );
461        }
462        self.push(c, Action::RemoveAnimal { entity: a });
463        let perform = self.perform(c, -1000.0, 0xb, 4.0, true);
464        self.push(c, perform);
465        let walk = MoveTo::new(point, 1, 0, WALKABLE, 0, -1.0, self.rng);
466        self.push(c, Action::move_to(walk));
467        self.with_wild(|w| w.hunted(a));
468    }
469
470    /// Slot `+0x38` (`FUN_1400f7790`): the rancher leaves the set, and a culled animal is free
471    /// again.
472    pub(crate) fn pasture_fail(&mut self, work: &mut Work, c: usize, param: i32) {
473        if let WorkKind::Pasture(h) = &mut work.kind {
474            h.workers.remove(&c);
475        }
476        if let Some(animal) = usize::try_from(param)
477            .ok()
478            .and_then(|a| self.wildlife.animals.get_mut(&a))
479        {
480            animal.flags &= !HUNTED;
481        }
482    }
483}