Skip to main content

banished_game/production/
quarry.rs

1//! Quarries and mines: the pit deepens as it is worked. See docs/QUARRY.md.
2//!
3//! - Entity event 4 (the build's end): the pit is dug to the description's shallow floor.
4//! - Its message, which each produced batch sends from the ConsumeProduce sequence (the
5//!   description's component type): the pit is dug to `lerp(shallow, deep, produced / lifetime)`,
6//!   then every cell of the map rectangle whose terrain is steep at any of four points (normal `z <
7//!   0.85`) becomes the quarry's (type 5, object the quarry).
8//!
9//! A dig: every vertex of the pit's picking mesh, placed by the model's transform,
10//! sets the height under it to the vertex's height, no lower than the floor. The DecalComponent is
11//! re-added around each dig (the port's decals follow the terrain).
12//!
13//! Original: `QuarryComponent`: `FUN_14010cad0`, `FUN_140055a50`, `FUN_1400554e0`, `FUN_1400c4440`;
14//! the dig is `FUN_140055320`.
15// vtable 0x1401605b0; slot +0x60; slot +0x78; +0x38
16
17use std::sync::Arc;
18
19use crate::nature::seeding::{Cells, Placed};
20use crate::terrain::{CellType, HeightGrid};
21
22/// A resolved `QuarryDescription`.
23#[derive(Debug, Clone, PartialEq)]
24pub struct QuarryTemplate {
25    // +0x28, +0x2c
26    /// The floor when built.
27    pub shallow: f32,
28    /// The floor at the end of production.
29    pub deep: f32,
30    // +0x20
31    /// The pit's picking mesh vertices (model space).
32    pub vertices: Arc<Vec<[f32; 3]>>,
33}
34
35/// `DAT_140178930`.
36const HEIGHT_SCALE: f32 = 256.0;
37/// The cell type a steep quarry cell becomes.
38pub const QUARRY_CELL: CellType = CellType(5);
39
40/// The pit under the quarry `p`, no lower than `floor`. `size` is the map's cells per side.
41///
42/// Original: `FUN_140055320`.
43pub fn dig(heights: &mut [i16], width: usize, q: &QuarryTemplate, p: &Placed, floor: f32) -> bool {
44    let half = ((width - 1) >> 1) as f32;
45    let model = crate::math::Transform::of_model(p.yaw, p.position);
46    let mut changed = false;
47    for &v in q.vertices.iter() {
48        // The entity transform (`+0x148`) times the vertex.
49        let [x, y, z] = model.apply(v);
50        let z = (z + 0.0).max(floor);
51        let (gx, gy) = ((x + 0.25 + half) as i32, (y + 0.25 + half) as i32);
52        if gx < 0 || gy < 0 || width as i32 <= gx || width as i32 <= gy {
53            continue;
54        }
55        let h = (z * HEIGHT_SCALE) as i32 as i16;
56        let cell = &mut heights[gy as usize * width + gx as usize];
57        changed |= *cell != h;
58        *cell = h;
59    }
60    changed
61}
62
63/// The message's second half: the quarry's steep cells become its own.
64///
65/// Original: `FUN_1400554e0`.
66pub fn claim_steep_cells(cells: &mut Cells, terrain: &HeightGrid, entity: usize, p: &Placed) {
67    let n = cells.size as i32;
68    let half = (n >> 1) as f32 - 0.5;
69    let (x0, y0) = p.cell;
70    let (w, h) = p.footprint;
71    for y in y0..y0 + h {
72        for x in x0..x0 + w {
73            if x < 0 || y < 0 || n <= x || n <= y {
74                continue;
75            }
76            let (cx, cy) = (x as f32 - half, y as f32 - half);
77            for (dx, dy) in [(-0.25, -0.25), (-0.25, 0.25), (0.25, -0.25), (0.25, 0.25)] {
78                // The cell centre's ground (`FUN_1400a48b0` keeps x and y) a quarter on, the
79                // terrain cell under it cast from 100 above, at most 200 (`FUN_1400afef0`);
80                // no hit leaves the normal zero, which claims the cell.
81                let normal = terrain.normal_below(cx + dx, cy + dy, 100.0, 200.0);
82                if normal[2] < 0.85 {
83                    let i = (y * n + x) as usize;
84                    cells.set_type(i, QUARRY_CELL);
85                    cells.objects[i] = Some(entity);
86                }
87            }
88        }
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    fn placed() -> Placed {
97        Placed {
98            template: 0,
99            bucket: 0,
100            cell: (2, 2),
101            footprint: (4, 4),
102            position: [0.0, 0.0, 0.0],
103            yaw: 0.0,
104            variant: 0,
105            mesh: 0,
106            growth: 0.0,
107            growth_state: None,
108            spawn_state: None,
109            alive: true,
110            removal_requested: false,
111            animation: None,
112            scale: [1.0; 4],
113        }
114    }
115
116    /// The pit follows the mesh down to the floor; a deeper floor lets it follow further,
117    /// and the cells over its sides become the quarry's.
118    #[test]
119    fn the_pit_deepens_to_its_floor_and_claims_steep_cells() {
120        let q = QuarryTemplate {
121            shallow: -1.0,
122            deep: -3.0,
123            vertices: Arc::new(vec![[0.0, 0.0, -5.0], [1.0, 0.0, -0.5], [2.0, 0.0, 0.0]]),
124        };
125        let width = 17;
126        let mut heights = vec![0i16; width * width];
127        let p = placed();
128        assert!(dig(&mut heights, width, &q, &p, q.shallow));
129        let at = |h: &[i16], x: usize| h[8 * width + 8 + x];
130        assert_eq!(
131            (at(&heights, 0), at(&heights, 1), at(&heights, 2)),
132            (-256, -128, 0)
133        );
134        assert!(dig(&mut heights, width, &q, &p, q.deep));
135        assert_eq!(at(&heights, 0), -768);
136        let mut cells = Cells::new(8, vec![CellType::GROUND; 64]);
137        let grid = HeightGrid {
138            width,
139            heights: &heights,
140        };
141        claim_steep_cells(&mut cells, &grid, 7, &p);
142        let n = 8;
143        let claimed: Vec<usize> = (0..n * n)
144            .filter(|&i| cells.types[i] == QUARRY_CELL)
145            .collect();
146        assert!(!claimed.is_empty());
147        assert!(claimed.iter().all(|&i| cells.objects[i] == Some(7)));
148    }
149}