Skip to main content

banished_game/town/
piles.rs

1//! Loose piles of goods on the ground (`RawMaterialComponent`): making, joining, taking and ageing them.
2
3use super::*;
4
5impl Sim<'_, '_> {
6    /// `FUN_1400ccd90`: `count` of `material` (already out of citizen `c`'s storage) as a
7    /// loose pile on the nearest flat free ground around the citizen, turned `RandTol(2) ×
8    /// 90°`; with no such cell within six, the goods are lost.
9    pub(crate) fn drop_loose_pile(&mut self, c: usize, material: usize, count: i32) {
10        let Some(at) = self.free_flat_cell(self.citizen_cell(c)) else {
11            return;
12        };
13        // The material's own template (a material is its pile's template).
14        let reference = &self.materials[material].reference;
15        let Some(template) = self
16            .templates
17            .iter()
18            .position(|t| &t.reference == reference && t.pile_material == Some(material))
19            .or_else(|| {
20                self.templates
21                    .iter()
22                    .position(|t| t.pile_material == Some(material))
23            })
24        else {
25            return;
26        };
27        let yaw = self.rng.rand_tol(2) as f32 * 90.0;
28        let n = self.size as i32;
29        self.spawn_pile(template, (at % n, at / n), count, yaw, None);
30        *self.models_dirty = true;
31    }
32
33    /// A RawMaterialComponent pile entity (`FUN_1400c8bf0` of a pile template, `FUN_14012ec30`
34    /// places it, turned `yaw`): stamped on its cell and activated with the next
35    /// [`Sim::activate_piles`].
36    pub(crate) fn spawn_pile(
37        &mut self,
38        pile_t: usize,
39        cell: (i32, i32),
40        amount: i32,
41        yaw: f32,
42        priority: Option<i32>,
43    ) -> usize {
44        let Some(material) = self.templates[pile_t].pile_material else {
45            return usize::MAX;
46        };
47        let pt = &self.templates[pile_t];
48        let n = self.size as i32;
49        let index = cell.1 * n + cell.0;
50        let id = self.placed.len();
51        let mut yaw = yaw as i32;
52        while yaw < 0 {
53            yaw += 360;
54        }
55        let (variant, mesh) = pt.model.as_ref().map_or((0, 0), |m| {
56            (m.variant.max(0) as usize, m.mesh.max(0) as usize)
57        });
58        self.placed.push(Placed {
59            template: pile_t,
60            bucket: pt.bucket,
61            cell,
62            footprint: (1, 1),
63            position: self.ground_at(index),
64            yaw: yaw as f32,
65            variant,
66            mesh,
67            growth: 0.0,
68            growth_state: None,
69            spawn_state: None,
70            alive: true,
71            removal_requested: false,
72            animation: None,
73            scale: [1.0; 4],
74        });
75        // The pile's MapComponent stamps its cell on activation (`MapDescription +0x30`).
76        self.stamp(id);
77        self.piles.insert(
78            id,
79            Pile {
80                material,
81                count: amount,
82                age: 0,
83                work: -1,
84                mask: pile_mask(self.materials[material].flags),
85                priority,
86                auto_pickup: None,
87                state: 0,
88                claimer: None,
89            },
90        );
91        self.new_piles.push(id);
92        id
93    }
94
95    /// RawMaterialComponent activation: register each new pile's Pickup work.
96    ///
97    /// Original: `FUN_14012e6a0`.
98    pub fn activate_piles(&mut self, jobs: &mut Jobs) {
99        for id in std::mem::take(self.new_piles) {
100            let Some(pile) = self.piles.get(&id) else {
101                continue;
102            };
103            let (mask, priority, auto_pickup) = (pile.mask, pile.priority, pile.auto_pickup);
104            // RawMaterialComponent's setup (`FUN_14012e790`): its material becomes known.
105            self.discover(pile.material);
106            let cell = self.rect_cell(id);
107            let work = Work::new(
108                id,
109                cell,
110                self.professions.laborer,
111                WorkKind::Pickup(PickupWork),
112            );
113            let w = jobs.add(work, mask, priority);
114            self.piles.get_mut(&id).unwrap().work = w;
115            if let Some(wp) = auto_pickup {
116                self.auto_pickup_add(wp, w);
117            }
118        }
119    }
120
121    /// Destroy a pile (picked up or decayed): its entity, its cell and its work.
122    pub fn destroy_pile(&mut self, jobs: &mut Jobs, id: usize) {
123        let Some(pile) = self.piles.remove(&id) else {
124            return;
125        };
126        if pile.work >= 0 {
127            jobs.remove(pile.work, self);
128        }
129        let p = &mut self.placed[id];
130        p.alive = false;
131        let (x, y) = p.cell;
132        let i = (y as usize) * self.size + x as usize;
133        if self.cells.objects[i] == Some(id) {
134            self.cells.objects[i] = None;
135            self.cells
136                .set_type(i, self.terrain.natural_cell_type(self.size, i));
137        }
138        *self.models_dirty = true;
139    }
140
141    /// `FUN_14010ae20`: every pile on the footprint (by rows) that the site's storage can take
142    /// goes into it.
143    pub(crate) fn absorb_piles(&mut self, jobs: &mut Jobs, e: usize) {
144        let Some(storage) = self.sites.get(&e).map(|s| s.storage) else {
145            return;
146        };
147        let n = self.size as i32;
148        let (x, y) = self.placed[e].cell;
149        let (w, h) = self.placed[e].footprint;
150        for cy in y..y + h {
151            for cx in x..x + w {
152                let Some(o) = self.cells.objects[(cy * n + cx) as usize] else {
153                    continue;
154                };
155                let Some(pile) = self.piles.get(&o) else {
156                    continue;
157                };
158                let (material, count) = (pile.material, pile.count);
159                if self.storages[storage].fits(self.materials, material, count) {
160                    self.storages[storage].add(material, count);
161                    self.destroy_pile(jobs, o);
162                }
163            }
164        }
165    }
166
167    /// PickupRawMaterial start: the pile goes into the carried storage.
168    ///
169    /// Original: `FUN_1400ceda0`.
170    pub fn take_pile(&mut self, jobs: &mut Jobs, c: usize, id: usize) {
171        let Some(pile) = self.piles.get(&id) else {
172            return;
173        };
174        let (material, count) = (pile.material, pile.count);
175        self.states[c].carried.add(material, count);
176        self.destroy_pile(jobs, id);
177    }
178
179    /// The scheduled update of the piles visited this iteration: piles older than an hour
180    /// disappear.
181    ///
182    /// Original: `FUN_14012e600`.
183    pub fn age_piles(&mut self, jobs: &mut Jobs, visits: &[(usize, i64)]) {
184        let old: Vec<usize> = visits
185            .iter()
186            .filter_map(|&(id, elapsed)| {
187                let p = self.piles.get_mut(&id)?;
188                p.age += elapsed;
189                (p.age > ticks(3600.0)).then_some(id)
190            })
191            .collect();
192        for id in old {
193            self.destroy_pile(jobs, id);
194        }
195    }
196}