Skip to main content

banished_game/town/
storage.rs

1//! Taking goods to storage: picking up piles, finding a storage, dropping off, and emptying a storage (Pickup, ClearStorage).
2
3use super::*;
4
5impl Sim<'_, '_> {
6    /// `FUN_1400f82f0`: a claimer that lost the task frees the pile.
7    pub(super) fn pickup_refresh(&mut self, work: &mut Work) {
8        let id = work.id;
9        if let Some(p) = self.piles.get_mut(&work.entity) {
10            if let Some(c) = p.claimer {
11                if self.characters[c].stack.holds_task(id).is_none() {
12                    p.claimer = None;
13                    p.state = 0;
14                }
15            }
16        }
17    }
18
19    /// `FUN_140092890`: does any registered storage take `count` of `material`?
20    pub(super) fn any_storage_fits(&self, material: usize, count: i32) -> bool {
21        self.storage_sites.iter().any(|s| {
22            let st = &self.storages[s.storage];
23            st.accepts & self.materials[material].flags != 0
24                && st.fits(self.materials, material, count)
25        })
26    }
27
28    /// `FUN_1400f83d0`: Pickup's slot `+0x20`. Takes this pile and every unclaimed pile within
29    /// five cells that fits the same storage, nearest first, while they fit the carried
30    /// capacity.
31    pub(super) fn pickup_try(&mut self, work: &mut Work, c: usize, time: i64) -> u8 {
32        let e = work.entity;
33        let Some(pile) = self.piles.get(&e).cloned() else {
34            return 1;
35        };
36        if pile.state != 0 {
37            return 1;
38        }
39        let size = |m: usize, s: &Self| s.materials[m].size;
40        let carried = &self.states[c].carried;
41        let mut free = carried.capacity - carried.used(self.materials);
42        if free < size(pile.material, self) * pile.count {
43            self.drop_off(c);
44            return 2;
45        }
46        let from = self.citizen_cell(c);
47        let cell = work.cell;
48        if !self.any_storage_fits(pile.material, pile.count) {
49            return 1;
50        }
51        let Some(nb) = nearest_neighbour(self.size, &self.cells.types, cell, from, 0xe07) else {
52            return 1;
53        };
54        if !self.reachable(nb, from) {
55            return 1;
56        }
57        if !self.patience(work, c, nb, from, time) {
58            return 0;
59        }
60        let found = self.find_storage(pile.material, pile.count, true, nb);
61        let Some(found) = found else {
62            return 1;
63        };
64        let first_rank = self.rank_or_distance(from, cell);
65        let mut list: Vec<(i32, i32, i32, i32, usize)> = vec![(cell, nb, first_rank, work.id, e)];
66        let n = self.size as i32;
67        let (px, py) = (cell % n, cell / n);
68        for dy in -5..=5 {
69            for dx in -5..=5 {
70                if dx == 0 && dy == 0 {
71                    continue;
72                }
73                let (x, y) = (px + dx, py + dy);
74                if x < 0 || y < 0 || x >= n || y >= n {
75                    continue;
76                }
77                let k = y * n + x;
78                let Some(o) = self.entity_on(k) else { continue };
79                let Some(other) = self.piles.get(&o).cloned() else {
80                    continue;
81                };
82                if !self.storages[found.storage].fits(self.materials, other.material, 1) {
83                    continue;
84                }
85                let Some(nb2) = nearest_neighbour(self.size, &self.cells.types, k, from, 0xe07)
86                else {
87                    continue;
88                };
89                if !self.reachable(nb2, from) {
90                    continue;
91                }
92                if other.work < 0 || other.state != 0 {
93                    continue;
94                }
95                let r = self.rank_or_distance(from, k);
96                let id = other.work;
97                list.push((k, nb2, r, id, o));
98            }
99        }
100        crate::behaviour::ai::crt_qsort_by(&mut list, |a, b| a.2 - b.2);
101        let mut first = true;
102        for (k, nb2, _, id, o) in list {
103            let p = &self.piles[&o];
104            let w = size(p.material, self) * p.count;
105            if w > free {
106                continue;
107            }
108            free -= w;
109            let point = self.ground_at(nb2);
110            self.pickup_sequence(
111                c,
112                if first { Some(&mut *work) } else { None },
113                id,
114                o,
115                point,
116                k,
117            );
118            first = false;
119            let p = self.piles.get_mut(&o).unwrap();
120            p.state = 1;
121            p.claimer = Some(c);
122        }
123        self.took_job(c, work);
124        if first {
125            1
126        } else {
127            2
128        }
129    }
130
131    /// `FUN_140092a60` from a cell (`put` finds a storage that takes the units).
132    pub(crate) fn find_storage(
133        &mut self,
134        material: usize,
135        count: i32,
136        put: bool,
137        cell: i32,
138    ) -> Option<crate::work::stock::StorageMatch> {
139        let town = TownView {
140            size: self.size,
141            types: &self.cells.types,
142            regions: self.regions,
143            occupants: &self.cells.occupants,
144            objects: &self.cells.objects,
145            floors: &self.cells.floors,
146            entities: self.entities,
147            happiness: self.happiness,
148        };
149        let (size, terrain) = (self.size, self.terrain);
150        let floors = &self.cells.floors;
151        let ground = move |cell: i32| crate::town::floor_ground(floors, size, terrain, cell);
152        let mut geometry = StorageGeometry {
153            town: &town,
154            sectors: self.sectors,
155            ground: &ground,
156        };
157        find_material_storage(
158            self.storage_sites,
159            self.storages,
160            self.materials,
161            material,
162            count,
163            put,
164            cell,
165            None,
166            &mut geometry,
167            self.rng,
168        )
169    }
170
171    /// [`Sim::find_storage`] passing over `exclude`'s own storage.
172    pub(crate) fn find_storage_excluding(
173        &mut self,
174        material: usize,
175        count: i32,
176        put: bool,
177        cell: i32,
178        exclude: usize,
179    ) -> Option<crate::work::stock::StorageMatch> {
180        let town = TownView {
181            size: self.size,
182            types: &self.cells.types,
183            regions: self.regions,
184            occupants: &self.cells.occupants,
185            objects: &self.cells.objects,
186            floors: &self.cells.floors,
187            entities: self.entities,
188            happiness: self.happiness,
189        };
190        let (size, terrain) = (self.size, self.terrain);
191        let floors = &self.cells.floors;
192        let ground = move |cell: i32| crate::town::floor_ground(floors, size, terrain, cell);
193        let mut geometry = StorageGeometry {
194            town: &town,
195            sectors: self.sectors,
196            ground: &ground,
197        };
198        find_material_storage(
199            self.storage_sites,
200            self.storages,
201            self.materials,
202            material,
203            count,
204            put,
205            cell,
206            Some(exclude),
207            &mut geometry,
208            self.rng,
209        )
210    }
211
212    /// `FUN_1400c5fa0`: walk next to a pile, pick it up and report. The first pile's sequence
213    /// clears the stack (`current` is the work being tried).
214    pub(super) fn pickup_sequence(
215        &mut self,
216        c: usize,
217        current: Option<&mut Work>,
218        work: i32,
219        pile: usize,
220        point: [f32; 3],
221        pile_cell: i32,
222    ) {
223        if let Some(w) = current {
224            self.clear_citizen(c, Some(w));
225        }
226        let facing = crate::behaviour::character::work_facing(self.facing_centre(pile), point);
227        let carrying = self.states[c].carried.used(self.materials) > 0;
228        self.push(c, Action::ShowTool(-1));
229        self.on_empty_think(c);
230        self.push(c, self.perform(c, facing, 7, 0.75, false));
231        if !carrying {
232            self.push(c, Action::ShowTool(-2));
233        }
234        self.push(c, Action::PickupPile { entity: pile });
235        self.push(c, Action::CompleteTask { work, param: 0 });
236        self.push(c, self.perform(c, facing, 6, 0.75, false));
237        let mask = WALKABLE | crate::pathfind::bit(self.cells.types[pile_cell as usize]);
238        let m = MoveTo::new(
239            point,
240            if carrying { 4 } else { 1 },
241            0,
242            mask,
243            0,
244            -1.0,
245            self.rng,
246        );
247        self.push(c, Action::move_to(m));
248        if carrying {
249            self.push(c, Action::ShowTool(-2));
250        }
251    }
252
253    /// Carry everything to storages that take it: one drop-off per storage (nearest found per
254    /// material, reusing a storage already chosen), then a final drop-off of whatever is left.
255    ///
256    /// Original: `FUN_14011b330`.
257    pub fn drop_off(&mut self, c: usize) {
258        let from = self.citizen_cell(c);
259        let items: Vec<(usize, i32)> = self.states[c]
260            .carried
261            .items
262            .iter()
263            .filter(|i| i.count != 0)
264            .map(|i| (i.material, i.count))
265            .collect();
266        let mut chosen: Vec<(usize, [f32; 3], usize)> = Vec::new();
267        for (material, count) in items {
268            if chosen
269                .iter()
270                .any(|&(s, _, _)| self.storages[s].fits(self.materials, material, count))
271            {
272                continue;
273            }
274            if let Some(f) = self.find_storage(material, count, true, from) {
275                let entity = self.storage_sites[f.site].entity;
276                chosen.push((f.storage, f.target, entity));
277            }
278        }
279        if chosen.is_empty() {
280            return;
281        }
282        self.clear_citizen(c, None);
283        self.on_empty_think(c);
284        // `Sequences::DropOffAt(.., redirect, first)`: the final drop-off has no storage, so
285        // whatever is left goes on the ground.
286        self.push(
287            c,
288            Action::DropOff {
289                storage: usize::MAX,
290                material: usize::MAX,
291                count: -1,
292                redirect: true,
293            },
294        );
295        let mask = WALKABLE | crate::pathfind::bit(self.cells.types[from as usize]);
296        for (storage, target, entity) in chosen {
297            let facing =
298                crate::behaviour::character::work_facing(self.facing_centre(entity), target);
299            self.push(c, self.perform(c, facing, 7, 0.75, false));
300            self.push(c, Action::ShowTool(-2));
301            self.push(
302                c,
303                Action::DropOff {
304                    storage,
305                    material: usize::MAX,
306                    count: -1,
307                    redirect: false,
308                },
309            );
310            self.push(c, self.perform(c, facing, 6, 0.75, false));
311            let m = MoveTo::new(target, 4, 0, mask, 0, -1.0, self.rng);
312            self.push(c, Action::move_to(m));
313        }
314        let s = &mut self.states[c];
315        s.job = -1;
316        s.health_events |= 4;
317        s.supply_activity = 0;
318    }
319}
320
321impl Sim<'_, '_> {
322    /// The residence update's work: a house nobody lives in that still holds goods gets a
323    /// ClearStorage work (mask 1); an empty storage has it removed.
324    ///
325    /// Original: `FUN_14012ee40`.
326    pub fn residence_clear_storage(&mut self, jobs: &mut Jobs, visits: &[(usize, i64)]) {
327        for &(e, _) in visits {
328            if !self.homes.contains_key(&e) {
329                continue;
330            }
331            let Some(site) = self.sites.get(&e) else {
332                continue;
333            };
334            let storage = site.storage;
335            let registered = site.work_id >= 0 && jobs.works.contains_key(&site.work_id);
336            let residents: usize = self.homes[&e].families.iter().map(Vec::len).sum();
337            let used = self.storages[storage].used(self.materials);
338            if residents == 0 && used != 0 && !registered {
339                let work = Work::new(
340                    e,
341                    self.rect_cell(e),
342                    self.professions.laborer,
343                    WorkKind::ClearStorage(crate::production::herbalist::HerbalistWork::default()),
344                );
345                self.set_work(jobs, e, work, 1, false);
346            } else if registered && used == 0 {
347                self.remove_work(jobs, e);
348            }
349        }
350    }
351
352    /// ClearStorage's slot `+0x20` (`FUN_1400ec120`): the first item still unclaimed that a
353    /// town storage takes, as much as the hauler can carry, is carried there from the house's
354    /// first use point (a full hauler drops off first).
355    pub(crate) fn clear_storage_try(&mut self, work: &mut Work, c: usize, time: i64) -> u8 {
356        let e = work.entity;
357        let Some(storage) = self.sites.get(&e).map(|s| s.storage) else {
358            return 1;
359        };
360        let here = self.citizen_cell(c);
361        let items: Vec<(usize, i32)> = self.storages[storage]
362            .items
363            .iter()
364            .map(|i| (i.material, i.count - i.reserved))
365            .collect();
366        for (k, (material, left)) in items.into_iter().enumerate() {
367            if left <= 0 {
368                continue;
369            }
370            let mut amount = left;
371            let size = self.materials[material].size.max(1);
372            let carried = &self.states[c].carried;
373            let free = carried.capacity - carried.used(self.materials);
374            if free < size * amount {
375                amount = free / size;
376            }
377            if amount == 0 {
378                // The drop-off activity (`FUN_14011b330`).
379                self.drop_off(c);
380                return 2;
381            }
382            let Some(found) = self.find_storage_excluding(material, amount, true, work.cell, e)
383            else {
384                continue;
385            };
386            if !self.reachable(here, work.cell) {
387                return 1;
388            }
389            // The house has no StorageLocation: its first use point.
390            if self.use_point_count(e) == 0 {
391                return 1;
392            }
393            let (source, _) = self.use_point(e, 0);
394            let source_cell = self.cell_of(source);
395            if !self.patience(work, c, source_cell, here, time) {
396                return 0;
397            }
398            let target_entity = self.storage_sites[found.site].entity;
399            self.haul(
400                c,
401                work,
402                Haul {
403                    target_entity,
404                    target_storage: found.storage,
405                    target: found.target,
406                    source_storage: storage,
407                    source_entity: e,
408                    source,
409                    material,
410                    amount,
411                    param: (amount << 8) | k as i32,
412                    partial: false,
413                    complete_on_pickup: true,
414                    ..Default::default()
415                },
416            );
417            self.took_job(c, work);
418            if let WorkKind::ClearStorage(h) = &mut work.kind {
419                h.workers.insert(c);
420            }
421            return 2;
422        }
423        1
424    }
425}