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>, 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 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 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 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}