Skip to main content

banished_game/production/
orchard.rs

1//! Orchards: OrchardComponent, FruitComponent on the trees, and the works PlantOrchard, TendArea
2//! (as an orchard's) and fruit picking. See docs/FARMING.md "Orchards".
3//!
4//! Original: `FUN_140052a40`, `0x14015f5a0`, `0x140167d30`, `0x140167940`.
5// vtable 0x1401602a0
6
7use std::collections::BTreeMap;
8
9use crate::behaviour::character::{Action, Component, Message, MoveTo};
10use crate::pathfind::{nearest_neighbour, WALKABLE};
11use crate::production::farm::AreaWork;
12use crate::scheduler::{seconds, ticks};
13use crate::town::Sim;
14use crate::work::construction::Callbacks;
15use crate::work::jobs::{Jobs, Work, WorkKind};
16
17// +0x24; +0x28; +0x2c; +0x20
18/// OrchardDescription (versioned): the tree spacing, a tending visit's ripening (share of the
19/// ripening time), the stock slot (version 1).
20///
21/// Original: `FUN_140052c60`.
22#[derive(Debug, Clone, Copy, Default, PartialEq)]
23pub struct OrchardTemplate {
24    /// The tree spacing.
25    pub spacing: (i32, i32),
26    /// A tending visit's ripening, as a share of the ripening time.
27    pub tend: f32,
28    /// The stock slot (version 1).
29    pub group: i32,
30}
31
32// +0x50
33/// One tree slot (0x28 bytes).
34#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
35pub struct TreeSlot {
36    // +0x00
37    /// The map cell.
38    pub cell: i32,
39    // +0x04
40    /// 0 to plant, 1 standing, 2 lost.
41    pub state: i32,
42    // +0x08
43    /// The tree.
44    pub entity: Option<usize>,
45    // +0x20
46    /// Picked this harvest.
47    pub picked: bool,
48}
49
50// +0x48..+0x60
51/// OrchardComponent.
52#[derive(Debug, Clone, Default, PartialEq)]
53pub struct Orchard {
54    // +0x48
55    /// Tending on.
56    pub enabled: bool,
57    // +0x4c
58    /// The calendar year of the last harvest.
59    pub year: u32,
60    /// The tree slots.
61    pub trees: Vec<TreeSlot>,
62    // +0x60
63    /// The tree (natural-resource template), chosen in the orchard's window.
64    pub tree: Option<usize>,
65}
66
67// +0x30; +0x34
68/// FruitComponent ripeness (0–1) and picked this season.
69#[derive(Debug, Clone, Copy, Default, PartialEq)]
70pub struct Fruit {
71    /// The ripeness, from 0 to 1.
72    pub ripeness: f32,
73    /// Whether it was picked this season.
74    pub picked: bool,
75}
76
77// +0x20; +0x24; +0x28; +0x30
78/// FruitDescription: ripening time (× 300 s), the best temperature and its range, the fruit's raw
79/// material (pile template).
80///
81/// Original: `FUN_14004ab40`.
82#[derive(Debug, Clone, Copy, Default, PartialEq)]
83pub struct FruitTemplate {
84    /// The ripening time, in months of 300 seconds.
85    pub months: f32,
86    /// The best temperature.
87    pub best: f32,
88    /// The range of temperatures around the best one.
89    pub range: f32,
90    /// The fruit's pile template.
91    pub pile: Option<usize>,
92}
93
94/// FruitComponent's scheduled update. A picked tree waits for the next season (months 7–11 keep it
95/// picked). A grown tree ripens at `1 / (months × 300)` per second scaled by `1 − ((t − best) /
96/// range)²` (0 outside the range, −10 at or below 32 °F).
97///
98/// Original: `FUN_14004a740`.
99pub fn ripen(f: &mut Fruit, d: &FruitTemplate, growth: f32, dt: i64, calendar: f32, t: f32) {
100    let rate = 1.0 / (d.months * 300.0);
101    let month = (calendar / 3600.0 * 12.0) as i32;
102    if f.picked && 4 < (month - 7) as u32 {
103        f.picked = false;
104    }
105    let distance = (t - d.best).abs() / d.range;
106    let weight = if !f.picked && 1.0 <= growth {
107        if !(0.0..=1.0).contains(&distance) {
108            if t <= 32.0 {
109                -10.0
110            } else {
111                0.0
112            }
113        } else {
114            1.0 - distance * distance
115        }
116    } else {
117        0.0
118    };
119    let g = growth.min(1.0);
120    f.ripeness += seconds(dt) * g * rate * weight;
121    if f.ripeness < 0.0 {
122        f.ripeness = 0.0;
123    }
124    if 1.0 < f.ripeness {
125        f.ripeness = 1.0;
126    }
127}
128
129impl Sim<'_, '_> {
130    fn orchard(&self, e: usize) -> Option<&Orchard> {
131        self.sites.get(&e)?.orchard.as_ref()
132    }
133
134    fn orchard_mut(&mut self, e: usize) -> Option<&mut Orchard> {
135        self.sites.get_mut(&e)?.orchard.as_mut()
136    }
137
138    fn orchard_desc(&self, e: usize) -> OrchardTemplate {
139        self.site_templates[self.sites[&e].template]
140            .orchard
141            .unwrap_or_default()
142    }
143
144    /// `FUN_140052190`: a standing tree that has gone (or been replaced) is lost.
145    fn orchard_refresh(&mut self, e: usize) {
146        let objects = &self.cells.objects;
147        let placed = &self.placed;
148        let templates = self.templates;
149        let Some(o) = self.sites.get_mut(&e).and_then(|s| s.orchard.as_mut()) else {
150            return;
151        };
152        for t in o.trees.iter_mut().filter(|t| t.state == 1) {
153            let here = objects[t.cell as usize];
154            let alive = here.is_some_and(|x| {
155                templates[placed[x].template].natural.is_some() && Some(x) == t.entity
156            });
157            if !alive {
158                t.state = 2;
159                t.entity = None;
160            }
161        }
162    }
163
164    fn orchard_count(&mut self, e: usize, state: i32) -> usize {
165        self.orchard_refresh(e);
166        self.orchard(e)
167            .map_or(0, |o| o.trees.iter().filter(|t| t.state == state).count())
168    }
169
170    /// State 2 enter (`FUN_140051e90`): the workplace is off.
171    pub(crate) fn orchard_enter2(&mut self, e: usize) {
172        self.enable_workplace(e, false);
173    }
174
175    /// State 2 update (`FUN_140051e20`) and message 3 (`FUN_140051db0`): switched on, plant.
176    pub(crate) fn orchard_update2(&mut self, e: usize) {
177        if self.orchard(e).is_some_and(|o| o.enabled) {
178            self.machine(e).request(9);
179        }
180    }
181
182    /// State 9 enter (`FUN_1400519d0`): a tree slot every `spacing` cells (rows, then
183    /// columns), the footprint lifted, then PlantOrchard.
184    pub(crate) fn orchard_enter9(&mut self, jobs: &mut Jobs, e: usize) {
185        let on = self.orchard(e).is_some_and(|o| o.enabled);
186        self.enable_workplace(e, on);
187        let d = self.orchard_desc(e);
188        let (sx, sy) = (d.spacing.0.max(1), d.spacing.1.max(1));
189        let n = self.size as i32;
190        let (x0, y0) = self.placed[e].cell;
191        let (w, h) = self.placed[e].footprint;
192        let cols = (w - 1 + sx) / sx;
193        let rows = (h - 1 + sy) / sy;
194        let mut trees = Vec::new();
195        for r in 0..rows {
196            for c in 0..cols {
197                trees.push(TreeSlot {
198                    cell: (y0 + r * sy) * n + x0 + c * sx,
199                    ..Default::default()
200                });
201            }
202        }
203        if let Some(o) = self.orchard_mut(e) {
204            o.trees = trees;
205        }
206        self.unstamp(e);
207        self.orchard_plant_work(jobs, e);
208    }
209
210    /// `FUN_1400fa610`.
211    fn orchard_plant_work(&mut self, jobs: &mut Jobs, e: usize) {
212        self.orchard_refresh(e);
213        let count = self.orchard(e).map_or(0, |o| o.trees.len());
214        let tree = self.orchard(e).and_then(|o| o.tree);
215        let profession = self
216            .workforce
217            .workplaces
218            .get(&e)
219            .and_then(|w| w.profession)
220            .unwrap_or(self.professions.laborer);
221        let work = Work::new(
222            e,
223            self.rect_cell(e),
224            profession,
225            WorkKind::PlantOrchard(AreaWork {
226                claims: BTreeMap::new(),
227                need: vec![false; count],
228                crop: tree,
229            }),
230        );
231        self.set_work(jobs, e, work, 1, false);
232    }
233
234    /// State 9 update (`FUN_1400518f0`): every tree standing → tend (10).
235    pub(crate) fn orchard_update9(&mut self, jobs: &mut Jobs, e: usize) {
236        let total = self.orchard(e).map_or(0, |o| o.trees.len());
237        if self.orchard_count(e, 1) == total {
238            self.remove_work(jobs, e);
239            self.machine(e).request(10);
240        }
241    }
242
243    /// Message 5: clear the orchard (0x14).
244    ///
245    /// Original: `FUN_140051d40`.
246    pub fn orchard_clear_now(&mut self, e: usize) {
247        self.machine(e).request(0x14);
248    }
249
250    /// Message 4: harvest now; nothing is picked yet this harvest.
251    ///
252    /// Original: `FUN_140051260`.
253    pub fn orchard_harvest_now(&mut self, e: usize) {
254        self.machine(e).request(0xb);
255        let year = self.year;
256        if let Some(o) = self.orchard_mut(e) {
257            for t in &mut o.trees {
258                t.picked = false;
259            }
260            o.year = year;
261        }
262    }
263
264    /// `FUN_140051fa0`: the mean ripeness of the standing trees (at most 1 each) over all.
265    fn orchard_ripeness(&self, e: usize) -> f32 {
266        self.orchard(e)
267            .map_or(0.0, |o| ripeness(o, &self.cells.objects, self.fruits))
268    }
269
270    /// `FUN_140051ee0`: switched on and not yet harvested this year, harvest when warm in months
271    /// 7–11 with any fruit, or when all is ripe (> 0.99).
272    fn orchard_harvest_due(&mut self, e: usize) -> bool {
273        let Some(o) = self.orchard(e) else {
274            return false;
275        };
276        if !o.enabled || self.year == o.year {
277            return false;
278        }
279        let month = (self.calendar / 3600.0 * 12.0) as i32;
280        let ripe = self.orchard_ripeness(e);
281        let autumn = self.temperature >= 33.0 && month >= 0 && ((month - 7) as u32) < 5;
282        if (autumn && 0.0 < ripe) || 0.99 < ripe {
283            self.orchard_harvest_now(e);
284            return true;
285        }
286        false
287    }
288
289    /// State 10 enter (`FUN_140051790`): lost trees → replant (12); else the tending work.
290    pub(crate) fn orchard_enter10(&mut self, jobs: &mut Jobs, e: usize) {
291        if self.orchard_count(e, 2) > 0 {
292            self.machine(e).request(0xc);
293            return;
294        }
295        self.orchard_tend_work(jobs, e);
296    }
297
298    fn orchard_tend_work(&mut self, jobs: &mut Jobs, e: usize) {
299        let profession = self
300            .workforce
301            .workplaces
302            .get(&e)
303            .and_then(|w| w.profession)
304            .unwrap_or(self.professions.laborer);
305        let work = Work::new(
306            e,
307            self.rect_cell(e),
308            profession,
309            WorkKind::TendArea(crate::production::herbalist::HerbalistWork::default()),
310        );
311        self.set_work(jobs, e, work, 1, false);
312    }
313
314    /// Exits of 10 and 12 (`FUN_140051500`, `FUN_140051310`): the work goes.
315    pub(crate) fn orchard_exit_work(&mut self, jobs: &mut Jobs, e: usize) {
316        let id = self.sites[&e].work_id;
317        if id >= 0 && jobs.works.contains_key(&id) {
318            self.remove_work(jobs, e);
319        }
320    }
321
322    /// State 10 update (`FUN_140051560`): a harvest, or replanting lost trees; tending runs
323    /// in months 1–8 only.
324    pub(crate) fn orchard_update10(&mut self, jobs: &mut Jobs, e: usize) {
325        self.orchard_refresh(e);
326        if self.orchard_harvest_due(e) {
327            return;
328        }
329        if self.orchard_count(e, 2) > 0 {
330            self.machine(e).request(0xc);
331        }
332        let month = (self.calendar / 3600.0 * 12.0) as i32;
333        let season = month >= 0 && ((month - 1) as u32) < 0xb && 2 < (month - 9) as u32;
334        let id = self.sites[&e].work_id;
335        let registered = id >= 0 && jobs.works.contains_key(&id);
336        if registered && !season {
337            self.remove_work(jobs, e);
338        } else if !registered && season {
339            self.orchard_tend_work(jobs, e);
340        }
341    }
342
343    /// State 12 enter (`FUN_140051440`): PlantOrchard again.
344    pub(crate) fn orchard_enter12(&mut self, jobs: &mut Jobs, e: usize) {
345        self.orchard_plant_work(jobs, e);
346    }
347
348    /// State 12 update (`FUN_140051370`): a harvest, or back to tending once no tree is lost.
349    pub(crate) fn orchard_update12(&mut self, e: usize) {
350        self.orchard_refresh(e);
351        if self.orchard_harvest_due(e) {
352            return;
353        }
354        if self.orchard_count(e, 2) == 0 {
355            self.machine(e).request(10);
356        }
357    }
358
359    /// State 11 enter (`FUN_140051160`): the picking work; the orchard's cells remember its
360    /// tree (`FUN_1400a4c40`, cell `+0x10`).
361    pub(crate) fn orchard_enter11(&mut self, jobs: &mut Jobs, e: usize) {
362        let need: Vec<bool> = self
363            .orchard(e)
364            .map(|o| o.trees.iter().map(|t| t.entity.is_some()).collect())
365            .unwrap_or_default();
366        let profession = self
367            .workforce
368            .workplaces
369            .get(&e)
370            .and_then(|w| w.profession)
371            .unwrap_or(self.professions.laborer);
372        let work = Work::new(
373            e,
374            self.rect_cell(e),
375            profession,
376            WorkKind::PickFruit(AreaWork {
377                claims: BTreeMap::new(),
378                need,
379                crop: None,
380            }),
381        );
382        self.set_work(jobs, e, work, 1, false);
383        let tree = self.orchard(e).and_then(|o| o.tree);
384        let n = self.size as i32;
385        let (x0, y0) = self.placed[e].cell;
386        let (w, h) = self.placed[e].footprint;
387        for y in y0..y0 + h {
388            for x in x0..x0 + w {
389                self.cells.previous_template[(y * n + x) as usize] = tree;
390            }
391        }
392    }
393
394    /// State 11 update (`FUN_140050fb0`): every standing tree picked → tend (10).
395    pub(crate) fn orchard_update11(&mut self, jobs: &mut Jobs, e: usize) {
396        let Some(o) = self.orchard(e) else { return };
397        let standing = o.trees.iter().filter(|t| t.state == 1).count();
398        let picked = o.trees.iter().filter(|t| t.picked).count();
399        if standing <= picked {
400            self.remove_work(jobs, e);
401            self.machine(e).request(10);
402        }
403    }
404
405    /// State 0x14 enter (`FUN_140050e40`): the trees are cleared (a ClearArea work with the
406    /// ClearArea description's filter, `FUN_1400ebe30(.., 1, 0, 1)`, categories `0x401`).
407    pub(crate) fn orchard_enter14(&mut self, jobs: &mut Jobs, e: usize) {
408        let id = self.sites[&e].work_id;
409        if id >= 0 && jobs.works.contains_key(&id) {
410            self.remove_work(jobs, e);
411        }
412        let filter = self.site_templates[self.sites[&e].template]
413            .clear
414            .as_ref()
415            .and_then(|c| c.reference.clone());
416        let work = Work::new(
417            e,
418            self.rect_cell(e),
419            self.professions.laborer,
420            WorkKind::Clear(crate::work::construction::ClearWork {
421                filter,
422                clear_resources: true,
423                clear_cells: false,
424                clear_piles: true,
425                ..Default::default()
426            }),
427        );
428        self.set_work(jobs, e, work, 0x401, false);
429    }
430
431    /// State 0x14 exit (`FUN_140050d40`).
432    pub(crate) fn orchard_exit14(&mut self, jobs: &mut Jobs, e: usize) {
433        self.remove_work(jobs, e);
434    }
435
436    /// State 0x14 update (`FUN_140050d90`): cleared → plant again (9).
437    pub(crate) fn orchard_update14(&mut self, e: usize) {
438        if self.is_clear(e) {
439            self.machine(e).request(9);
440        }
441    }
442
443    /// OrchardComponent's message slot. 0: slot `index` was planted (a tree with fruit stands, else
444    /// it is lost); 1: the tree was picked (its fruit starts again); 2: a tending visit ripens its
445    /// fruit by `ripening time × 300 s × tend`.
446    ///
447    /// Original: `FUN_1400522f0`.
448    pub fn orchard_message(&mut self, e: usize, message: i32, index: i32) {
449        let Some(slot) = self
450            .orchard(e)
451            .and_then(|o| o.trees.get(index as usize))
452            .copied()
453        else {
454            return;
455        };
456        let here = self.cells.objects[slot.cell as usize];
457        match message {
458            0 => {
459                let fruit = here.is_some_and(|x| {
460                    let t = &self.templates[self.placed[x].template];
461                    t.natural.is_some() && t.fruit.is_some()
462                });
463                if let Some(t) = self
464                    .orchard_mut(e)
465                    .and_then(|o| o.trees.get_mut(index as usize))
466                {
467                    if fruit {
468                        t.state = 1;
469                        t.entity = here;
470                    } else {
471                        t.state = 2;
472                    }
473                }
474            }
475            1 => {
476                self.orchard_refresh(e);
477                let Some(t) = self
478                    .orchard_mut(e)
479                    .and_then(|o| o.trees.get_mut(index as usize))
480                else {
481                    return;
482                };
483                t.picked = true;
484                if let Some(x) = t.entity {
485                    let f = self.fruits.entry(x).or_default();
486                    f.ripeness = 0.0;
487                    f.picked = true;
488                }
489            }
490            2 => {
491                self.orchard_refresh(e);
492                let Some(x) = here else { return };
493                let Some(fd) = self.templates[self.placed[x].template].fruit else {
494                    return;
495                };
496                let tend = self.orchard_desc(e).tend;
497                let dt = ticks(fd.months * 300.0 * tend);
498                let growth = self.placed[x].growth;
499                let (calendar, t) = (self.calendar, self.temperature);
500                ripen(
501                    self.fruits.entry(x).or_default(),
502                    &fd,
503                    growth,
504                    dt,
505                    calendar,
506                    t,
507                );
508            }
509            _ => {}
510        }
511    }
512
513    /// The orchard's buttons: 0 plant now (message 3, state 2), 1 harvest now (message 4, states 10
514    /// and 12), 2 tending on/off, 3 clear (message 5, states 9–12).
515    ///
516    /// Original: `FUN_140052500`.
517    pub fn orchard_action(&mut self, e: usize, action: i32) {
518        let Some(state) = self.sites.get(&e).map(|s| s.machine.state) else {
519            return;
520        };
521        match action {
522            0 if state == 2 => self.machine(e).request(9),
523            1 if state == 0xa || state == 0xc => self.orchard_harvest_now(e),
524            2 => {
525                let on = match self.orchard_mut(e) {
526                    Some(o) => {
527                        o.enabled = !o.enabled;
528                        o.enabled
529                    }
530                    None => return,
531                };
532                self.enable_workplace(e, on);
533            }
534            3 if matches!(state, 9..=0xc) => self.orchard_clear_now(e),
535            _ => {}
536        }
537    }
538
539    // +0x58
540    /// The orchard's tree list (as CropUI's slot): choosing the first tree switches tending on.
541    pub fn set_orchard_tree(&mut self, e: usize, tree: Option<usize>) {
542        let Some(old) = self.orchard(e).map(|o| o.tree) else {
543            return;
544        };
545        if old.is_none() && tree.is_some() {
546            self.orchard_action(e, 2);
547        }
548        if let Some(o) = self.orchard_mut(e) {
549            o.tree = tree;
550        }
551    }
552
553    /// PlantOrchard slot `+0x20` (`FUN_1400f9fc0`): the first empty, unclaimed slot next to a
554    /// reachable cell gets a tree.
555    pub(crate) fn plant_orchard_try(&mut self, work: &mut Work, c: usize) -> u8 {
556        let e = work.entity;
557        self.orchard_refresh(e);
558        let WorkKind::PlantOrchard(pa) = &work.kind else {
559            return 0;
560        };
561        if self.work_max(e) < pa.claims.len() as i32 {
562            return 1;
563        }
564        let Some(o) = self.orchard(e).cloned() else {
565            return 0;
566        };
567        let from = self.citizen_cell(c);
568        for (i, slot) in o.trees.iter().enumerate() {
569            let WorkKind::PlantOrchard(pa) = &work.kind else {
570                return 0;
571            };
572            if pa.need.get(i).copied().unwrap_or(true) || pa.claims.contains_key(&(i as i32)) {
573                continue;
574            }
575            if slot.entity.is_some() {
576                continue;
577            }
578            let Some(nb) = nearest_neighbour(self.size, &self.cells.types, slot.cell, from, 0xe0f)
579            else {
580                continue;
581            };
582            if !self.reachable(from, nb) {
583                continue;
584            }
585            let Some(tree) = pa.crop else { return 1 };
586            let point = self.ground_at(nb);
587            let message = Message {
588                component: Component::Orchard,
589                message: 0,
590                entity: Some(e),
591                a: i as i32,
592                b: 0,
593            };
594            self.plant_sequence(
595                c,
596                work,
597                tree,
598                point,
599                slot.cell,
600                Some(e),
601                Some(message),
602                i as i32,
603            );
604            self.took_job(c, work);
605            if let WorkKind::PlantOrchard(pa) = &mut work.kind {
606                pa.claims.insert(i as i32, c);
607                pa.need[i] = true;
608            }
609            return 2;
610        }
611        1
612    }
613
614    /// PlantOrchard refresh (`FUN_1400f9cc0`): slots whose worker lost the task are free.
615    pub(crate) fn plant_orchard_refresh(&mut self, work: &mut Work) {
616        let id = work.id;
617        let WorkKind::PlantOrchard(pa) = &mut work.kind else {
618            return;
619        };
620        let lost: Vec<i32> = pa
621            .claims
622            .iter()
623            .filter(|(_, &c)| self.characters[c].stack.holds_task(id).is_none())
624            .map(|(&i, _)| i)
625            .collect();
626        for i in lost {
627            pa.claims.remove(&i);
628            if let Some(n) = pa.need.get_mut(i as usize) {
629                *n = false;
630            }
631        }
632    }
633
634    /// PlantOrchard complete and fail (`FUN_1400f9f60`, `FUN_1400f9ef0`): the slot is free.
635    pub(crate) fn plant_orchard_done(work: &mut Work, index: i32) {
636        if let WorkKind::PlantOrchard(pa) = &mut work.kind {
637            pa.claims.remove(&index);
638            if let Some(n) = pa.need.get_mut(index as usize) {
639                *n = false;
640            }
641        }
642    }
643
644    /// TendArea slot `+0x20` (`FUN_1400fa960`) for an orchard: a random tree.
645    pub(crate) fn tend_orchard_try(&mut self, work: &mut Work, c: usize) -> u8 {
646        let e = work.entity;
647        let WorkKind::TendArea(t) = &work.kind else {
648            return 0;
649        };
650        if self.work_max(e) < t.workers.len() as i32 {
651            return 1;
652        }
653        let from = self.citizen_cell(c);
654        let count = self.orchard(e).map_or(0, |o| o.trees.len()) as u32;
655        let index = self.rng.rand_below(count) as i32;
656        let Some(cell) = self
657            .orchard(e)
658            .and_then(|o| o.trees.get(index as usize))
659            .map(|t| t.cell)
660        else {
661            return 0;
662        };
663        let Some(nb) = nearest_neighbour(self.size, &self.cells.types, cell, from, WALKABLE) else {
664            return 0;
665        };
666        if !self.reachable(from, nb) {
667            return 0;
668        }
669        let at = self.ground_at(cell);
670        let stand = self.ground_at(nb);
671        let message = Message {
672            component: Component::Orchard,
673            message: 2,
674            entity: Some(e),
675            a: index,
676            b: 0,
677        };
678        self.tend_sequence(c, work, stand, at, message);
679        self.took_job(c, work);
680        if let WorkKind::TendArea(t) = &mut work.kind {
681            t.workers.insert(c);
682        }
683        2
684    }
685
686    /// The picking work's slot `+0x20` (`FUN_1400f2d90`) after the AutoPickup offers.
687    pub(crate) fn pick_fruit_try(&mut self, work: &mut Work, c: usize) -> u8 {
688        let e = work.entity;
689        let WorkKind::PickFruit(pf) = &work.kind else {
690            return 0;
691        };
692        if self.work_max(e) < pf.claims.len() as i32 {
693            return 1;
694        }
695        let group = self.orchard_desc(e).group;
696        let (count, limit) = self.stock[group.clamp(0, 20) as usize];
697        self.set_icon(e, crate::work::professions::icon::LIMIT, limit <= count);
698        if limit <= count {
699            return 1;
700        }
701        let Some(o) = self.orchard(e).cloned() else {
702            return 0;
703        };
704        let from = self.citizen_cell(c);
705        for (i, slot) in o.trees.iter().enumerate() {
706            let WorkKind::PickFruit(pf) = &work.kind else {
707                return 0;
708            };
709            if !pf.need[i] || pf.claims.contains_key(&(i as i32)) {
710                continue;
711            }
712            let Some(tree) = self.cells.objects[slot.cell as usize] else {
713                if let WorkKind::PickFruit(pf) = &mut work.kind {
714                    pf.need[i] = false;
715                }
716                continue;
717            };
718            let Some(nb) = nearest_neighbour(self.size, &self.cells.types, slot.cell, from, 0xe0f)
719            else {
720                continue;
721            };
722            if !self.reachable(from, nb) {
723                continue;
724            }
725            if self.templates[self.placed[tree].template].natural.is_none() {
726                if let WorkKind::PickFruit(pf) = &mut work.kind {
727                    pf.need[i] = false;
728                }
729                continue;
730            }
731            if self.claimed.contains(&tree) {
732                continue;
733            }
734            let fd = self.templates[self.placed[tree].template]
735                .fruit
736                .unwrap_or_default();
737            let amount = self.fruits.get(&tree).map_or(0.0, |f| f.ripeness);
738            let point = self.ground_at(nb);
739            self.pick_sequence(c, work, e, point, fd.pile, i % 2 == 0, amount, nb, i as i32);
740            self.took_job(c, work);
741            if let WorkKind::PickFruit(pf) = &mut work.kind {
742                pf.claims.insert(i as i32, c);
743                pf.need[i] = false;
744            }
745            return 2;
746        }
747        1
748    }
749
750    /// `FUN_1400c0c80`: walk to the tree, work 1 s, leave the fruit (scaled by its ripeness,
751    /// every other tree only), tell the orchard, report.
752    #[allow(clippy::too_many_arguments)]
753    fn pick_sequence(
754        &mut self,
755        c: usize,
756        work: &mut Work,
757        orchard: usize,
758        point: [f32; 3],
759        fruit: Option<usize>,
760        leave: bool,
761        amount: f32,
762        cell: i32,
763        index: i32,
764    ) {
765        self.clear_citizen(c, Some(work));
766        self.on_empty_think(c);
767        self.push(
768            c,
769            Action::CompleteTask {
770                work: work.id,
771                param: index,
772            },
773        );
774        self.push(
775            c,
776            Action::SendMessage(Message {
777                component: Component::Orchard,
778                message: 1,
779                entity: Some(orchard),
780                a: index,
781                b: 0,
782            }),
783        );
784        if leave {
785            if let Some(t) = fruit {
786                let batch = self.states[c].carried.capacity;
787                self.push(
788                    c,
789                    Action::CreateRawMaterial {
790                        template: t,
791                        cell,
792                        batch,
793                        workplace: self.states[c].workplace,
794                        factor: amount,
795                    },
796                );
797            }
798        }
799        self.push(c, self.perform(c, -1000.0, 8, 1.0, true));
800        let walk = MoveTo::new(point, 1, 0, WALKABLE, 0, -1.0, self.rng);
801        self.push(c, Action::move_to(walk));
802    }
803
804    /// Picking complete and fail (`FUN_1400f2d50`, `FUN_1400f2ce0`).
805    pub(crate) fn pick_done(work: &mut Work, index: i32, failed: bool) {
806        if let WorkKind::PickFruit(pf) = &mut work.kind {
807            pf.claims.remove(&index);
808            if failed {
809                if let Some(n) = pf.need.get_mut(index as usize) {
810                    *n = true;
811                }
812            }
813        }
814    }
815}
816
817/// `Sim::orchard_ripeness` of an orchard: with no trees, 1.
818pub fn ripeness(
819    o: &Orchard,
820    objects: &[Option<usize>],
821    fruits: &std::collections::BTreeMap<usize, Fruit>,
822) -> f32 {
823    if o.trees.is_empty() {
824        return 1.0;
825    }
826    let sum: f32 = o
827        .trees
828        .iter()
829        .filter(|t| t.state == 1 && t.entity.is_some())
830        .filter(|t| objects[t.cell as usize] == t.entity)
831        .map(|t| {
832            let x = t.entity.unwrap();
833            fruits.get(&x).map_or(0.0, |f| f.ripeness.min(1.0))
834        })
835        .sum();
836    sum / o.trees.len() as f32
837}