Skip to main content

banished_game/people/
cemetery.rs

1//! Cemeteries and burial: CemeteryComponent and TombstoneComponent. See docs/CITIZEN-LIFE.md
2//! "Burial".
3//!
4//! A cemetery lays out its graves on every other row inside its fence, clear of the gate. A citizen
5//! who dies is buried in the nearest reachable cemetery with a free grave, which gets a tombstone
6//! for five to seven years.
7//!
8//! Original: `FUN_140048100`, `0x140160aa0`.
9// vtable 0x14015f1c0
10
11use crate::scheduler::ticks;
12use crate::terrain::CellType;
13use crate::town::Sim;
14
15/// `DAT_140178934`.
16const PERIOD: f32 = 300.0;
17
18// +0x20
19/// CemeteryDescription (the tombstone template) with its TombstoneDescription: a grave lasts `(life
20/// + RandFloat × range)` × 300 s.
21#[derive(Debug, Clone, Copy, PartialEq)]
22pub struct CemeteryTemplate {
23    /// The tombstone template.
24    pub tombstone: usize,
25    /// How long a grave lasts, in months of 300 seconds.
26    pub life: f32,
27    /// The random part of how long it lasts.
28    pub range: f32,
29}
30
31impl Sim<'_, '_> {
32    /// Activation (`FUN_140047b60`): the cells inside the fence (one in from the map
33    /// rectangle) on every other row along the gate's side, except the two lines in front of
34    /// the gate (the first MultiObject part).
35    fn lay_graves(&self, e: usize) -> Vec<i32> {
36        let n = self.size as i32;
37        let (x0, y0) = self.placed[e].cell;
38        let (w, h) = self.placed[e].footprint;
39        let yaw = self.placed[e].yaw as i32;
40        let along = yaw == 0 || yaw == 180;
41        let gate = self.sites.get(&e).and_then(|s| s.parts.first().copied());
42        let off = gate.map_or(-10, |g| {
43            let (gx, gy) = self.placed[g].cell;
44            if along {
45                gx - x0
46            } else {
47                gy - y0
48            }
49        });
50        let (x1, y1, w2, h2) = (x0 + 1, y0 + 1, w - 2, h - 2);
51        let mut graves = Vec::new();
52        if along {
53            for row in (0..h2).step_by(2) {
54                for col in 0..w2 {
55                    if col != off + 1 && col != off {
56                        graves.push((row + y1) * n + col + x1);
57                    }
58                }
59            }
60        } else {
61            for col in (0..w2).step_by(2) {
62                for row in 0..h2 {
63                    if row != off + 1 && row != off {
64                        graves.push((row + y1) * n + col + x1);
65                    }
66                }
67            }
68        }
69        graves
70    }
71
72    /// State 2 enter (`FUN_140047920`): join the town's cemeteries; a grave without a
73    /// tombstone is plain ground.
74    pub(crate) fn cemetery_enter2(&mut self, e: usize) {
75        self.cemeteries.push(e);
76        if self.sites.get(&e).is_some_and(|s| s.graves.is_empty()) {
77            let graves = self.lay_graves(e);
78            self.sites.get_mut(&e).unwrap().graves = graves;
79        }
80        for cell in self.sites[&e].graves.clone() {
81            let i = cell as usize;
82            if self.cells.objects[i].is_none_or(|o| !self.tombstones.contains_key(&o)) {
83                self.cells.set_type(i, CellType(2));
84                self.cells.objects[i] = None;
85            }
86        }
87    }
88
89    /// The burials decided this update: the grave becomes plain ground and gets a tombstone turned
90    /// as the cemetery, which lasts its tombstone description's time and stamps its cell.
91    ///
92    /// Original: `FUN_1400473f0`; the tombstone's set-up `FUN_1400581c0`.
93    pub fn place_burials(&mut self) {
94        for (cemetery, cell) in std::mem::take(self.burials) {
95            let Some(t) = self
96                .sites
97                .get(&cemetery)
98                .and_then(|s| self.site_templates[s.template].cemetery)
99            else {
100                continue;
101            };
102            let i = cell as usize;
103            self.cells.set_type(i, CellType(2));
104            self.cells.objects[i] = None;
105            let n = self.size as i32;
106            let template = &self.templates[t.tombstone];
107            let (variant, mesh) = template.model.as_ref().map_or((0, 0), |m| {
108                (m.variant.max(0) as usize, m.mesh.max(0) as usize)
109            });
110            let mut yaw = self.placed[cemetery].yaw as i32;
111            while yaw < 0 {
112                yaw += 360;
113            }
114            let id = self.placed.len();
115            self.placed.push(crate::nature::seeding::Placed {
116                template: t.tombstone,
117                bucket: template.bucket,
118                cell: (cell % n, cell / n),
119                footprint: (1, 1),
120                position: self.ground_at(cell),
121                yaw: yaw as f32,
122                variant,
123                mesh,
124                growth: 0.0,
125                growth_state: None,
126                spawn_state: None,
127                alive: true,
128                removal_requested: false,
129                animation: None,
130                scale: [1.0; 4],
131            });
132            let life = (self.rng.rand_float() * t.range + t.life) * PERIOD;
133            self.tombstones.insert(id, ticks(life));
134            self.stamp(id);
135            *self.models_dirty = true;
136        }
137    }
138}
139
140/// TombstoneComponent's scheduled update: the time left runs down; at none the tombstone goes (its
141/// cell is plain ground again). Returns whether it went.
142///
143/// Original: `FUN_140058130`.
144pub fn tombstone_visit(
145    tombstones: &mut std::collections::BTreeMap<usize, i64>,
146    cells: &mut crate::nature::seeding::Cells,
147    placed: &mut [crate::nature::seeding::Placed],
148    id: usize,
149    elapsed: i64,
150) -> bool {
151    let Some(left) = tombstones.get_mut(&id) else {
152        return false;
153    };
154    *left -= elapsed;
155    if 0 < *left {
156        return false;
157    }
158    tombstones.remove(&id);
159    let p = &mut placed[id];
160    p.alive = false;
161    let i = (p.cell.1 as usize) * cells.size + p.cell.0 as usize;
162    if cells.objects[i] == Some(id) {
163        cells.objects[i] = None;
164        cells.set_type(i, CellType(2));
165    }
166    true
167}