1use crate::town::{Sim, CONSTRUCTION_MASK};
19use crate::work::construction::{run, BuildWork, Callbacks, Controller};
20use crate::work::jobs::{Jobs, Work, WorkKind};
21use crate::work::professions::icon;
22
23#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
25pub struct DestroyTemplate {
26 pub replace: Option<usize>,
29 pub material: i32,
34 pub decal: i32,
37 pub min_mesh: i32,
40}
41
42#[derive(Debug, Clone, Default, PartialEq, Eq)]
47pub struct Destroy {
48 pub time: i64,
50 pub damaged: bool,
52 pub refund: bool,
54 pub count: i32,
56 pub target: i32,
58 pub rebuild: Option<usize>,
60 pub damaged_look: bool,
64}
65
66impl Destroy {
67 pub fn new(t: &DestroyTemplate) -> Destroy {
69 Destroy {
70 rebuild: t.replace,
71 ..Default::default()
72 }
73 }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq)]
81pub struct Rebuild {
82 pub template: usize,
84 pub cell: (i32, i32),
86 pub footprint: (i32, i32),
88 pub yaw: f32,
90 pub position: [f32; 3],
92 pub variant: usize,
94}
95
96const QUARTER: f32 = 90.0;
98const FLAT: f32 = 0.2;
99
100impl Sim<'_, '_> {
101 fn destroy_state(&mut self, e: usize) -> Option<&mut Destroy> {
102 self.sites.get_mut(&e)?.destroy.as_mut()
103 }
104
105 fn shown_state(&self, e: usize) -> i8 {
107 self.sites.get(&e).map_or(-1, |s| {
108 if s.machine.pending < 0 {
109 s.machine.state
110 } else {
111 s.machine.pending
112 }
113 })
114 }
115
116 pub fn is_damaged(&self, e: usize) -> bool {
120 matches!(self.shown_state(e), 6 | 7)
121 }
122
123 pub fn is_demolishing(&self, e: usize) -> bool {
127 matches!(self.shown_state(e), 3..=5)
128 }
129
130 fn unfinished(&self, e: usize) -> bool {
132 let site = &self.sites[&e];
133 let t = &self.site_templates[site.template];
134 site.progress < t.work * site.stages
135 }
136
137 pub(crate) fn has_build(&self, e: usize) -> bool {
139 let site = &self.sites[&e];
140 self.site_templates[site.template]
141 .controllers
142 .contains(&Controller::Build)
143 }
144
145 pub fn demolish(&mut self, jobs: &mut Jobs, e: usize) {
150 if self.destroy_state(e).is_none() {
151 return;
152 }
153 if self.is_damaged(e) {
154 self.destroy_message(jobs, e, 1);
155 return;
156 }
157 run(self, jobs, e, false, false);
158 self.machine(e).request(3);
159 run(self, jobs, e, false, false);
160 }
161
162 pub fn upgrade(&mut self, jobs: &mut Jobs, e: usize, template: usize) {
166 let Some(d) = self.destroy_state(e) else {
167 return;
168 };
169 d.rebuild = Some(template);
170 self.demolish(jobs, e);
171 }
172
173 pub fn cancel_demolition(&mut self, jobs: &mut Jobs, e: usize) {
179 self.destroy_message(jobs, e, 0);
180 let site = &self.sites[&e];
181 let replace = self.site_templates[site.template]
182 .destroy
183 .and_then(|d| d.replace);
184 if let Some(d) = self.destroy_state(e) {
185 d.rebuild = replace;
186 }
187 }
188
189 pub fn damage(&mut self, jobs: &mut Jobs, e: usize, time: i64) -> bool {
194 if self.destroy_state(e).is_none() {
195 return false;
196 }
197 if self.has_build(e) && self.sites[&e].progress == 0 {
198 return false;
199 }
200 let d = self.destroy_state(e).unwrap();
201 d.damaged = true;
202 d.time = time;
203 if self.machine(e).index < 0 {
204 run(self, jobs, e, false, false);
205 }
206 self.machine(e).request(3);
207 run(self, jobs, e, false, false);
208 true
209 }
210
211 pub fn to_rubble(&mut self, jobs: &mut Jobs, e: usize) {
215 if self.machine(e).index < 0 {
216 run(self, jobs, e, false, false);
217 }
218 self.machine(e).request(7);
219 run(self, jobs, e, false, false);
220 }
221
222 pub fn destroy_message(&mut self, jobs: &mut Jobs, e: usize, msg: i32) {
225 if self.destroy_state(e).is_none() || self.machine(e).index < 0 {
226 return;
227 }
228 match (self.machine(e).state, msg) {
229 (4 | 5, 0) => self.cancel(jobs, e),
230 (6, 2) => self.machine(e).request(7),
231 (7, 0) => {
233 self.become_site(jobs, e);
234 let template = self.placed[e].template;
235 let d = self.destroy_state(e).unwrap();
236 d.refund = true;
237 d.rebuild = Some(template);
238 self.machine(e).request(5);
239 }
240 (7, 1) => {
242 self.become_site(jobs, e);
243 let build = self.has_build(e);
244 if build {
245 self.destroy_state(e).unwrap().refund = true;
246 }
247 self.machine(e).request(5);
248 }
249 _ => {}
250 }
251 }
252
253 fn cancel(&mut self, jobs: &mut Jobs, e: usize) {
256 self.set_icon(e, icon::DESTROY, false);
257 self.remove_work(jobs, e);
258 if self.destroy_state(e).is_some_and(|d| d.damaged) {
259 self.become_building(jobs, e);
260 self.machine(e).request(7);
261 return;
262 }
263 if !self.has_build(e) {
264 self.machine(e).request(2);
265 run(self, jobs, e, false, false);
266 } else {
267 let m = self.sites[&e].parts.first().copied().unwrap_or(e);
269 let mesh = self.placed[m].mesh;
270 let state = if mesh == usize::MAX {
271 0
272 } else if mesh as i32 == self.meshes_of(m) - 1 {
273 2
274 } else {
275 8
276 };
277 self.machine(e).request(state);
278 }
279 self.become_building(jobs, e);
280 }
281
282 fn meshes_of(&self, m: usize) -> i32 {
283 let p = &self.placed[m];
284 self.templates[p.template]
285 .model
286 .as_ref()
287 .and_then(|model| model.variants.get(p.variant))
288 .map_or(1, |v| v.meshes.len() as i32)
289 }
290
291 pub(crate) fn residence_leave(&mut self, e: usize) {
295 let Some(mut home) = self.homes.remove(&e) else {
296 return;
297 };
298 crate::people::status::set_icon(&mut home.status, icon::NO_FOOD, false, false);
299 crate::people::status::set_icon(&mut home.status, icon::NO_FIREWOOD, false, false);
300 for slot in 0..home.families.len() {
301 while let Some(&c) = home.families[slot].first() {
302 self.move_out(&mut home, c);
303 }
304 }
305 self.workforce.residences.retain(|&r| r != e);
306 if let Some(site) = self.sites.get_mut(&e) {
307 site.home = Some(home);
308 }
309 }
310
311 fn move_out(&mut self, home: &mut crate::people::life::Home, c: usize) {
313 for f in home.families.iter_mut() {
314 f.retain(|&x| x != c);
315 }
316 self.states[c].home = None;
317 self.states[c].home_slot = -1;
318 for d in self.states[c].dependents.clone() {
319 if self.states[d].home.is_some() {
320 self.move_out(home, d);
321 }
322 }
323 }
324
325 pub(crate) fn destroy_enter3(&mut self, jobs: &mut Jobs, e: usize) {
327 if let Some(d) = self.destroy_state(e) {
328 d.count = 0;
329 }
330 let id = self.sites[&e].work_id;
331 if jobs.works.contains_key(&id) {
332 self.remove_work(jobs, e);
333 }
334 if let Some(w) = self.workforce.workplaces.get_mut(&e) {
336 for bit in 0..32 {
337 w.set_icon(bit, false);
338 }
339 }
340 if self.destroy_state(e).is_some_and(|d| d.damaged) {
341 self.machine(e).request(6);
342 } else {
343 self.machine(e).request(4);
344 self.set_icon(e, icon::DESTROY, true);
345 }
346 }
347
348 pub(crate) fn destroy_enter4(&mut self, jobs: &mut Jobs, e: usize) {
350 self.become_site(jobs, e);
351 let build = self.has_build(e);
352 if build {
353 self.destroy_state(e).unwrap().refund = true;
354 }
355 if self.storage_empty(e) || (build && self.unfinished(e)) {
356 self.machine(e).request(5);
357 return;
358 }
359 let work = Work::new(
360 e,
361 self.rect_cell(e),
362 self.professions.laborer,
363 WorkKind::ClearStorage(crate::production::herbalist::HerbalistWork::default()),
364 );
365 self.set_work(jobs, e, work, 1, false);
366 }
367
368 fn storage_empty(&self, e: usize) -> bool {
370 let s = self.sites[&e].storage;
371 self.storages[s].used(self.materials) == 0
372 }
373
374 pub(crate) fn destroy_update4(&mut self, jobs: &mut Jobs, e: usize) {
377 if !self.storage_empty(e) {
378 return;
379 }
380 let id = self.sites[&e].work_id;
381 let busy = jobs.works.get(&id).is_some_and(|w| match &w.kind {
382 WorkKind::ClearStorage(c) => !c.workers.is_empty(),
383 _ => false,
384 });
385 if !busy {
386 self.machine(e).request(5);
387 self.remove_work(jobs, e);
388 }
389 }
390
391 pub(crate) fn destroy_enter5(&mut self, jobs: &mut Jobs, e: usize) {
395 if self.has_build(e) {
396 let site = &self.sites[&e];
397 if site.progress != 0 {
398 let work = self.site_templates[site.template].work;
399 let target = ((work * site.stages) / 2).max(1);
400 self.destroy_state(e).unwrap().target = target;
401 let w = Work::new(
402 e,
403 self.rect_cell(e),
404 self.professions.builder,
405 WorkKind::Build(BuildWork {
406 destroy: true,
407 occupied: vec![false; self.build_point_count(e)],
408 ..Default::default()
409 }),
410 );
411 self.set_work(jobs, e, w, CONSTRUCTION_MASK, false);
412 return;
413 }
414 if self.destroy_state(e).is_some_and(|d| d.refund) {
417 let (x, y) = self.placed[e].cell;
418 let (w, h) = self.placed[e].footprint;
419 self.refund(e, x, y, w, h);
420 }
421 }
422 self.deactivate(jobs, e);
423 }
424
425 pub(crate) fn destroy_update5(&mut self, jobs: &mut Jobs, e: usize) {
428 let Some(d) = self.destroy_state(e).cloned() else {
429 return;
430 };
431 if d.count < d.target {
432 return;
433 }
434 self.remove_work(jobs, e);
435 let p = &self.placed[e];
436 let (cell, footprint, yaw) = (p.cell, p.footprint, p.yaw);
437 let (position, variant) = (p.position, p.variant);
438 self.become_building(jobs, e);
439 let finished = !self.unfinished(e);
440 self.deactivate(jobs, e);
441 if let Some(template) = d.rebuild {
442 if finished || d.damaged {
443 let own = self.placed[e].template;
444 let replace = self.site_templates[self.sites[&e].template]
445 .destroy
446 .and_then(|t| t.replace);
447 if d.refund && (template == own || Some(template) != replace) {
450 self.refund(e, cell.0, cell.1, footprint.0, footprint.1);
451 }
452 let mut yaw = yaw as i32;
453 while yaw < 0 {
454 yaw += 360;
455 }
456 self.rebuilds.push(Rebuild {
457 template,
458 cell,
459 footprint,
460 yaw: yaw as f32,
461 position,
462 variant,
463 });
464 return;
465 }
466 }
467 if d.refund {
468 self.refund(e, cell.0, cell.1, footprint.0, footprint.1);
469 }
470 }
471
472 pub(crate) fn destroy_enter6(&mut self, e: usize) {
474 let s = self.sites[&e].storage;
475 self.storage_changes.push((e, false));
477 self.storages[s].items.clear();
478 if self.destroy_state(e).is_some_and(|d| d.time < 1) {
479 self.collapse(e);
480 } else {
481 self.damaged_look(e);
483 }
484 }
485
486 pub(crate) fn destroy_update6(&mut self, e: usize, dt: i64) {
488 let Some(d) = self.destroy_state(e) else {
489 return;
490 };
491 if d.time != 0 {
492 d.time -= dt;
493 if d.time < 1 {
494 d.time = 0;
495 self.collapse(e);
496 }
497 }
498 }
499
500 pub(crate) fn destroy_exit6(&mut self, e: usize) {
503 if self.templates[self.placed[e].template].model.is_none() {
504 return;
505 }
506 if self.destroy_state(e).is_some_and(|d| 0 < d.time) {
507 self.collapse(e);
508 }
509 if self.rng.rand_below(2) == 0 {
510 self.collapse(e);
511 }
512 }
513
514 fn damaged_look(&mut self, e: usize) {
516 if let Some(d) = self.destroy_state(e) {
517 if !d.damaged_look {
518 d.damaged_look = true;
519 *self.models_dirty = true;
520 }
521 }
522 }
523
524 fn collapse(&mut self, e: usize) {
530 let Some(t) = self
531 .sites
532 .get(&e)
533 .and_then(|s| self.site_templates[s.template].destroy)
534 else {
535 return;
536 };
537 let mesh = self.placed[e].mesh;
538 let current = if mesh == usize::MAX { -1 } else { mesh as i32 };
539 let next = (current - 1).max(0).max(t.min_mesh);
540 self.damaged_look(e);
541 let d = self.decals.entry(e).or_default();
542 if d.material != t.decal {
543 d.material = t.decal;
544 *self.models_dirty = true;
545 }
546 if next != current {
547 self.placed[e].mesh = next as usize;
548 *self.models_dirty = true;
549 }
550 }
551
552 pub(crate) fn deactivate(&mut self, jobs: &mut Jobs, e: usize) {
555 self.remove_work(jobs, e);
556 let n = self.size as i32;
557 let stamped = {
558 let p = &self.placed[e];
559 let (x, y) = p.cell;
560 let (w, h) = p.footprint;
561 (y..y + h).any(|cy| {
562 (x..x + w).any(|cx| self.cells.objects[(cy * n + cx) as usize] == Some(e))
563 })
564 };
565 if stamped {
566 self.unstamp(e);
567 }
568 let p = &self.placed[e];
569 let (x, y) = p.cell;
570 let (w, h) = p.footprint;
571 for cy in y..y + h {
572 for cx in x..x + w {
573 let i = (cy * n + cx) as usize;
574 if self.cells.occupants[i] == Some(e) {
575 self.cells.occupants[i] = None;
576 }
577 }
578 }
579 if let Some(site) = self.sites.get_mut(&e) {
580 site.destroyed = true;
581 }
582 }
583
584 fn refund(&mut self, e: usize, x: i32, y: i32, w: i32, h: i32) {
589 let site = &self.sites[&e];
590 let t = &self.site_templates[site.template];
591 let mut piles: Vec<(usize, i32)> = Vec::new();
592 if site.progress < t.work * site.stages {
593 let div = if 0 < site.progress { 2 } else { 1 };
594 for item in &self.storages[site.storage].items {
595 let Some(k) = t.materials.iter().position(|&(_, m)| m == item.material) else {
596 continue;
597 };
598 if let Some(&Some(pile)) = t.material_templates.get(k) {
599 piles.push((pile, (item.count - item.reserved) / div));
600 }
601 }
602 } else {
603 let div = if self.sites[&e].destroy.as_ref().is_some_and(|d| d.damaged) {
604 4
605 } else {
606 2
607 };
608 for (k, &(count, _)) in t.materials.iter().enumerate() {
609 if let Some(&Some(pile)) = t.material_templates.get(k) {
610 piles.push((pile, count / div));
611 }
612 }
613 }
614 let n = self.size as i32;
615 let (mut col, mut row) = (0, 0);
616 for (pile, mut left) in piles {
617 if left <= 0 {
618 continue;
619 }
620 let Some(material) = self.templates[pile].pile_material else {
621 continue;
622 };
623 loop {
626 let cell = (y + row) * n + x + col;
627 if cell < 0 || n * n <= cell {
628 break;
629 }
630 let i = cell as usize;
631 let ground = self.ground_at(cell);
632 if self.cells.occupants[i].is_none()
633 && self.cells.types[i].0 == 2
634 && 0.0 <= ground[2]
635 && ground[2] < FLAT
636 {
637 let amount = left.min(100 / self.materials[material].size.max(1));
638 let yaw = self.rng.rand_tol(2) as f32 * QUARTER;
639 self.spawn_pile(pile, (x + col, y + row), amount, yaw, None);
640 left -= amount;
641 }
642 col += 1;
643 if w <= col {
644 col = 0;
645 row += 1;
646 }
647 if row == h || left <= 0 {
648 break;
649 }
650 }
651 }
652 }
653}