1use 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
18const MASK: u32 = 0x2000;
20pub const NONE: u8 = 0xff;
23pub const BUILDING: u8 = 0x80;
25pub const GROUND: u8 = 2;
27
28#[derive(Debug, Clone, Default, PartialEq, Eq)]
32pub struct RoadWork {
33 pub builder: Option<usize>,
35 pub material: Option<usize>,
37 pub cell: i32,
39 pub ty: u8,
41 pub old_kind: u8,
43 pub old_built: u8,
45 pub old_type: u8,
47}
48
49impl Sim<'_, '_> {
50 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 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 fn set_cell_type(&mut self, cell: i32, ty: u8) {
136 self.cells.set_type(cell as usize, CellType(ty));
137 }
138
139 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 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 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 #[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}