Skip to main content

banished_game/work/
road.rs

1//! Roads: planning a road's cells, BuildRoad, CompleteRoad and taking a planned road back. See
2//! docs/ROADS.md.
3//!
4//! A planned cell gets its road kind at once and a BuildRoad work (a builder's, mask `0x2000`); the
5//! builder clears what lies on it, brings a stone for a stone road, works it four seconds and the
6//! cell becomes road. Clearing a road (a building's ClearArea or Remove Roads' marker) lays it back
7//! to ground the same way.
8//!
9//! Original: `FUN_1400a1e30`, `FUN_1400ea020`, `FUN_1400cad20`, `FUN_1400a1b10`, `FUN_1400a1bd0`.
10// vtable 0x140167430; vtable 0x140165f80; +0x2a
11
12use crate::behaviour::character::{Action, MoveTo};
13use crate::pathfind::WALKABLE;
14use crate::terrain::CellType;
15use crate::town::Sim;
16use crate::work::jobs::{Jobs, Work, WorkKind};
17
18/// `0x2000`: BuildRoad's registration mask (category 13, construction).
19const MASK: u32 = 0x2000;
20// +0x2a; +0x2b
21/// No road, and while a road is being built / once it is.
22pub const NONE: u8 = 0xff;
23/// The bit of a road kind that says the road is being built.
24pub const BUILDING: u8 = 0x80;
25/// The cell type a cleared road goes back to.
26pub const GROUND: u8 = 2;
27
28// +0x48; +0x50; +0x58; +0x5c; +0x60..+0x68; +0x2b
29/// BuildRoad's state: its builder, the material a cell takes (stone roads), the cell, the cell type
30/// it becomes, the cell's road kind, byte and type before (restored when the plan is taken back).
31#[derive(Debug, Clone, Default, PartialEq, Eq)]
32pub struct RoadWork {
33    /// The builder, if any.
34    pub builder: Option<usize>,
35    /// The raw material the cell takes (stone roads).
36    pub material: Option<usize>,
37    /// The cell.
38    pub cell: i32,
39    /// The cell type it becomes.
40    pub ty: u8,
41    /// The cell's road kind before.
42    pub old_kind: u8,
43    /// The cell's built byte before.
44    pub old_built: u8,
45    /// The cell's type before.
46    pub old_type: u8,
47}
48
49impl Sim<'_, '_> {
50    // +0x28; +0x2b
51    /// Plan a better road (a lower kind) than the cell has: a plan already there is taken back; a
52    /// cell that is neither road (types 0, 1) nor planned counts one more; a `BuildRoad` work is
53    /// registered, and the cell keeps the new kind while it is built.
54    ///
55    /// Original: `FUN_1400a1e30(cell, kind, type, material)`.
56    pub fn plan_road(
57        &mut self,
58        jobs: &mut Jobs,
59        cell: i32,
60        kind: u8,
61        ty: u8,
62        material: Option<usize>,
63    ) {
64        let i = cell as usize;
65        if self.cells.road_kind[i] <= kind {
66            return;
67        }
68        if self.pending_roads.contains_key(&cell) {
69            self.unplan_road(jobs, cell);
70        }
71        let t = self.cells.types[i].0;
72        if t != 0
73            && t != 1
74            && self.cells.road_kind[i] == NONE
75            && !self.pending_roads.contains_key(&cell)
76        {
77            self.cells.flags[i] = self.cells.flags[i].wrapping_add(1);
78        }
79        let road = RoadWork {
80            builder: None,
81            material,
82            cell,
83            ty,
84            old_kind: self.cells.road_kind[i],
85            old_built: self.cells.road_built[i],
86            old_type: t,
87        };
88        let work = Work::new(
89            usize::MAX,
90            cell,
91            self.professions.builder,
92            WorkKind::Road(road),
93        );
94        let id = jobs.add(work, MASK, None);
95        self.pending_roads.insert(cell, id);
96        self.cells.road_kind[i] = kind;
97        self.cells.road_built[i] = BUILDING;
98        self.cells.road_revision += 1;
99    }
100
101    /// A planned road's work goes and the cell gets back what it had (true); a cell without a road
102    /// kind becomes ground at once (false); a road built stays (true, for the caller to clear).
103    ///
104    /// Original: `FUN_1400a1bd0`.
105    pub fn unplan_road(&mut self, jobs: &mut Jobs, cell: i32) -> bool {
106        let i = cell as usize;
107        let mut restored = None;
108        if let Some(id) = self.pending_roads.remove(&cell) {
109            if let Some(WorkKind::Road(r)) = jobs.works.get(&id).map(|w| w.kind.clone()) {
110                restored = Some((r.old_kind, r.old_built, r.old_type));
111            }
112            jobs.remove(id, self);
113        }
114        self.cells.road_revision += 1;
115        if self.cells.road_kind[i] == NONE {
116            self.set_cell_type(cell, GROUND);
117            return false;
118        }
119        if let Some((kind, built, ty)) = restored {
120            if kind == NONE {
121                self.cells.flags[i] = self.cells.flags[i].wrapping_sub(1);
122            }
123            self.cells.road_kind[i] = kind;
124            self.cells.road_built[i] = built;
125            self.set_cell_type(cell, ty);
126            if kind == NONE {
127                return false;
128            }
129            *self.models_dirty = true;
130        }
131        true
132    }
133
134    /// `FUN_1400a47c0`: the cell's type (the path grid follows it).
135    fn set_cell_type(&mut self, cell: i32, ty: u8) {
136        self.cells.set_type(cell as usize, CellType(ty));
137    }
138
139    // +0x28
140    /// The start of `CompleteRoad`: to ground (type 2) the cell
141    /// loses its road (one less, no kind); else it becomes the road type. Either way it leaves the
142    /// planned set. A road flattens the terrain at its cell's corners; the road decals follow
143    /// `road_revision`.
144    ///
145    /// Original: `FUN_1400a1b10(cell, kind byte, type)`; `FUN_1400a35b0`, `FUN_1400b4840`.
146    pub fn complete_road(&mut self, cell: i32, built: u8, ty: u8) {
147        let i = cell as usize;
148        if ty == GROUND {
149            self.cells.flags[i] = self.cells.flags[i].wrapping_sub(1);
150            self.cells.road_kind[i] = NONE;
151            self.cells.road_built[i] = 0;
152        } else {
153            self.cells.road_built[i] = built;
154            let n = self.size as i32;
155            let (x, y) = (cell % n, cell / n);
156            self.flatten_requests.push([x, y, x + 1, y + 1]);
157        }
158        self.set_cell_type(cell, ty);
159        self.pending_roads.remove(&cell);
160        self.cells.road_revision += 1;
161        *self.models_dirty = true;
162    }
163
164    /// BuildRoad's slot `+0x20` (`FUN_1400e9660`): one builder at a time, the cell reachable.
165    /// A pile on it is picked up first (its Pickup work offered), a resource not being removed
166    /// is removed from the nearest free neighbour; else the cell is built: a dirt road at once,
167    /// a stone road with a stone carried or fetched (dropping off first when the builder cannot
168    /// take one).
169    pub(crate) fn road_try(&mut self, work: &mut Work, c: usize, time: i64) -> u8 {
170        let WorkKind::Road(r) = &work.kind else {
171            return 0;
172        };
173        if r.builder.is_some() {
174            return 1;
175        }
176        let (cell, ty, material) = (r.cell, r.ty, r.material);
177        let here = self.citizen_cell(c);
178        if !self.reachable(here, cell) {
179            return 1;
180        }
181        if let Some(o) = self.cells.objects[cell as usize] {
182            if let Some(pile) = self.piles.get(&o) {
183                self.offers = vec![pile.work];
184                return crate::work::jobs::NESTED;
185            }
186            if self.templates[self.placed[o].template].natural.is_some()
187                && !self.claimed.contains(&o)
188            {
189                let Some(nb) = crate::pathfind::nearest_neighbour(
190                    self.size,
191                    &self.cells.types,
192                    cell,
193                    here,
194                    0xe07,
195                ) else {
196                    return 1;
197                };
198                if !self.reachable(nb, here) {
199                    return 1;
200                }
201                let point = self.ground_at(nb);
202                self.removal_sequence(c, work, o, point, false);
203                self.took_job(c, work);
204                if let WorkKind::Road(r) = &mut work.kind {
205                    r.builder = Some(c);
206                }
207                return 2;
208            }
209            return 1;
210        }
211        let target = self.ground_at(cell);
212        match material {
213            None => self.road_sequence(c, work, target, cell, ty, None, None),
214            Some(m) => {
215                if 0 < self.states[c].carried.available(m) {
216                    self.road_sequence(c, work, target, cell, ty, Some(m), None);
217                } else {
218                    let Some(found) = self.find_storage(m, 1, false, here) else {
219                        return 1;
220                    };
221                    let size = self.materials[m].size;
222                    let carried = &self.states[c].carried;
223                    if carried.capacity - carried.used(self.materials) < size {
224                        self.drop_off(c);
225                        return 2;
226                    }
227                    let entity = self.storage_sites[found.site].entity;
228                    self.road_sequence(
229                        c,
230                        work,
231                        target,
232                        cell,
233                        ty,
234                        Some(m),
235                        Some((found.storage, found.target, entity)),
236                    );
237                }
238            }
239        }
240        let _ = time;
241        self.took_job(c, work);
242        if let WorkKind::Road(r) = &mut work.kind {
243            r.builder = Some(c);
244        }
245        2
246    }
247
248    /// ClearArea's road cells: `FUN_1400c54a0` to ground (type 2, `+0x2b` 0), the task's
249    /// parameter the cell.
250    pub(crate) fn road_clear_sequence(
251        &mut self,
252        c: usize,
253        work: &mut Work,
254        target: [f32; 3],
255        cell: i32,
256    ) {
257        self.clear_citizen(c, Some(work));
258        self.on_empty_think(c);
259        self.push(
260            c,
261            Action::CompleteTask {
262                work: work.id,
263                param: cell,
264            },
265        );
266        self.push(
267            c,
268            Action::CompleteRoad {
269                cell,
270                ty: GROUND,
271                built: 0,
272            },
273        );
274        self.push(c, Action::ShowTool(-1));
275        self.push(c, self.perform(c, -1000.0, 10, 4.0, true));
276        let walk = MoveTo::new(target, 2, 0, WALKABLE, 0, -1.0, self.rng);
277        self.push(c, Action::move_to(walk));
278        self.push(c, Action::ShowTool(1));
279    }
280
281    /// `FUN_1400c54a0`: (fetch: walk to the storage, take what can be carried of the material)
282    /// → the shovel → walk to the cell → work it 4 s (clip 10, not turning) → (use one of the
283    /// material) → CompleteRoad → the task (parameter 1, which ends the work).
284    #[allow(clippy::too_many_arguments)]
285    pub(crate) fn road_sequence(
286        &mut self,
287        c: usize,
288        work: &mut Work,
289        target: [f32; 3],
290        cell: i32,
291        ty: u8,
292        material: Option<usize>,
293        fetch: Option<(usize, [f32; 3], usize)>,
294    ) {
295        self.clear_citizen(c, Some(work));
296        self.on_empty_think(c);
297        self.push(
298            c,
299            Action::CompleteTask {
300                work: work.id,
301                param: 1,
302            },
303        );
304        self.push(
305            c,
306            Action::CompleteRoad {
307                cell,
308                ty,
309                built: NONE,
310            },
311        );
312        self.push(c, Action::ShowTool(-1));
313        if let Some(m) = material {
314            self.push(
315                c,
316                Action::ConsumeStoredRawMaterial {
317                    storage: usize::MAX,
318                    items: vec![(m, 1)],
319                    reserved: false,
320                },
321            );
322        }
323        self.push(c, self.perform(c, -1000.0, 10, 4.0, true));
324        let walk = MoveTo::new(target, 2, 0, WALKABLE, 0, -1.0, self.rng);
325        self.push(c, Action::move_to(walk));
326        self.push(c, Action::ShowTool(1));
327        if let (Some(m), Some((storage, source, entity))) = (material, fetch) {
328            let facing =
329                crate::behaviour::character::work_facing(self.facing_centre(entity), source);
330            self.push(c, self.perform(c, facing, 7, 0.75, false));
331            let carried = &self.states[c].carried;
332            let amount = (carried.capacity / self.materials[m].size.max(1))
333                .min(self.storages[storage].available(m));
334            self.storages[storage].reserve(m, amount);
335            self.push(
336                c,
337                Action::PickupStoredRawMaterial {
338                    storage,
339                    items: vec![(m, amount)],
340                    partial: true,
341                },
342            );
343            self.push(c, self.perform(c, facing, 6, 0.75, false));
344            let walk_clip = if 0 < self.states[c].carried.used(self.materials) {
345                4
346            } else {
347                1
348            };
349            let walk = MoveTo::new(source, walk_clip, 0, WALKABLE, 0, -1.0, self.rng);
350            self.push(c, Action::move_to(walk));
351        }
352    }
353}