veloren_world/site2/
tile.rs

1use super::*;
2use crate::util::DHashSet;
3use common::spiral::Spiral2d;
4use std::ops::Range;
5
6pub const TILE_SIZE: u32 = 6;
7pub const ZONE_SIZE: u32 = 16;
8pub const ZONE_RADIUS: u32 = 16;
9pub const TILE_RADIUS: u32 = ZONE_SIZE * ZONE_RADIUS;
10#[expect(dead_code)]
11pub const MAX_BLOCK_RADIUS: u32 = TILE_SIZE * TILE_RADIUS;
12
13pub struct TileGrid {
14    pub(crate) bounds: Aabr<i32>, // Inclusive
15    zones: Grid<Option<Grid<Option<Tile>>>>,
16}
17
18impl Default for TileGrid {
19    fn default() -> Self {
20        Self {
21            bounds: Aabr::new_empty(Vec2::zero()),
22            zones: Grid::populate_from(Vec2::broadcast(ZONE_RADIUS as i32 * 2 + 1), |_| None),
23        }
24    }
25}
26
27impl TileGrid {
28    pub fn get_known(&self, tpos: Vec2<i32>) -> Option<&Tile> {
29        let tpos = tpos + TILE_RADIUS as i32;
30        self.zones
31            .get(tpos.map(|e| e.div_euclid(ZONE_SIZE as i32)))
32            .and_then(|zone| {
33                zone.as_ref()?
34                    .get(tpos.map(|e| e.rem_euclid(ZONE_SIZE as i32)))
35            })
36            .and_then(|tile| tile.as_ref())
37    }
38
39    pub fn get(&self, tpos: Vec2<i32>) -> &Tile {
40        static EMPTY: Tile = Tile::empty();
41        self.get_known(tpos).unwrap_or(&EMPTY)
42    }
43
44    // WILL NOT EXPAND BOUNDS!
45    pub fn get_mut(&mut self, tpos: Vec2<i32>) -> Option<&mut Tile> {
46        let tpos = tpos + TILE_RADIUS as i32;
47        self.zones
48            .get_mut(tpos.map(|e| e.div_euclid(ZONE_SIZE as i32)))
49            .and_then(|zone| {
50                zone.get_or_insert_with(|| {
51                    Grid::populate_from(Vec2::broadcast(ZONE_SIZE as i32), |_| None)
52                })
53                .get_mut(tpos.map(|e| e.rem_euclid(ZONE_SIZE as i32)))
54                .map(|tile| tile.get_or_insert_with(Tile::empty))
55            })
56    }
57
58    pub fn set(&mut self, tpos: Vec2<i32>, tile: Tile) -> Option<Tile> {
59        self.bounds.expand_to_contain_point(tpos);
60        self.get_mut(tpos).map(|t| std::mem::replace(t, tile))
61    }
62
63    pub fn find_near<R>(
64        &self,
65        tpos: Vec2<i32>,
66        mut f: impl FnMut(Vec2<i32>, &Tile) -> Option<R>,
67    ) -> Option<(R, Vec2<i32>)> {
68        const MAX_SEARCH_RADIUS_BLOCKS: u32 = 70;
69        const MAX_SEARCH_CELLS: u32 = ((MAX_SEARCH_RADIUS_BLOCKS / TILE_SIZE) * 2 + 1).pow(2);
70        Spiral2d::new()
71            .take(MAX_SEARCH_CELLS as usize)
72            .map(|r| tpos + r)
73            .find_map(|tpos| f(tpos, self.get(tpos)).zip(Some(tpos)))
74    }
75
76    pub fn grow_aabr(
77        &self,
78        center: Vec2<i32>,
79        area_range: Range<u32>,
80        min_dims: Extent2<u32>,
81    ) -> Result<Aabr<i32>, Aabr<i32>> {
82        let mut aabr = Aabr {
83            min: center,
84            max: center + 1,
85        };
86
87        if !self.get(center).is_empty() {
88            return Err(aabr);
89        };
90
91        let mut last_growth = 0;
92        for i in 0..32 {
93            if i - last_growth >= 4
94                || aabr.size().product()
95                    + if i % 2 == 0 {
96                        aabr.size().h
97                    } else {
98                        aabr.size().w
99                    }
100                    > area_range.end as i32
101            {
102                break;
103            } else {
104                // `center.sum()` to avoid biasing certain directions
105                match (i + center.sum().abs()) % 4 {
106                    0 if (aabr.min.y..aabr.max.y + 1)
107                        .all(|y| self.get(Vec2::new(aabr.max.x, y)).is_empty()) =>
108                    {
109                        aabr.max.x += 1;
110                        last_growth = i;
111                    },
112                    1 if (aabr.min.x..aabr.max.x + 1)
113                        .all(|x| self.get(Vec2::new(x, aabr.max.y)).is_empty()) =>
114                    {
115                        aabr.max.y += 1;
116                        last_growth = i;
117                    },
118                    2 if (aabr.min.y..aabr.max.y + 1)
119                        .all(|y| self.get(Vec2::new(aabr.min.x - 1, y)).is_empty()) =>
120                    {
121                        aabr.min.x -= 1;
122                        last_growth = i;
123                    },
124                    3 if (aabr.min.x..aabr.max.x + 1)
125                        .all(|x| self.get(Vec2::new(x, aabr.min.y - 1)).is_empty()) =>
126                    {
127                        aabr.min.y -= 1;
128                        last_growth = i;
129                    },
130                    _ => {},
131                }
132            }
133        }
134
135        if aabr.size().product() as u32 >= area_range.start
136            && aabr.size().w as u32 >= min_dims.w
137            && aabr.size().h as u32 >= min_dims.h
138        {
139            Ok(aabr)
140        } else {
141            Err(aabr)
142        }
143    }
144
145    pub fn grow_organic(
146        &self,
147        rng: &mut impl Rng,
148        center: Vec2<i32>,
149        area_range: Range<u32>,
150    ) -> Result<DHashSet<Vec2<i32>>, DHashSet<Vec2<i32>>> {
151        let mut tiles = DHashSet::default();
152        let mut open = Vec::new();
153
154        tiles.insert(center);
155        open.push(center);
156
157        while tiles.len() < area_range.end as usize && !open.is_empty() {
158            let tile = open.remove(rng.gen_range(0..open.len()));
159
160            for &rpos in CARDINALS.iter() {
161                let neighbor = tile + rpos;
162
163                if self.get(neighbor).is_empty() && !tiles.contains(&neighbor) {
164                    tiles.insert(neighbor);
165                    open.push(neighbor);
166                }
167            }
168        }
169
170        if tiles.len() >= area_range.start as usize {
171            Ok(tiles)
172        } else {
173            Err(tiles)
174        }
175    }
176}
177
178#[derive(Clone, PartialEq)]
179pub enum TileKind {
180    Empty,
181    Hazard(HazardKind),
182    Field,
183    Plaza,
184    Road { a: u16, b: u16, w: u16 },
185    Path { c: Vec2<f32>, w: f32 },
186    Building,
187    Castle,
188    Wall(Dir),
189    Tower(RoofKind),
190    Keep(KeepKind),
191    Gate,
192    GnarlingFortification,
193    Bridge,
194    AdletStronghold,
195    DwarvenMine,
196}
197
198use std::fmt;
199impl fmt::Display for TileKind {
200    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
201        match self {
202            TileKind::Empty => write!(f, "Empty"),
203            TileKind::Hazard(_) => write!(f, "Hazard"),
204            TileKind::Field => write!(f, "Field"),
205            TileKind::Plaza => write!(f, "Plaza"),
206            TileKind::Road { .. } => write!(f, "Road"),
207            TileKind::Path { .. } => write!(f, "Path"),
208            TileKind::Building => write!(f, "Building"),
209            TileKind::Castle => write!(f, "Castle"),
210            TileKind::Wall(_) => write!(f, "Wall"),
211            TileKind::Tower(_) => write!(f, "Tower"),
212            TileKind::Keep(_) => write!(f, "Keep"),
213            TileKind::Gate => write!(f, "Gate"),
214            TileKind::GnarlingFortification => write!(f, "GnarlingFortification"),
215            TileKind::Bridge => write!(f, "Bridge"),
216            TileKind::AdletStronghold => write!(f, "AdletStronghold"),
217            TileKind::DwarvenMine => write!(f, "DwarvenMine"),
218        }
219    }
220}
221
222#[derive(Clone, PartialEq)]
223pub struct Tile {
224    pub kind: TileKind,
225    pub plot: Option<Id<Plot>>,
226    pub(crate) hard_alt: Option<i32>,
227}
228
229impl Tile {
230    pub const fn empty() -> Self {
231        Self {
232            kind: TileKind::Empty,
233            plot: None,
234            hard_alt: None,
235        }
236    }
237
238    /// Create a tile that is not associated with any plot.
239    pub const fn free(kind: TileKind) -> Self {
240        Self {
241            kind,
242            plot: None,
243            hard_alt: None,
244        }
245    }
246
247    pub fn is_empty(&self) -> bool { self.kind == TileKind::Empty }
248
249    pub fn is_natural(&self) -> bool {
250        matches!(
251            self.kind,
252            TileKind::Empty | TileKind::Hazard(_) | TileKind::AdletStronghold
253        )
254    }
255
256    pub fn is_road(&self) -> bool {
257        matches!(
258            self.kind,
259            TileKind::Plaza | TileKind::Road { .. } | TileKind::Path { .. }
260        )
261    }
262
263    pub fn is_obstacle(&self) -> bool {
264        matches!(self.kind, TileKind::Hazard(_)) || self.is_building()
265    }
266
267    pub fn is_building(&self) -> bool {
268        matches!(
269            self.kind,
270            TileKind::Building | TileKind::Castle | TileKind::Wall(_)
271        )
272    }
273}
274
275#[derive(Copy, Clone, PartialEq)]
276pub enum HazardKind {
277    Water,
278    Hill { gradient: f32 },
279}
280
281#[derive(Copy, Clone, PartialEq, Eq)]
282pub enum KeepKind {
283    Middle,
284    Corner,
285    Wall(Dir),
286}
287
288#[derive(Copy, Clone, PartialEq, Eq)]
289pub enum RoofKind {
290    Parapet,
291    Pyramid,
292}