Skip to main content

banished_game/world/
map.rs

1//! The gameplay map: `n × n` cells over the centre of the `2n` terrain (see docs/MAP-CELLS.md).
2
3use crate::math::sin_cos_deg;
4use crate::terrain::{CellType, HeightGrid};
5
6/// The gameplay map: the type of each cell. See [`Map::find_start`] for the start of a new town.
7pub struct Map {
8    /// Cells per side (`n`).
9    pub size: usize,
10    // +0x29
11    /// Cell type (byte) per cell, row-major.
12    pub types: Vec<CellType>,
13}
14
15impl Map {
16    /// Classify every cell from the terrain.
17    ///
18    /// Original: `FUN_1400a43f0`.
19    pub fn new(size: usize, terrain: &HeightGrid) -> Map {
20        Map {
21            size,
22            types: (0..size * size)
23                .map(|i| terrain.natural_cell_type(size, i))
24                .collect(),
25        }
26    }
27
28    /// Every cell within `radius` (`dx² + dy² ≤ radius_sq`) of `(x, y)` is in the map and buildable
29    /// ground.
30    ///
31    /// Original: `FUN_1400a7d50`.
32    pub fn is_clear(&self, x: i32, y: i32, radius: i32, radius_sq: i32) -> bool {
33        let n = self.size as i32;
34        for dy in -radius..=radius {
35            for dx in -radius..=radius {
36                if dx * dx + dy * dy > radius_sq {
37                    continue;
38                }
39                let (cx, cy) = (x + dx, y + dy);
40                if cx < 0 || cy < 0 || cx >= n || cy >= n {
41                    return false;
42                }
43                if self.types[(cy * n + cx) as usize] != CellType::GROUND {
44                    return false;
45                }
46            }
47        }
48        true
49    }
50
51    /// The town's start cell. Rings outward from the map centre, testing `4, 8, 16, …` evenly
52    /// spaced angles per ring and moving out `3r/4` cells each ring. Loops forever (as the original
53    /// does) if no clear area exists.
54    ///
55    /// Original: `FUN_1400a7bf0`.
56    pub fn find_start(&self, radius: i32) -> (i32, i32) {
57        let centre = self.size as i32 >> 1;
58        let mut count = 4;
59        let mut dist = 0.0f32;
60        loop {
61            for i in 0..count {
62                let angle = i as f32 * (360.0 / count as f32);
63                let (sin, cos) = sin_cos_deg(angle);
64                let x = (cos * dist) as i32 + centre;
65                let y = (sin * dist) as i32 + centre;
66                if self.is_clear(x, y, radius, radius * radius) {
67                    return (x, y);
68                }
69            }
70            count *= 2;
71            dist += ((radius * 3) / 4) as f32;
72        }
73    }
74}