veloren_world/site/
mod.rs

1pub mod economy;
2mod generation;
3pub mod genstat;
4pub mod namegen;
5pub mod plot;
6mod tile;
7pub mod util;
8
9use self::tile::{HazardKind, KeepKind, RoofKind, TILE_SIZE, Tile, TileGrid};
10pub use self::{
11    economy::Economy,
12    generation::{Fill, Painter, Primitive, PrimitiveRef, Structure, aabr_with_z},
13    genstat::{GenStatPlotKind, GenStatSiteKind, SitesGenMeta},
14    plot::{Plot, PlotKind, foreach_plot},
15    tile::TileKind,
16};
17use crate::{
18    Canvas, IndexRef, Land,
19    config::CONFIG,
20    sim::Path,
21    util::{CARDINALS, DHashSet, Grid, SQUARE_4, SQUARE_9, attempt},
22};
23use common::{
24    astar::Astar,
25    calendar::Calendar,
26    comp::Alignment,
27    generation::{EntityInfo, EntitySpawn},
28    lottery::Lottery,
29    map::MarkerKind,
30    spiral::Spiral2d,
31    store::{Id, Store},
32    terrain::{
33        Block, BlockKind, SiteKindMeta, SpriteKind, TerrainChunkSize,
34        site::{DungeonKindMeta, SettlementKindMeta},
35    },
36    util::Dir2,
37    vol::RectVolSize,
38};
39use hashbrown::DefaultHashBuilder;
40use namegen::NameGen;
41use rand::{SeedableRng, prelude::*, seq::IndexedRandom};
42use rand_chacha::ChaChaRng;
43use std::ops::Range;
44use vek::*;
45
46/// Seed a new RNG from an old RNG, thereby making the old RNG independent of
47/// changing use of the new RNG. The practical effect of this is to reduce the
48/// extent to which changes to child generation algorithm produce a 'butterfly
49/// effect' on their parent generators, meaning that generators will be less
50/// likely to produce entirely different outcomes if some detail of a generation
51/// algorithm changes slightly. This is generally good and makes worldgen code
52/// easier to maintain and less liable to breaking changes.
53fn reseed<R: Rng>(rng: &mut R) -> impl Rng + use<R> {
54    ChaChaRng::from_seed(rng.random::<[u8; 32]>())
55}
56
57pub struct SpawnRules {
58    pub trees: bool,
59    pub max_warp: f32,
60    pub paths: bool,
61    pub waypoints: bool,
62}
63
64impl SpawnRules {
65    #[must_use]
66    pub fn combine(self, other: Self) -> Self {
67        // Should be commutative
68        Self {
69            trees: self.trees && other.trees,
70            max_warp: self.max_warp.min(other.max_warp),
71            paths: self.paths && other.paths,
72            waypoints: self.waypoints && other.waypoints,
73        }
74    }
75}
76
77impl Default for SpawnRules {
78    fn default() -> Self {
79        Self {
80            trees: true,
81            max_warp: 1.0,
82            paths: true,
83            waypoints: true,
84        }
85    }
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89pub enum SiteKind {
90    Refactor,
91    CliffTown,
92    SavannahTown,
93    DesertCity,
94    ChapelSite,
95    DwarvenMine,
96    CoastalTown,
97    Citadel,
98    Terracotta,
99    GiantTree,
100    Gnarling,
101    Bridge(Vec2<i32>, Vec2<i32>),
102    Adlet,
103    Haniwa,
104    PirateHideout,
105    JungleRuin,
106    RockCircle,
107    TrollCave,
108    Camp,
109    Cultist,
110    Sahagin,
111    VampireCastle,
112    GliderCourse,
113    Myrmidon,
114}
115
116impl SiteKind {
117    pub fn meta(&self) -> Option<SiteKindMeta> {
118        match self {
119            SiteKind::Refactor => Some(SiteKindMeta::Settlement(SettlementKindMeta::Default)),
120            SiteKind::CliffTown => Some(SiteKindMeta::Settlement(SettlementKindMeta::CliffTown)),
121            SiteKind::SavannahTown => {
122                Some(SiteKindMeta::Settlement(SettlementKindMeta::SavannahTown))
123            },
124            SiteKind::CoastalTown => {
125                Some(SiteKindMeta::Settlement(SettlementKindMeta::CoastalTown))
126            },
127            SiteKind::DesertCity => Some(SiteKindMeta::Settlement(SettlementKindMeta::DesertCity)),
128            SiteKind::Gnarling => Some(SiteKindMeta::Dungeon(DungeonKindMeta::Gnarling)),
129            SiteKind::Adlet => Some(SiteKindMeta::Dungeon(DungeonKindMeta::Adlet)),
130            SiteKind::Terracotta => Some(SiteKindMeta::Dungeon(DungeonKindMeta::Terracotta)),
131            SiteKind::Haniwa => Some(SiteKindMeta::Dungeon(DungeonKindMeta::Haniwa)),
132            SiteKind::Myrmidon => Some(SiteKindMeta::Dungeon(DungeonKindMeta::Myrmidon)),
133            SiteKind::DwarvenMine => Some(SiteKindMeta::Dungeon(DungeonKindMeta::DwarvenMine)),
134            SiteKind::ChapelSite => Some(SiteKindMeta::Dungeon(DungeonKindMeta::SeaChapel)),
135            SiteKind::Cultist => Some(SiteKindMeta::Dungeon(DungeonKindMeta::Cultist)),
136            SiteKind::Sahagin => Some(SiteKindMeta::Dungeon(DungeonKindMeta::Sahagin)),
137            SiteKind::VampireCastle => Some(SiteKindMeta::Dungeon(DungeonKindMeta::VampireCastle)),
138
139            _ => None,
140        }
141    }
142
143    pub fn should_do_economic_simulation(&self) -> bool {
144        matches!(
145            self,
146            SiteKind::Refactor
147                | SiteKind::CliffTown
148                | SiteKind::SavannahTown
149                | SiteKind::CoastalTown
150                | SiteKind::DesertCity
151        )
152    }
153
154    pub fn marker(&self) -> Option<MarkerKind> {
155        match self {
156            SiteKind::Refactor
157            | SiteKind::CliffTown
158            | SiteKind::SavannahTown
159            | SiteKind::CoastalTown
160            | SiteKind::DesertCity => Some(MarkerKind::Town),
161            SiteKind::Citadel => Some(MarkerKind::Castle),
162            SiteKind::Bridge(_, _) => Some(MarkerKind::Bridge),
163            SiteKind::GiantTree => Some(MarkerKind::Tree),
164            SiteKind::Gnarling => Some(MarkerKind::Gnarling),
165            SiteKind::DwarvenMine => Some(MarkerKind::DwarvenMine),
166            SiteKind::ChapelSite => Some(MarkerKind::ChapelSite),
167            SiteKind::Terracotta => Some(MarkerKind::Terracotta),
168            SiteKind::GliderCourse => Some(MarkerKind::GliderCourse),
169            SiteKind::Cultist => Some(MarkerKind::Cultist),
170            SiteKind::Sahagin => Some(MarkerKind::Sahagin),
171            SiteKind::Myrmidon => Some(MarkerKind::Myrmidon),
172            SiteKind::Adlet => Some(MarkerKind::Adlet),
173            SiteKind::Haniwa => Some(MarkerKind::Haniwa),
174            SiteKind::VampireCastle => Some(MarkerKind::VampireCastle),
175
176            SiteKind::PirateHideout
177            | SiteKind::JungleRuin
178            | SiteKind::RockCircle
179            | SiteKind::TrollCave
180            | SiteKind::Camp => None,
181        }
182    }
183}
184
185#[derive(Default)]
186pub struct Site {
187    pub origin: Vec2<i32>,
188    name: Option<String>,
189    // NOTE: Do we want these to be public?
190    pub tiles: TileGrid,
191    pub plots: Store<Plot>,
192    pub plazas: Vec<Id<Plot>>,
193    pub roads: Vec<Id<Plot>>,
194    pub economy: Option<Box<Economy>>,
195    pub kind: Option<SiteKind>,
196}
197
198impl Site {
199    pub fn filter_plots<'a, F: FnMut(&'a Plot) -> bool>(
200        &'a self,
201        mut f: F,
202    ) -> std::iter::Filter<impl ExactSizeIterator<Item = &'a Plot>, impl FnMut(&&'a Plot) -> bool>
203    {
204        self.plots.values().filter(move |p| f(p))
205    }
206
207    pub fn any_plot<F: FnMut(&Plot) -> bool>(&self, f: F) -> bool { self.plots.values().any(f) }
208
209    pub fn meta(&self) -> Option<SiteKindMeta> { self.kind.and_then(|s| s.meta()) }
210
211    pub fn economy_mut(&mut self) -> &mut Economy { self.economy.get_or_insert_default() }
212
213    pub fn do_economic_simulation(&self) -> bool {
214        self.kind.is_some_and(|s| s.should_do_economic_simulation())
215    }
216
217    pub fn trade_information(&self, id: Id<Site>) -> Option<common::trade::SiteInformation> {
218        self.economy
219            .as_ref()
220            .map(|econ| common::trade::SiteInformation {
221                id: id.id(),
222                unconsumed_stock: econ.get_available_stock(),
223            })
224    }
225
226    pub fn radius(&self) -> f32 {
227        ((self
228            .tiles
229            .bounds
230            .min
231            .map(|e| e.abs())
232            .reduce_max()
233            .max(self.tiles.bounds.max.map(|e| e.abs()).reduce_max())
234            // Temporary solution for giving giant_tree's leaves enough space to be painted correctly
235            // TODO: This will have to be replaced by a system as described on discord :
236            // https://discord.com/channels/449602562165833758/450064928720814081/937044837461536808
237            + if self
238                .plots
239                .values()
240                .any(|p| matches!(&p.kind, PlotKind::GiantTree(_)))
241            {
242                // 25 Seems to be big enough for the current scale of 4.0
243                25
244            } else {
245                5
246            })
247            * TILE_SIZE as i32) as f32
248    }
249
250    pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
251        let tile_pos = self.wpos_tile_pos(wpos);
252        let max_warp = SQUARE_9
253            .iter()
254            .filter_map(|rpos| {
255                let tile_pos = tile_pos + rpos;
256                if self.tiles.get(tile_pos).is_natural() {
257                    None
258                } else {
259                    let clamped =
260                        wpos.clamped(self.tile_wpos(tile_pos), self.tile_wpos(tile_pos + 1) - 1);
261                    Some(clamped.as_::<f32>().distance_squared(wpos.as_::<f32>()))
262                }
263            })
264            .min_by_key(|d2| *d2 as i32)
265            .map(|d2| d2.sqrt() / TILE_SIZE as f32)
266            .unwrap_or(1.0);
267        let base_spawn_rules = SpawnRules {
268            trees: max_warp == 1.0,
269            max_warp,
270            paths: max_warp > f32::EPSILON,
271            waypoints: true,
272        };
273        self.plots
274            .values()
275            .filter_map(|plot| match &plot.kind {
276                PlotKind::Gnarling(g) => Some(g.spawn_rules(wpos)),
277                PlotKind::Adlet(ad) => Some(ad.spawn_rules(wpos)),
278                PlotKind::SeaChapel(p) => Some(p.spawn_rules(wpos)),
279                PlotKind::Haniwa(ha) => Some(ha.spawn_rules(wpos)),
280                PlotKind::TerracottaPalace(tp) => Some(tp.spawn_rules(wpos)),
281                PlotKind::TerracottaHouse(th) => Some(th.spawn_rules(wpos)),
282                PlotKind::TerracottaYard(ty) => Some(ty.spawn_rules(wpos)),
283                PlotKind::Cultist(cl) => Some(cl.spawn_rules(wpos)),
284                PlotKind::Sahagin(sg) => Some(sg.spawn_rules(wpos)),
285                PlotKind::DwarvenMine(dm) => Some(dm.spawn_rules(wpos)),
286                PlotKind::VampireCastle(vc) => Some(vc.spawn_rules(wpos)),
287                PlotKind::MyrmidonArena(ma) => Some(ma.spawn_rules(wpos)),
288                PlotKind::MyrmidonHouse(mh) => Some(mh.spawn_rules(wpos)),
289                PlotKind::AirshipDock(ad) => Some(ad.spawn_rules(wpos)),
290                PlotKind::CoastalAirshipDock(cad) => Some(cad.spawn_rules(wpos)),
291                PlotKind::CliffTownAirshipDock(clad) => Some(clad.spawn_rules(wpos)),
292                PlotKind::DesertCityAirshipDock(dcad) => Some(dcad.spawn_rules(wpos)),
293                PlotKind::SavannahAirshipDock(sad) => Some(sad.spawn_rules(wpos)),
294                _ => None,
295            })
296            .fold(base_spawn_rules, |a, b| a.combine(b))
297    }
298
299    pub fn bounds(&self) -> Aabr<i32> {
300        let border = 1;
301        Aabr {
302            min: self.tile_wpos(self.tiles.bounds.min - border),
303            max: self.tile_wpos(self.tiles.bounds.max + 1 + border),
304        }
305    }
306
307    pub fn plot(&self, id: Id<Plot>) -> &Plot { &self.plots[id] }
308
309    pub fn plots(&self) -> impl ExactSizeIterator<Item = &Plot> + '_ { self.plots.values() }
310
311    pub fn plazas(&self) -> impl ExactSizeIterator<Item = Id<Plot>> + '_ {
312        self.plazas.iter().copied()
313    }
314
315    pub fn create_plot(&mut self, plot: Plot) -> Id<Plot> { self.plots.insert(plot) }
316
317    pub fn blit_aabr(&mut self, aabr: Aabr<i32>, tile: Tile) {
318        for y in 0..aabr.size().h {
319            for x in 0..aabr.size().w {
320                self.tiles.set(aabr.min + Vec2::new(x, y), tile.clone());
321            }
322        }
323    }
324
325    pub fn create_road(
326        &mut self,
327        land: &Land,
328        a: Vec2<i32>,
329        b: Vec2<i32>,
330        w: u16,
331        kind: plot::RoadKind,
332    ) -> Option<Id<Plot>> {
333        const MAX_ITERS: usize = 4096;
334        let range = &(-(w as i32) / 2..w as i32 - (w as i32 + 1) / 2);
335        // Manhattan distance.
336        let heuristic =
337            |(tile, _): &(Vec2<i32>, Vec2<i32>)| (tile - b).map(|e| e.abs()).sum() as f32;
338        let (path, _cost) = Astar::new(MAX_ITERS, (a, Vec2::zero()), DefaultHashBuilder::default())
339            .poll(
340                MAX_ITERS,
341                &heuristic,
342                |(tile, prev_dir)| {
343                    let tile = *tile;
344                    let prev_dir = *prev_dir;
345                    let this = &self;
346                    CARDINALS.iter().map(move |dir| {
347                        let neighbor = (tile + *dir, *dir);
348
349                        // Transition cost
350                        let alt_a = land.get_alt_approx(this.tile_center_wpos(tile));
351                        let alt_b = land.get_alt_approx(this.tile_center_wpos(neighbor.0));
352                        let mut cost = 1.0
353                            + (alt_a - alt_b).abs() / TILE_SIZE as f32
354                            + (prev_dir != *dir) as i32 as f32;
355
356                        for i in range.clone() {
357                            let orth = dir.yx() * i;
358                            let tile = this.tiles.get(neighbor.0 + orth);
359                            if tile.is_obstacle() {
360                                cost += 1000.0;
361                            } else if !tile.is_empty() && !tile.is_road() {
362                                cost += 25.0;
363                            }
364                        }
365
366                        (neighbor, cost)
367                    })
368                },
369                |(tile, _)| *tile == b,
370            )
371            .into_path()?;
372
373        let plot = self.create_plot(Plot {
374            kind: PlotKind::Road(plot::Road {
375                path: path.iter().map(|(tile, _)| *tile).collect(),
376                kind,
377            }),
378            root_tile: a,
379            tiles: path.iter().map(|(tile, _)| *tile).collect(),
380        });
381
382        self.roads.push(plot);
383
384        for (i, (tile, _)) in path.iter().enumerate() {
385            for y in range.clone() {
386                for x in range.clone() {
387                    let tile = tile + Vec2::new(x, y);
388                    if matches!(
389                        self.tiles.get(tile).kind,
390                        TileKind::Empty | TileKind::Path { .. }
391                    ) {
392                        self.tiles.set(tile, Tile {
393                            kind: TileKind::Road {
394                                a: i.saturating_sub(1) as u16,
395                                b: (i + 1).min(path.len() - 1) as u16,
396                                w,
397                            },
398                            plot: Some(plot),
399                            hard_alt: Some(land.get_alt_approx(self.tile_center_wpos(tile)) as i32),
400                        });
401                    }
402                }
403            }
404        }
405
406        Some(plot)
407    }
408
409    pub fn find_aabr(
410        &mut self,
411        search_pos: Vec2<i32>,
412        area_range: Range<u32>,
413        min_dims: Extent2<u32>,
414    ) -> Option<(Aabr<i32>, Vec2<i32>, Vec2<i32>, Option<i32>)> {
415        let ((aabr, (door_dir, hard_alt)), door_pos) =
416            self.tiles.find_near(search_pos, |center, _| {
417                let dir = CARDINALS
418                    .iter()
419                    .find(|dir| self.tiles.get(center + *dir).is_road())?;
420                let hard_alt = self.tiles.get(center + *dir).plot.and_then(|plot| {
421                    if let PlotKind::Plaza(p) = self.plots.get(plot).kind() {
422                        p.hard_alt
423                    } else {
424                        None
425                    }
426                });
427                self.tiles
428                    .grow_aabr(center, area_range.clone(), min_dims)
429                    .ok()
430                    .zip(Some((*dir, hard_alt)))
431            })?;
432        Some((aabr, door_pos, door_dir, hard_alt))
433    }
434
435    pub fn find_roadside_aabr(
436        &mut self,
437        rng: &mut impl Rng,
438        area_range: Range<u32>,
439        min_dims: Extent2<u32>,
440    ) -> Option<(Aabr<i32>, Vec2<i32>, Vec2<i32>, Option<i32>)> {
441        let dir = Vec2::<f32>::zero()
442            .map(|_| rng.random_range(-1.0..1.0))
443            .normalized();
444        let search_pos = if rng.random() {
445            let plaza = self.plot(*self.plazas.choose(rng)?);
446            let sz = plaza.find_bounds().size();
447            plaza.root_tile + dir.map(|e: f32| e.round() as i32) * (sz + 1)
448        } else if let PlotKind::Road(plot::Road { path, .. }) =
449            &self.plot(*self.roads.choose(rng)?).kind
450        {
451            *path.nodes().choose(rng)? + (dir * 1.0).map(|e: f32| e.round() as i32)
452        } else {
453            unreachable!()
454        };
455
456        self.find_aabr(search_pos, area_range, min_dims)
457    }
458
459    pub fn find_rural_aabr(
460        &mut self,
461        rng: &mut impl Rng,
462        area_range: Range<u32>,
463        min_dims: Extent2<u32>,
464    ) -> Option<(Aabr<i32>, Vec2<i32>, Vec2<i32>, Option<i32>)> {
465        // Choose a random plaza as the center of our search
466        let search_center = self
467            .plazas
468            .choose(rng)
469            .map(|&p| self.plot(p).root_tile)
470            .unwrap_or_default();
471
472        // Search in a random direction from the plaza
473        let dir = Vec2::<f32>::zero()
474            .map(|_| rng.random_range(-1.0..1.0))
475            .normalized();
476        let search_offset = dir.map2(min_dims.into(), |e: f32, sz: u32| {
477            (e * sz as f32 * 0.75 + 10.0).round() as i32
478        });
479
480        self.find_aabr(search_center + search_offset, area_range, min_dims)
481    }
482
483    pub fn make_plaza_at(
484        &mut self,
485        land: &Land,
486        index: IndexRef,
487        tile_aabr: Aabr<i32>,
488        rng: &mut impl Rng,
489        road_kind: plot::RoadKind,
490    ) -> Option<Id<Plot>> {
491        let tpos = tile_aabr.center();
492        let plaza_alt = land.get_alt_approx(self.tile_center_wpos(tpos)) as i32;
493
494        let plaza = self.create_plot(Plot {
495            kind: PlotKind::Plaza(plot::Plaza::generate(
496                tile_aabr, road_kind, self, land, index, rng,
497            )),
498            root_tile: tpos,
499            tiles: aabr_tiles(tile_aabr).collect(),
500        });
501        self.plazas.push(plaza);
502        self.blit_aabr(tile_aabr, Tile {
503            kind: TileKind::Plaza,
504            plot: Some(plaza),
505            hard_alt: Some(plaza_alt),
506        });
507
508        let mut already_pathed = vec![];
509        // One major, one minor road
510        for _ in (0..rng.random_range(1.25..2.25) as u16).rev() {
511            if let Some(&p) = self
512                .plazas
513                .iter()
514                .filter(|&&p| {
515                    !already_pathed.contains(&p)
516                        && p != plaza
517                        && already_pathed.iter().all(|&ap| {
518                            (self.plot(ap).root_tile - tpos)
519                                .map(|e| e as f32)
520                                .normalized()
521                                .dot(
522                                    (self.plot(p).root_tile - tpos)
523                                        .map(|e| e as f32)
524                                        .normalized(),
525                                )
526                                < 0.0
527                        })
528                })
529                .min_by_key(|&&p| self.plot(p).root_tile.distance_squared(tpos))
530            {
531                self.create_road(
532                    land,
533                    self.plot(p).root_tile,
534                    tpos,
535                    2, /* + i */
536                    road_kind,
537                );
538                already_pathed.push(p);
539            } else {
540                break;
541            }
542        }
543
544        Some(plaza)
545    }
546
547    pub fn make_plaza(
548        &mut self,
549        land: &Land,
550        index: IndexRef,
551        rng: &mut impl Rng,
552        generator_stats: &mut SitesGenMeta,
553        site_name: &str,
554        road_kind: plot::RoadKind,
555    ) -> Option<Id<Plot>> {
556        generator_stats.attempt(site_name, GenStatPlotKind::Plaza);
557        let plaza_radius = rng.random_range(1..4);
558        let plaza_dist = 6.5 + plaza_radius as f32 * 3.0;
559        let aabr = attempt(32, || {
560            self.plazas
561                .choose(rng)
562                .map(|&p| {
563                    self.plot(p).root_tile
564                        + (Vec2::new(rng.random_range(-1.0..1.0), rng.random_range(-1.0..1.0))
565                            .normalized()
566                            * plaza_dist)
567                            .map(|e| e as i32)
568                })
569                .or_else(|| Some(Vec2::zero()))
570                .map(|center_tile| Aabr {
571                    min: center_tile + Vec2::broadcast(-plaza_radius),
572                    max: center_tile + Vec2::broadcast(plaza_radius + 1),
573                })
574                .filter(|&aabr| {
575                    rng.random_range(0..48) > aabr.center().map(|e| e.abs()).reduce_max()
576                        && aabr_tiles(aabr).all(|tile| !self.tiles.get(tile).is_obstacle())
577                })
578                .filter(|&aabr| {
579                    self.plazas.iter().all(|&p| {
580                        let dist_sqr = if let PlotKind::Plaza(plaza) = &self.plot(p).kind {
581                            let intersection = plaza.aabr.intersection(aabr);
582                            // If the size of the intersection is greater than zero they intersect
583                            // on that axis and the distance on that axis is 0.
584                            intersection
585                                .size()
586                                .map(|e| e.min(0) as f32)
587                                .magnitude_squared()
588                        } else {
589                            let r = self.plot(p).root_tile();
590                            let closest_point = aabr.projected_point(r);
591                            closest_point.as_::<f32>().distance_squared(r.as_::<f32>())
592                        };
593                        dist_sqr > (plaza_dist * 0.85).powi(2)
594                    })
595                })
596        })?;
597        generator_stats.success(site_name, GenStatPlotKind::Plaza);
598        self.make_plaza_at(land, index, aabr, rng, road_kind)
599    }
600
601    pub fn demarcate_obstacles(&mut self, land: &Land) {
602        const SEARCH_RADIUS: u32 = 96;
603
604        Spiral2d::new()
605            .take((SEARCH_RADIUS * 2 + 1).pow(2) as usize)
606            .for_each(|tile| {
607                let wpos = self.tile_center_wpos(tile);
608                if let Some(kind) = Spiral2d::new()
609                    .take(9)
610                    .find_map(|rpos| wpos_is_hazard(land, wpos + rpos))
611                {
612                    for &rpos in &SQUARE_4 {
613                        // `get_mut` doesn't increase generation bounds
614                        self.tiles
615                            .get_mut(tile - rpos - 1)
616                            .filter(|tile| tile.is_natural())
617                            .map(|tile| tile.kind = TileKind::Hazard(kind));
618                    }
619                }
620                if let Some((_, path_wpos, path, _)) = land.get_nearest_path(wpos) {
621                    let tile_aabr = Aabr {
622                        min: self.tile_wpos(tile),
623                        max: self.tile_wpos(tile + 1) - 1,
624                    };
625
626                    if tile_aabr
627                        .projected_point(path_wpos.as_())
628                        .as_()
629                        .distance_squared(path_wpos)
630                        < path.width.powi(2)
631                    {
632                        self.tiles
633                            .get_mut(tile)
634                            .filter(|tile| tile.is_natural())
635                            .map(|tile| {
636                                tile.kind = TileKind::Path {
637                                    closest_pos: path_wpos,
638                                    path,
639                                }
640                            });
641                    }
642                }
643            });
644    }
645
646    /// The find_roadside_aabr function wants to have an existing plaza or road.
647    /// This function is used to find a suitable location for the first plaza in
648    /// a town, which has the side-effect of creating at least one road.
649    /// This function is more expensive than the make_plaza function but
650    /// fails to find a plaza location only if there are no suitable
651    /// locations within the entire search radius.
652    ///
653    /// It works by exhaustively finding all tiles within a ring pattern around
654    /// the town center where the tile and all surrounding tiles to the
655    /// plaza radius are not hazards or roads. It then chooses the tile with
656    /// the minimum distance from the town center as the plaza location. See
657    /// the comments in common/src/spiral.rs for more information on the spiral
658    /// ring pattern.
659    ///
660    /// demarcate_obstacles() should be called before this function to mark the
661    /// obstacles and roads. (Cliff Towns are an exception).
662    pub fn make_initial_plaza(
663        &mut self,
664        land: &Land,
665        index: IndexRef,
666        rng: &mut impl Rng,
667        plaza_radius: u32,
668        search_inner_radius: u32,
669        search_width: u32,
670        generator_stats: &mut SitesGenMeta,
671        site_name: &str,
672        road_kind: plot::RoadKind,
673    ) -> Option<Id<Plot>> {
674        generator_stats.attempt(site_name, GenStatPlotKind::InitialPlaza);
675        // Find all the suitable locations for a plaza.
676        let mut plaza_locations = vec![];
677        // Search over a spiral ring pattern
678        Spiral2d::with_ring(search_inner_radius, search_width).for_each(|tpos| {
679            let aabr = Aabr {
680                min: tpos - Vec2::broadcast(plaza_radius as i32),
681                max: tpos + Vec2::broadcast(plaza_radius as i32 + 1),
682            };
683            // if all the tiles in the proposed plaza location are also not hazards or roads
684            // then add the tile as a candidate for a plaza location
685            if aabr_tiles(aabr).all(|tpos| self.tiles.get(tpos).is_empty()) {
686                plaza_locations.push(aabr);
687            }
688        });
689        if plaza_locations.is_empty() {
690            // No suitable plaza locations were found, it's unlikely that the town will be
691            // able to be generated, but we can try to make a plaza anyway with
692            // the original make_plaza function.
693            self.make_plaza(land, index, rng, generator_stats, site_name, road_kind)
694        } else {
695            // Choose the minimum distance from the town center.
696            plaza_locations.sort_by_key(|&aabr| {
697                aabr.min
698                    .map2(aabr.max, |a, b| a.abs().min(b.abs()))
699                    .magnitude_squared()
700            });
701            // use the first plaza location as the plaza position
702            let aabr = plaza_locations.first()?;
703            generator_stats.success(site_name, GenStatPlotKind::InitialPlaza);
704            self.make_plaza_at(land, index, *aabr, rng, road_kind)
705        }
706    }
707
708    /// This is make_initial_plaza with default options/parameters. This calls
709    /// make_initial_plaza with the default parameters for the plaza_radius
710    /// and search_inner_radius. The plaza_radius will be in the range 1-3,
711    /// and the search_inner_radius will be 7 + plaza_radius. The search_width
712    /// will be PLAZA_MAX_SEARCH_RADIUS - search_inner_radius. The
713    /// search_inner_radius is approximately the same distance
714    /// from the center of town as for the original make_plaza function, so this
715    /// function will place the initial plaza and roads near where the
716    /// original make_plaza function would place them in the case where the site
717    /// is clear of hazards.
718    ///
719    /// This default plaza generation function is used for generating cities,
720    /// cliff towns, savannah towns, and coastal towns. The other town types
721    /// (terracotta, myrmidon, desert city) have a central feature so they
722    /// use specific plaza generation parameters and call the make_initial_plaza
723    /// function directly.
724    ///
725    /// demarcate_obstacles() should be called before this function to mark the
726    /// obstacles and roads.
727    pub fn make_initial_plaza_default(
728        &mut self,
729        land: &Land,
730        index: IndexRef,
731        rng: &mut impl Rng,
732        generator_stats: &mut SitesGenMeta,
733        site_name: &str,
734        road_kind: plot::RoadKind,
735    ) -> Option<Id<Plot>> {
736        // The plaza radius can be 1, 2, or 3.
737        let plaza_radius = rng.random_range(1..4);
738        // look for plaza locations within a ring with an outer dimension
739        // of 24 tiles and an inner dimension that will offset the plaza from the town
740        // center.
741        let search_inner_radius = 7 + plaza_radius;
742        const PLAZA_MAX_SEARCH_RADIUS: u32 = 24;
743        self.make_initial_plaza(
744            land,
745            index,
746            rng,
747            plaza_radius,
748            search_inner_radius,
749            PLAZA_MAX_SEARCH_RADIUS - search_inner_radius,
750            generator_stats,
751            site_name,
752            road_kind,
753        )
754    }
755
756    pub fn name(&self) -> Option<&str> { self.name.as_deref() }
757
758    pub fn generate_mine(land: &Land, rng: &mut impl Rng, origin: Vec2<i32>) -> Self {
759        let mut rng = reseed(rng);
760        let mut site = Site {
761            origin,
762            kind: Some(SiteKind::DwarvenMine),
763            ..Site::default()
764        };
765
766        let size = 60.0;
767
768        let aabr = Aabr {
769            min: Vec2::broadcast(-size as i32),
770            max: Vec2::broadcast(size as i32),
771        };
772
773        let wpos: Vec2<i32> = [1, 2].into();
774
775        let dwarven_mine =
776            plot::DwarvenMine::generate(land, &mut reseed(&mut rng), &site, wpos, aabr);
777        site.name = Some(dwarven_mine.name().to_string());
778        let plot = site.create_plot(Plot {
779            kind: PlotKind::DwarvenMine(dwarven_mine),
780            root_tile: aabr.center(),
781            tiles: aabr_tiles(aabr).collect(),
782        });
783
784        site.blit_aabr(aabr, Tile {
785            kind: TileKind::Empty,
786            plot: Some(plot),
787            hard_alt: Some(1_i32),
788        });
789
790        site
791    }
792
793    pub fn generate_citadel(land: &Land, rng: &mut impl Rng, origin: Vec2<i32>) -> Self {
794        let mut rng = reseed(rng);
795        let mut site = Site {
796            origin,
797            kind: Some(SiteKind::Citadel),
798            ..Site::default()
799        };
800        site.demarcate_obstacles(land);
801        let citadel = plot::Citadel::generate(origin, land, &mut rng);
802        site.name = Some(citadel.name().to_string());
803        let size = citadel.radius() / tile::TILE_SIZE as i32;
804        let aabr = Aabr {
805            min: Vec2::broadcast(-size),
806            max: Vec2::broadcast(size),
807        };
808        let plot = site.create_plot(Plot {
809            kind: PlotKind::Citadel(citadel),
810            root_tile: aabr.center(),
811            tiles: aabr_tiles(aabr).collect(),
812        });
813        site.blit_aabr(aabr, Tile {
814            kind: TileKind::Building,
815            plot: Some(plot),
816            hard_alt: None,
817        });
818        site
819    }
820
821    pub fn generate_gnarling(land: &Land, rng: &mut impl Rng, origin: Vec2<i32>) -> Self {
822        let mut rng = reseed(rng);
823        let mut site = Site {
824            origin,
825            kind: Some(SiteKind::Gnarling),
826            ..Site::default()
827        };
828        site.demarcate_obstacles(land);
829        let gnarling_fortification = plot::GnarlingFortification::generate(origin, land, &mut rng);
830        site.name = Some(gnarling_fortification.name().to_string());
831        let size = gnarling_fortification.radius() / TILE_SIZE as i32;
832        let aabr = Aabr {
833            min: Vec2::broadcast(-size),
834            max: Vec2::broadcast(size),
835        };
836        let plot = site.create_plot(Plot {
837            kind: PlotKind::Gnarling(gnarling_fortification),
838            root_tile: aabr.center(),
839            tiles: aabr_tiles(aabr).collect(),
840        });
841        site.blit_aabr(aabr, Tile {
842            kind: TileKind::GnarlingFortification,
843            plot: Some(plot),
844            hard_alt: None,
845        });
846        site
847    }
848
849    pub fn generate_adlet(
850        land: &Land,
851        rng: &mut impl Rng,
852        origin: Vec2<i32>,
853        index: IndexRef,
854    ) -> Self {
855        let mut rng = reseed(rng);
856        let mut site = Site {
857            origin,
858            kind: Some(SiteKind::Adlet),
859            ..Site::default()
860        };
861        site.demarcate_obstacles(land);
862        let adlet_stronghold = plot::AdletStronghold::generate(origin, land, &mut rng, index);
863        site.name = Some(adlet_stronghold.name().to_string());
864        let (cavern_aabr, wall_aabr) = adlet_stronghold.plot_tiles(origin);
865        let plot = site.create_plot(Plot {
866            kind: PlotKind::Adlet(adlet_stronghold),
867            root_tile: cavern_aabr.center(),
868            tiles: aabr_tiles(cavern_aabr)
869                .chain(aabr_tiles(wall_aabr))
870                .collect(),
871        });
872        site.blit_aabr(cavern_aabr, Tile {
873            kind: TileKind::AdletStronghold,
874            plot: Some(plot),
875            hard_alt: None,
876        });
877        site.blit_aabr(wall_aabr, Tile {
878            kind: TileKind::AdletStronghold,
879            plot: Some(plot),
880            hard_alt: None,
881        });
882        site
883    }
884
885    pub fn generate_terracotta(
886        land: &Land,
887        index: IndexRef,
888        rng: &mut impl Rng,
889        origin: Vec2<i32>,
890        generator_stats: &mut SitesGenMeta,
891    ) -> Self {
892        let mut rng = reseed(rng);
893        let gen_name = NameGen::location(&mut rng).generate_terracotta();
894        let suffix = [
895            "Tombs",
896            "Necropolis",
897            "Ruins",
898            "Mausoleum",
899            "Cemetery",
900            "Burial Grounds",
901            "Remains",
902            "Temples",
903            "Gardens",
904        ]
905        .choose(&mut rng)
906        .unwrap();
907        let name = match rng.random_range(0..2) {
908            0 => format!("{} {}", gen_name, suffix),
909            _ => format!("{} of {}", suffix, gen_name),
910        };
911        let mut site = Site {
912            origin,
913            name: Some(name.clone()),
914            kind: Some(SiteKind::Terracotta),
915            ..Site::default()
916        };
917
918        // place the initial plaza
919        site.demarcate_obstacles(land);
920        // The terracotta_palace is 15 tiles in radius, so the plaza should be outside
921        // the palace.
922        const TERRACOTTA_PLAZA_RADIUS: u32 = 3;
923        const TERRACOTTA_PLAZA_SEARCH_INNER: u32 = 17;
924        const TERRACOTTA_PLAZA_SEARCH_WIDTH: u32 = 12;
925        generator_stats.add(site.name(), GenStatSiteKind::Terracotta);
926        let road_kind = plot::RoadKind {
927            lights: plot::RoadLights::Terracotta,
928            material: plot::RoadMaterial::Sandstone,
929        };
930        site.make_initial_plaza(
931            land,
932            index,
933            &mut rng,
934            TERRACOTTA_PLAZA_RADIUS,
935            TERRACOTTA_PLAZA_SEARCH_INNER,
936            TERRACOTTA_PLAZA_SEARCH_WIDTH,
937            generator_stats,
938            &name,
939            road_kind,
940        );
941
942        let size = 15.0 as i32;
943        let aabr = Aabr {
944            min: Vec2::broadcast(-size),
945            max: Vec2::broadcast(size),
946        };
947        {
948            let terracotta_palace =
949                plot::TerracottaPalace::generate(land, &mut reseed(&mut rng), &site, aabr);
950            let terracotta_palace_alt = terracotta_palace.alt;
951            let plot = site.create_plot(Plot {
952                kind: PlotKind::TerracottaPalace(terracotta_palace),
953                root_tile: aabr.center(),
954                tiles: aabr_tiles(aabr).collect(),
955            });
956
957            site.blit_aabr(aabr, Tile {
958                kind: TileKind::Building,
959                plot: Some(plot),
960                hard_alt: Some(terracotta_palace_alt),
961            });
962        }
963        let build_chance = Lottery::from(vec![(12.0, 1), (4.0, 2)]);
964        for _ in 0..16 {
965            match *build_chance.choose_seeded(rng.random()) {
966                1 => {
967                    // TerracottaHouse
968                    generator_stats.attempt(site.name(), GenStatPlotKind::House);
969                    let size = (9.0 + rng.random::<f32>().powf(5.0) * 1.5).round() as u32;
970                    if let Some((aabr, _, _, alt)) = attempt(32, || {
971                        site.find_roadside_aabr(
972                            &mut rng,
973                            9..(size + 1).pow(2),
974                            Extent2::broadcast(size),
975                        )
976                    }) {
977                        let terracotta_house = plot::TerracottaHouse::generate(
978                            land,
979                            &mut reseed(&mut rng),
980                            &site,
981                            aabr,
982                            alt,
983                        );
984                        let terracotta_house_alt = terracotta_house.alt;
985                        let plot = site.create_plot(Plot {
986                            kind: PlotKind::TerracottaHouse(terracotta_house),
987                            root_tile: aabr.center(),
988                            tiles: aabr_tiles(aabr).collect(),
989                        });
990
991                        site.blit_aabr(aabr, Tile {
992                            kind: TileKind::Building,
993                            plot: Some(plot),
994                            hard_alt: Some(terracotta_house_alt),
995                        });
996
997                        generator_stats.success(site.name(), GenStatPlotKind::House);
998                    } else {
999                        site.make_plaza(land, index, &mut rng, generator_stats, &name, road_kind);
1000                    }
1001                },
1002
1003                2 => {
1004                    // TerracottaYard
1005                    generator_stats.attempt(site.name(), GenStatPlotKind::Yard);
1006                    let size = (9.0 + rng.random::<f32>().powf(5.0) * 1.5).round() as u32;
1007                    if let Some((aabr, _, _, alt)) = attempt(32, || {
1008                        site.find_roadside_aabr(
1009                            &mut rng,
1010                            9..(size + 1).pow(2),
1011                            Extent2::broadcast(size),
1012                        )
1013                    }) {
1014                        let terracotta_yard = plot::TerracottaYard::generate(
1015                            land,
1016                            &mut reseed(&mut rng),
1017                            &site,
1018                            aabr,
1019                            alt,
1020                        );
1021                        let terracotta_yard_alt = terracotta_yard.alt;
1022                        let plot = site.create_plot(Plot {
1023                            kind: PlotKind::TerracottaYard(terracotta_yard),
1024                            root_tile: aabr.center(),
1025                            tiles: aabr_tiles(aabr).collect(),
1026                        });
1027
1028                        site.blit_aabr(aabr, Tile {
1029                            kind: TileKind::Building,
1030                            plot: Some(plot),
1031                            hard_alt: Some(terracotta_yard_alt),
1032                        });
1033
1034                        generator_stats.success(site.name(), GenStatPlotKind::Yard);
1035                    } else {
1036                        site.make_plaza(land, index, &mut rng, generator_stats, &name, road_kind);
1037                    }
1038                },
1039                _ => {},
1040            }
1041        }
1042        site
1043    }
1044
1045    pub fn generate_myrmidon(
1046        land: &Land,
1047        index: IndexRef,
1048        rng: &mut impl Rng,
1049        origin: Vec2<i32>,
1050        generator_stats: &mut SitesGenMeta,
1051    ) -> Self {
1052        let mut rng = reseed(rng);
1053        let gen_name = NameGen::location(&mut rng).generate_danari();
1054        let suffix = ["City", "Metropolis"].choose(&mut rng).unwrap();
1055        let name = match rng.random_range(0..2) {
1056            0 => format!("{} {}", gen_name, suffix),
1057            _ => format!("{} of {}", suffix, gen_name),
1058        };
1059        let mut site = Site {
1060            origin,
1061            name: Some(name.clone()),
1062            kind: Some(SiteKind::Myrmidon),
1063            ..Site::default()
1064        };
1065
1066        let road_kind = plot::RoadKind {
1067            lights: plot::RoadLights::Default,
1068            material: plot::RoadMaterial::Dirt,
1069        };
1070
1071        // place the initial plaza
1072        site.demarcate_obstacles(land);
1073        // The myrmidon_arena is 16 tiles in radius, so the plaza should be outside the
1074        // palace.
1075        const MYRMIDON_PLAZA_RADIUS: u32 = 3;
1076        const MYRMIDON_PLAZA_SEARCH_INNER: u32 = 18;
1077        const MYRMIDON_PLAZA_SEARCH_WIDTH: u32 = 12;
1078        generator_stats.add(site.name(), GenStatSiteKind::Myrmidon);
1079        generator_stats.attempt(site.name(), GenStatPlotKind::InitialPlaza);
1080        site.make_initial_plaza(
1081            land,
1082            index,
1083            &mut rng,
1084            MYRMIDON_PLAZA_RADIUS,
1085            MYRMIDON_PLAZA_SEARCH_INNER,
1086            MYRMIDON_PLAZA_SEARCH_WIDTH,
1087            generator_stats,
1088            &name,
1089            road_kind,
1090        );
1091
1092        let size = 16.0 as i32;
1093        let aabr = Aabr {
1094            min: Vec2::broadcast(-size),
1095            max: Vec2::broadcast(size),
1096        };
1097        {
1098            let myrmidon_arena =
1099                plot::MyrmidonArena::generate(land, &mut reseed(&mut rng), &site, aabr);
1100            let myrmidon_arena_alt = myrmidon_arena.alt;
1101            let plot = site.create_plot(Plot {
1102                kind: PlotKind::MyrmidonArena(myrmidon_arena),
1103                root_tile: aabr.center(),
1104                tiles: aabr_tiles(aabr).collect(),
1105            });
1106
1107            site.blit_aabr(aabr, Tile {
1108                kind: TileKind::Building,
1109                plot: Some(plot),
1110                hard_alt: Some(myrmidon_arena_alt),
1111            });
1112        }
1113        for _ in 0..30 {
1114            // MyrmidonHouse
1115            generator_stats.attempt(site.name(), GenStatPlotKind::House);
1116            let size = (9.0 + rng.random::<f32>().powf(5.0) * 1.5).round() as u32;
1117            if let Some((aabr, _, _, alt)) = attempt(32, || {
1118                site.find_roadside_aabr(&mut rng, 9..(size + 1).pow(2), Extent2::broadcast(size))
1119            }) {
1120                let myrmidon_house =
1121                    plot::MyrmidonHouse::generate(land, &mut reseed(&mut rng), &site, aabr, alt);
1122                let myrmidon_house_alt = myrmidon_house.alt;
1123                let plot = site.create_plot(Plot {
1124                    kind: PlotKind::MyrmidonHouse(myrmidon_house),
1125                    root_tile: aabr.center(),
1126                    tiles: aabr_tiles(aabr).collect(),
1127                });
1128
1129                site.blit_aabr(aabr, Tile {
1130                    kind: TileKind::Building,
1131                    plot: Some(plot),
1132                    hard_alt: Some(myrmidon_house_alt),
1133                });
1134
1135                generator_stats.success(site.name(), GenStatPlotKind::House);
1136            } else {
1137                site.make_plaza(land, index, &mut rng, generator_stats, &name, road_kind);
1138            }
1139        }
1140
1141        site
1142    }
1143
1144    pub fn generate_giant_tree(land: &Land, rng: &mut impl Rng, origin: Vec2<i32>) -> Self {
1145        let mut rng = reseed(rng);
1146        let mut site = Site {
1147            origin,
1148            kind: Some(SiteKind::GiantTree),
1149            ..Site::default()
1150        };
1151        site.demarcate_obstacles(land);
1152        let giant_tree = plot::GiantTree::generate(&site, Vec2::zero(), land, &mut rng);
1153        site.name = Some(giant_tree.name().to_string());
1154        let size = (giant_tree.radius() / TILE_SIZE as f32).ceil() as i32;
1155        let aabr = Aabr {
1156            min: Vec2::broadcast(-size),
1157            max: Vec2::broadcast(size) + 1,
1158        };
1159        let plot = site.create_plot(Plot {
1160            kind: PlotKind::GiantTree(giant_tree),
1161            root_tile: aabr.center(),
1162            tiles: aabr_tiles(aabr).collect(),
1163        });
1164        site.blit_aabr(aabr, Tile {
1165            kind: TileKind::Building,
1166            plot: Some(plot),
1167            hard_alt: None,
1168        });
1169        site
1170    }
1171
1172    // Size is 0..1
1173    pub fn generate_city(
1174        land: &Land,
1175        index: IndexRef,
1176        rng: &mut impl Rng,
1177        origin: Vec2<i32>,
1178        size: f32,
1179        calendar: Option<&Calendar>,
1180        generator_stats: &mut SitesGenMeta,
1181    ) -> Self {
1182        let mut rng = reseed(rng);
1183        let name = NameGen::location(&mut rng).generate_town();
1184        let mut site = Site {
1185            origin,
1186            name: Some(name.clone()),
1187            kind: Some(SiteKind::Refactor),
1188            ..Site::default()
1189        };
1190        let road_kind = plot::RoadKind {
1191            lights: plot::RoadLights::Default,
1192            material: plot::RoadMaterial::Cobblestone,
1193        };
1194
1195        // place the initial plaza
1196        site.demarcate_obstacles(land);
1197        generator_stats.add(site.name(), GenStatSiteKind::City);
1198        site.make_initial_plaza_default(land, index, &mut rng, generator_stats, &name, road_kind);
1199
1200        let build_chance = Lottery::from(vec![
1201            (64.0, 1), // house
1202            (5.0, 2),  // guard tower
1203            (25.0, 3), // field
1204            //(32.0, 4), // castle
1205            (5.0, 5),  // workshop
1206            (15.0, 6), // airship dock
1207            (15.0, 7), // tavern
1208            (5.0, 8),  // barn
1209        ]);
1210
1211        // These plots have minimums or limits.
1212        let mut workshops = 0;
1213        let mut castles = 0;
1214        let mut taverns = 0;
1215        let mut airship_docks = 0;
1216
1217        for _ in 0..(size * 200.0) as i32 {
1218            match *build_chance.choose_seeded(rng.random()) {
1219                // Workshop
1220                n if (n == 5 && workshops < (size * 5.0) as i32) || workshops == 0 => {
1221                    generator_stats.attempt(site.name(), GenStatPlotKind::Workshop);
1222                    let size = (3.0 + rng.random::<f32>().powf(5.0) * 1.5).round() as u32;
1223                    if let Some((aabr, door_tile, door_dir, alt)) = attempt(32, || {
1224                        site.find_roadside_aabr(
1225                            &mut rng,
1226                            4..(size + 1).pow(2),
1227                            Extent2::broadcast(size),
1228                        )
1229                    }) {
1230                        let workshop = plot::Workshop::generate(
1231                            land,
1232                            &mut reseed(&mut rng),
1233                            &site,
1234                            door_tile,
1235                            door_dir,
1236                            aabr,
1237                            alt,
1238                        );
1239                        let workshop_alt = workshop.alt;
1240                        let plot = site.create_plot(Plot {
1241                            kind: PlotKind::Workshop(workshop),
1242                            root_tile: aabr.center(),
1243                            tiles: aabr_tiles(aabr).collect(),
1244                        });
1245
1246                        site.blit_aabr(aabr, Tile {
1247                            kind: TileKind::Building,
1248                            plot: Some(plot),
1249                            hard_alt: Some(workshop_alt),
1250                        });
1251                        workshops += 1;
1252                        generator_stats.success(site.name(), GenStatPlotKind::Workshop);
1253                    } else {
1254                        site.make_plaza(land, index, &mut rng, generator_stats, &name, road_kind);
1255                    }
1256                },
1257                // House
1258                1 => {
1259                    let size = (1.5 + rng.random::<f32>().powf(5.0) * 1.0).round() as u32;
1260                    generator_stats.attempt(site.name(), GenStatPlotKind::House);
1261                    if let Some((aabr, door_tile, door_dir, alt)) = attempt(32, || {
1262                        site.find_roadside_aabr(
1263                            &mut rng,
1264                            4..(size + 1).pow(2),
1265                            Extent2::broadcast(size),
1266                        )
1267                    }) {
1268                        let house = plot::House::generate(
1269                            land,
1270                            &mut reseed(&mut rng),
1271                            &site,
1272                            door_tile,
1273                            door_dir,
1274                            aabr,
1275                            calendar,
1276                            alt,
1277                        );
1278                        let house_alt = house.alt;
1279                        let plot = site.create_plot(Plot {
1280                            kind: PlotKind::House(house),
1281                            root_tile: aabr.center(),
1282                            tiles: aabr_tiles(aabr).collect(),
1283                        });
1284
1285                        site.blit_aabr(aabr, Tile {
1286                            kind: TileKind::Building,
1287                            plot: Some(plot),
1288                            hard_alt: Some(house_alt),
1289                        });
1290                        generator_stats.success(site.name(), GenStatPlotKind::House);
1291                    } else {
1292                        site.make_plaza(land, index, &mut rng, generator_stats, &name, road_kind);
1293                    }
1294                },
1295                // Guard tower
1296                2 => {
1297                    generator_stats.attempt(site.name(), GenStatPlotKind::GuardTower);
1298                    if let Some((_aabr, _, _door_dir, _)) = attempt(10, || {
1299                        site.find_roadside_aabr(&mut rng, 4..4, Extent2::new(2, 2))
1300                    }) {
1301                        // let plot = site.create_plot(Plot {
1302                        //     kind: PlotKind::Castle(plot::Castle::generate(
1303                        //         land,
1304                        //         &mut rng,
1305                        //         &site,
1306                        //         aabr,
1307                        //     )),
1308                        //     root_tile: aabr.center(),
1309                        //     tiles: aabr_tiles(aabr).collect(),
1310                        // });
1311
1312                        // site.blit_aabr(aabr, Tile {
1313                        //     kind: TileKind::Castle,
1314                        //     plot: Some(plot),
1315                        //     hard_alt: None,
1316                        // });
1317                    }
1318                },
1319                // Field
1320                3 => {
1321                    Self::generate_farm(false, &mut rng, &mut site, land);
1322                },
1323                // Castle
1324                4 if size > 0.2 && castles < 1 => {
1325                    generator_stats.attempt(site.name(), GenStatPlotKind::Castle);
1326                    if let Some((aabr, _entrance_tile, _door_dir, _alt)) = attempt(64, || {
1327                        site.find_rural_aabr(&mut rng, 16 * 16..18 * 18, Extent2::new(16, 16))
1328                    }) {
1329                        let offset = rng.random_range(5..(aabr.size().w.min(aabr.size().h) - 4));
1330                        let gate_aabr = Aabr {
1331                            min: Vec2::new(aabr.min.x + offset - 1, aabr.min.y),
1332                            max: Vec2::new(aabr.min.x + offset + 2, aabr.min.y + 1),
1333                        };
1334                        let castle = plot::Castle::generate(land, &mut rng, &site, aabr, gate_aabr);
1335                        let castle_alt = castle.alt;
1336                        let plot = site.create_plot(Plot {
1337                            kind: PlotKind::Castle(castle),
1338                            root_tile: aabr.center(),
1339                            tiles: aabr_tiles(aabr).collect(),
1340                        });
1341
1342                        let wall_north = Tile {
1343                            kind: TileKind::Wall(Dir2::Y),
1344                            plot: Some(plot),
1345                            hard_alt: Some(castle_alt),
1346                        };
1347
1348                        let wall_east = Tile {
1349                            kind: TileKind::Wall(Dir2::X),
1350                            plot: Some(plot),
1351                            hard_alt: Some(castle_alt),
1352                        };
1353                        for x in 0..aabr.size().w {
1354                            site.tiles
1355                                .set(aabr.min + Vec2::new(x, 0), wall_east.clone());
1356                            site.tiles.set(
1357                                aabr.min + Vec2::new(x, aabr.size().h - 1),
1358                                wall_east.clone(),
1359                            );
1360                        }
1361                        for y in 0..aabr.size().h {
1362                            site.tiles
1363                                .set(aabr.min + Vec2::new(0, y), wall_north.clone());
1364                            site.tiles.set(
1365                                aabr.min + Vec2::new(aabr.size().w - 1, y),
1366                                wall_north.clone(),
1367                            );
1368                        }
1369
1370                        let gate = Tile {
1371                            kind: TileKind::Gate,
1372                            plot: Some(plot),
1373                            hard_alt: Some(castle_alt),
1374                        };
1375                        let tower_parapet = Tile {
1376                            kind: TileKind::Tower(RoofKind::Parapet),
1377                            plot: Some(plot),
1378                            hard_alt: Some(castle_alt),
1379                        };
1380                        let tower_pyramid = Tile {
1381                            kind: TileKind::Tower(RoofKind::Pyramid),
1382                            plot: Some(plot),
1383                            hard_alt: Some(castle_alt),
1384                        };
1385
1386                        site.tiles.set(
1387                            Vec2::new(aabr.min.x + offset - 2, aabr.min.y),
1388                            tower_parapet.clone(),
1389                        );
1390                        site.tiles
1391                            .set(Vec2::new(aabr.min.x + offset - 1, aabr.min.y), gate.clone());
1392                        site.tiles
1393                            .set(Vec2::new(aabr.min.x + offset, aabr.min.y), gate.clone());
1394                        site.tiles
1395                            .set(Vec2::new(aabr.min.x + offset + 1, aabr.min.y), gate.clone());
1396                        site.tiles.set(
1397                            Vec2::new(aabr.min.x + offset + 2, aabr.min.y),
1398                            tower_parapet.clone(),
1399                        );
1400
1401                        site.tiles
1402                            .set(Vec2::new(aabr.min.x, aabr.min.y), tower_parapet.clone());
1403                        site.tiles
1404                            .set(Vec2::new(aabr.max.x - 1, aabr.min.y), tower_parapet.clone());
1405                        site.tiles
1406                            .set(Vec2::new(aabr.min.x, aabr.max.y - 1), tower_parapet.clone());
1407                        site.tiles.set(
1408                            Vec2::new(aabr.max.x - 1, aabr.max.y - 1),
1409                            tower_parapet.clone(),
1410                        );
1411
1412                        // Courtyard
1413                        site.blit_aabr(
1414                            Aabr {
1415                                min: aabr.min + 1,
1416                                max: aabr.max - 1,
1417                            },
1418                            Tile {
1419                                kind: TileKind::Road { a: 0, b: 0, w: 0 },
1420                                plot: Some(plot),
1421                                hard_alt: Some(castle_alt),
1422                            },
1423                        );
1424
1425                        // Keep
1426                        site.blit_aabr(
1427                            Aabr {
1428                                min: aabr.center() - 3,
1429                                max: aabr.center() + 3,
1430                            },
1431                            Tile {
1432                                kind: TileKind::Wall(Dir2::Y),
1433                                plot: Some(plot),
1434                                hard_alt: Some(castle_alt),
1435                            },
1436                        );
1437                        site.tiles.set(
1438                            Vec2::new(aabr.center().x + 2, aabr.center().y + 2),
1439                            tower_pyramid.clone(),
1440                        );
1441                        site.tiles.set(
1442                            Vec2::new(aabr.center().x + 2, aabr.center().y - 3),
1443                            tower_pyramid.clone(),
1444                        );
1445                        site.tiles.set(
1446                            Vec2::new(aabr.center().x - 3, aabr.center().y + 2),
1447                            tower_pyramid.clone(),
1448                        );
1449                        site.tiles.set(
1450                            Vec2::new(aabr.center().x - 3, aabr.center().y - 3),
1451                            tower_pyramid.clone(),
1452                        );
1453
1454                        site.blit_aabr(
1455                            Aabr {
1456                                min: aabr.center() - 2,
1457                                max: aabr.center() + 2,
1458                            },
1459                            Tile {
1460                                kind: TileKind::Keep(KeepKind::Middle),
1461                                plot: Some(plot),
1462                                hard_alt: Some(castle_alt),
1463                            },
1464                        );
1465
1466                        castles += 1;
1467                        generator_stats.success(site.name(), GenStatPlotKind::Castle);
1468                    }
1469                },
1470                //airship dock
1471                6 if (size > 0.125 && airship_docks == 0) => {
1472                    generator_stats.attempt(site.name(), GenStatPlotKind::AirshipDock);
1473                    // The airship dock rendered size is 10x10.
1474                    // The rendering code allows for a margin of 1 tile
1475                    // and we want to keep the dock footprint as small as possible.
1476                    // The area range for the aabr is fixed at size squared (81) since the
1477                    // dock structure is square.
1478                    let size = 9u32;
1479                    if let Some((aabr, door_tile, door_dir, _)) = attempt(32, || {
1480                        site.find_roadside_aabr(&mut rng, 81..82, Extent2::broadcast(size))
1481                    }) {
1482                        let airship_dock = plot::AirshipDock::generate(
1483                            land,
1484                            index,
1485                            &mut reseed(&mut rng),
1486                            &site,
1487                            door_tile,
1488                            door_dir,
1489                            aabr,
1490                        );
1491                        let airship_dock_alt = airship_dock.alt;
1492                        let plot = site.create_plot(Plot {
1493                            kind: PlotKind::AirshipDock(airship_dock),
1494                            root_tile: aabr.center(),
1495                            tiles: aabr_tiles(aabr).collect(),
1496                        });
1497
1498                        site.blit_aabr(aabr, Tile {
1499                            kind: TileKind::Building,
1500                            plot: Some(plot),
1501                            hard_alt: Some(airship_dock_alt),
1502                        });
1503                        airship_docks += 1;
1504                        generator_stats.success(site.name(), GenStatPlotKind::AirshipDock);
1505                    } else {
1506                        site.make_plaza(land, index, &mut rng, generator_stats, &name, road_kind);
1507                    }
1508                },
1509                7 if (size > 0.125 && taverns < 2) => {
1510                    generator_stats.attempt(site.name(), GenStatPlotKind::Tavern);
1511                    let size = (4.5 + rng.random::<f32>().powf(5.0) * 2.0).round() as u32;
1512                    if let Some((aabr, door_tile, door_dir, alt)) = attempt(32, || {
1513                        site.find_roadside_aabr(
1514                            &mut rng,
1515                            8..(size + 1).pow(2),
1516                            Extent2::broadcast(size),
1517                        )
1518                    }) {
1519                        let tavern = plot::Tavern::generate(
1520                            land,
1521                            index,
1522                            &mut reseed(&mut rng),
1523                            &site,
1524                            door_tile,
1525                            Dir2::from_vec2(door_dir),
1526                            aabr,
1527                            alt,
1528                        );
1529                        let tavern_alt = tavern.door_wpos.z;
1530                        let plot = site.create_plot(Plot {
1531                            kind: PlotKind::Tavern(tavern),
1532                            root_tile: aabr.center(),
1533                            tiles: aabr_tiles(aabr).collect(),
1534                        });
1535
1536                        site.blit_aabr(aabr, Tile {
1537                            kind: TileKind::Building,
1538                            plot: Some(plot),
1539                            hard_alt: Some(tavern_alt),
1540                        });
1541
1542                        taverns += 1;
1543                        generator_stats.success(site.name(), GenStatPlotKind::Tavern);
1544                    } else {
1545                        site.make_plaza(land, index, &mut rng, generator_stats, &name, road_kind);
1546                    }
1547                },
1548                8 => {
1549                    Self::generate_barn(false, &mut rng, &mut site, land, index);
1550                },
1551                _ => {},
1552            }
1553        }
1554
1555        site
1556    }
1557
1558    pub fn generate_glider_course(
1559        land: &Land,
1560        _index: IndexRef,
1561        rng: &mut impl Rng,
1562        origin: Vec2<i32>,
1563    ) -> Self {
1564        let mut rng = reseed(rng);
1565        let mut site = Site {
1566            origin,
1567            kind: Some(SiteKind::GliderCourse),
1568            ..Site::default()
1569        };
1570
1571        // TODO use the nearest peak name. Unfortunately this requires `Civs` but we
1572        // only have access to `WorldSim`
1573        site.name = Some(NameGen::location(&mut rng).generate_town() + " Glider Course");
1574
1575        // Pick the starting downhill direction based on the average drop over
1576        // two chunks in the four cardinal directions
1577        let origin_alt = land.get_alt_approx(origin);
1578        let alt_drops: Vec<f32> = CARDINALS
1579            .iter()
1580            .map(|c| {
1581                origin_alt
1582                    - 0.5
1583                        * (land.get_alt_approx(origin + *c * TerrainChunkSize::RECT_SIZE.x as i32)
1584                            + land.get_alt_approx(
1585                                origin + 2 * *c * TerrainChunkSize::RECT_SIZE.x as i32,
1586                            ))
1587            })
1588            .collect();
1589        let mut cardinal = 0;
1590        let mut max_drop = 0.0;
1591        for (i, drop) in alt_drops.iter().enumerate() {
1592            if *drop > max_drop {
1593                max_drop = *drop;
1594                cardinal = i;
1595            }
1596        }
1597        let dir = match cardinal {
1598            0 => Dir2::X,
1599            1 => Dir2::Y,
1600            2 => Dir2::NegX,
1601            3 => Dir2::NegY,
1602            _ => Dir2::X,
1603        };
1604        let size = 2.0;
1605
1606        let mut valid_course = true;
1607        let mut positions = Vec::new();
1608
1609        // Platform
1610        let pos = origin;
1611        let tile_pos: Vec2<i32> = Vec2::zero();
1612        positions.push((pos, tile_pos));
1613
1614        // This defines the distance between rings
1615        // An offset of 5 results in courses that are about 1 minute long
1616        // An offset of 6+ results in not all plots being in range of the site
1617        const CHUNK_OFFSET: usize = 5;
1618        // WARNING: This assumes x and y lengths of a chunk are the same!!!
1619        let offset = CHUNK_OFFSET as i32 * TerrainChunkSize::RECT_SIZE.x as i32;
1620        // Always convert to tiles then back to wpos to remove any integer division
1621        // artifacts
1622        let tile_offset = offset / TILE_SIZE as i32;
1623        let pos_offset = tile_offset * TILE_SIZE as i32;
1624
1625        // Loop 1 is always straight forward from the launch platform
1626        let pos = origin + pos_offset * dir.to_vec2();
1627        let tile_pos = tile_offset * dir.to_vec2();
1628        positions.push((pos, tile_pos));
1629
1630        // Loops 2-9 follow the downhill path of terrain chunks
1631        // In the future it may be desirable to follow ridges and the like but that
1632        // would be a future MR
1633        let mut last_pos = pos;
1634        let mut last_tile_pos = tile_pos;
1635        for j in 1..(CHUNK_OFFSET * 9 + 1) {
1636            let c_downhill = land.get_chunk_wpos(last_pos).and_then(|c| c.downhill);
1637            if let Some(downhill) = c_downhill {
1638                let downhill_chunk =
1639                    downhill.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / (sz as i32));
1640                let downhill_chunk_pos = TerrainChunkSize::center_wpos(downhill_chunk);
1641                let downhill_vec = downhill_chunk_pos - last_pos;
1642                // Convert to tiles first, then back to wpos to ensure coordinates align, as
1643                // chunks are not tile aligned
1644                let tile_offset = downhill_vec / (TILE_SIZE as i32);
1645                let pos_offset = tile_offset * TILE_SIZE as i32;
1646                let pos = last_pos + pos_offset;
1647                let tile_pos = last_tile_pos + tile_offset;
1648                last_pos = pos;
1649                last_tile_pos = tile_pos;
1650                // Only want to save positions with large enough chunk offsets, not every chunk
1651                // position
1652                if j % CHUNK_OFFSET == 0 {
1653                    positions.push((pos, tile_pos));
1654                }
1655            } else {
1656                valid_course = false;
1657            }
1658        }
1659        // Currently there is no check to ensure the delta z between rings is
1660        // sufficient to successfully fly through the course. This should cause
1661        // no glider course site to be created. Right now it just doesn't spawn
1662        // one in the world (similar to towns when placed near/on bodies of water).
1663        // In the future maybe the generate functions should return an `Option`
1664        // instead of a `Site`
1665        if valid_course && positions.len() > 1 {
1666            for (i, window) in positions.windows(2).enumerate() {
1667                if !window.is_empty() {
1668                    let [(pos, tile_pos), (next_pos, next_tile_pos)] = window else {
1669                        panic!(
1670                            "previous condition required positions Vec to have at least two \
1671                             elements"
1672                        );
1673                    };
1674                    if i == 0 {
1675                        // Launch platform
1676                        let aabr = Aabr {
1677                            min: Vec2::broadcast(-size as i32),
1678                            max: Vec2::broadcast(size as i32),
1679                        };
1680                        let glider_platform = plot::GliderPlatform::generate(
1681                            land,
1682                            &mut reseed(&mut rng),
1683                            &site,
1684                            *pos,
1685                            dir,
1686                        );
1687                        let alt = glider_platform.alt - 5;
1688                        let plot = site.create_plot(Plot {
1689                            kind: PlotKind::GliderPlatform(glider_platform),
1690                            root_tile: aabr.center(),
1691                            tiles: aabr_tiles(aabr).collect(),
1692                        });
1693                        site.blit_aabr(aabr, Tile {
1694                            kind: TileKind::Building,
1695                            plot: Some(plot),
1696                            hard_alt: Some(alt),
1697                        });
1698                    } else if i < 9 {
1699                        // Point each ring after 1 towards the next ring
1700                        // This provides a subtle guide through the course
1701                        let dir = if i > 1 {
1702                            Dir2::from_vec2(next_pos - pos)
1703                        } else {
1704                            dir
1705                        };
1706                        let aabr = Aabr {
1707                            min: Vec2::broadcast(-size as i32) + tile_pos,
1708                            max: Vec2::broadcast(size as i32) + tile_pos,
1709                        };
1710                        let glider_ring = plot::GliderRing::generate(
1711                            land,
1712                            &mut reseed(&mut rng),
1713                            &site,
1714                            pos,
1715                            i,
1716                            dir,
1717                        );
1718                        let plot = site.create_plot(Plot {
1719                            kind: PlotKind::GliderRing(glider_ring),
1720                            root_tile: aabr.center(),
1721                            tiles: aabr_tiles(aabr).collect(),
1722                        });
1723                        site.blit_aabr(aabr, Tile {
1724                            kind: TileKind::Building,
1725                            plot: Some(plot),
1726                            hard_alt: None,
1727                        });
1728                    } else if i == 9 {
1729                        // last ring (ring 9) and finish platform
1730                        // Separate condition due to window iterator to ensure
1731                        // the finish platform is generated
1732                        let dir = Dir2::from_vec2(next_pos - pos);
1733                        let aabr = Aabr {
1734                            min: Vec2::broadcast(-size as i32) + tile_pos,
1735                            max: Vec2::broadcast(size as i32) + tile_pos,
1736                        };
1737                        let glider_ring = plot::GliderRing::generate(
1738                            land,
1739                            &mut reseed(&mut rng),
1740                            &site,
1741                            pos,
1742                            i,
1743                            dir,
1744                        );
1745                        let plot = site.create_plot(Plot {
1746                            kind: PlotKind::GliderRing(glider_ring),
1747                            root_tile: aabr.center(),
1748                            tiles: aabr_tiles(aabr).collect(),
1749                        });
1750                        site.blit_aabr(aabr, Tile {
1751                            kind: TileKind::Building,
1752                            plot: Some(plot),
1753                            hard_alt: None,
1754                        });
1755                        // Finish
1756                        let size = 10.0;
1757                        let aabr = Aabr {
1758                            min: Vec2::broadcast(-size as i32) + next_tile_pos,
1759                            max: Vec2::broadcast(size as i32) + next_tile_pos,
1760                        };
1761                        let glider_finish = plot::GliderFinish::generate(
1762                            land,
1763                            &mut reseed(&mut rng),
1764                            &site,
1765                            *next_pos,
1766                        );
1767                        let plot = site.create_plot(Plot {
1768                            kind: PlotKind::GliderFinish(glider_finish),
1769                            root_tile: aabr.center(),
1770                            tiles: aabr_tiles(aabr).collect(),
1771                        });
1772                        site.blit_aabr(aabr, Tile {
1773                            kind: TileKind::Building,
1774                            plot: Some(plot),
1775                            hard_alt: None,
1776                        });
1777                    }
1778                }
1779            }
1780        }
1781
1782        site
1783    }
1784
1785    pub fn generate_cliff_town(
1786        land: &Land,
1787        index: IndexRef,
1788        rng: &mut impl Rng,
1789        origin: Vec2<i32>,
1790        generator_stats: &mut SitesGenMeta,
1791    ) -> Self {
1792        let mut rng = reseed(rng);
1793        let name = NameGen::location(&mut rng).generate_arabic();
1794        let mut site = Site {
1795            origin,
1796            name: Some(name.clone()),
1797            kind: Some(SiteKind::CliffTown),
1798            ..Site::default()
1799        };
1800        let mut campfires = 0;
1801        let road_kind = plot::RoadKind {
1802            lights: plot::RoadLights::Default,
1803            material: plot::RoadMaterial::Sandstone,
1804        };
1805
1806        // place the initial plaza
1807        generator_stats.add(site.name(), GenStatSiteKind::CliffTown);
1808        site.make_initial_plaza_default(land, index, &mut rng, generator_stats, &name, road_kind);
1809
1810        let build_chance = Lottery::from(vec![(30.0, 1), (50.0, 2)]);
1811        let mut airship_docks = 0;
1812        for _ in 0..80 {
1813            match *build_chance.choose_seeded(rng.random()) {
1814                1 => {
1815                    // CliffTower
1816                    let size = (9.0 + rng.random::<f32>().powf(5.0) * 1.0).round() as u32;
1817                    generator_stats.attempt(site.name(), GenStatPlotKind::House);
1818                    let campfire = campfires < 4;
1819                    if let Some((aabr, door_tile, door_dir, alt)) = attempt(32, || {
1820                        site.find_roadside_aabr(
1821                            &mut rng,
1822                            8..(size + 1).pow(2),
1823                            Extent2::broadcast(size),
1824                        )
1825                    }) {
1826                        let cliff_tower = plot::CliffTower::generate(
1827                            land,
1828                            index,
1829                            &mut reseed(&mut rng),
1830                            &site,
1831                            door_tile,
1832                            door_dir,
1833                            aabr,
1834                            campfire,
1835                            alt,
1836                        );
1837                        let cliff_tower_alt = cliff_tower.alt;
1838                        let plot = site.create_plot(Plot {
1839                            kind: PlotKind::CliffTower(cliff_tower),
1840                            root_tile: aabr.center(),
1841                            tiles: aabr_tiles(aabr).collect(),
1842                        });
1843                        site.blit_aabr(aabr, Tile {
1844                            kind: TileKind::Building,
1845                            plot: Some(plot),
1846                            hard_alt: Some(cliff_tower_alt),
1847                        });
1848                        campfires += 1;
1849                        generator_stats.success(site.name(), GenStatPlotKind::House);
1850                    } else {
1851                        site.make_plaza(land, index, &mut rng, generator_stats, &name, road_kind);
1852                    }
1853                },
1854                2 if airship_docks < 1 => {
1855                    // CliffTownAirshipDock
1856                    let size = 25u32;
1857                    generator_stats.attempt(site.name(), GenStatPlotKind::AirshipDock);
1858                    if let Some((aabr, door_tile, door_dir, _)) = attempt(32, || {
1859                        site.find_roadside_aabr(&mut rng, 625..626, Extent2::broadcast(size))
1860                    }) {
1861                        let cliff_town_airship_dock = plot::CliffTownAirshipDock::generate(
1862                            land,
1863                            index,
1864                            &mut reseed(&mut rng),
1865                            &site,
1866                            door_tile,
1867                            door_dir,
1868                            aabr,
1869                        );
1870                        let cliff_town_airship_dock_alt = cliff_town_airship_dock.alt;
1871                        let plot = site.create_plot(Plot {
1872                            kind: PlotKind::CliffTownAirshipDock(cliff_town_airship_dock),
1873                            root_tile: aabr.center(),
1874                            tiles: aabr_tiles(aabr).collect(),
1875                        });
1876
1877                        site.blit_aabr(aabr, Tile {
1878                            kind: TileKind::Building,
1879                            plot: Some(plot),
1880                            hard_alt: Some(cliff_town_airship_dock_alt),
1881                        });
1882                        airship_docks += 1;
1883                        generator_stats.success(site.name(), GenStatPlotKind::AirshipDock);
1884                    } else {
1885                        site.make_plaza(land, index, &mut rng, generator_stats, &name, road_kind);
1886                    }
1887                },
1888                _ => {},
1889            }
1890        }
1891
1892        site.demarcate_obstacles(land);
1893        site
1894    }
1895
1896    pub fn generate_savannah_town(
1897        land: &Land,
1898        index: IndexRef,
1899        rng: &mut impl Rng,
1900        origin: Vec2<i32>,
1901        generator_stats: &mut SitesGenMeta,
1902    ) -> Self {
1903        let mut rng = reseed(rng);
1904        let name = NameGen::location(&mut rng).generate_savannah_custom();
1905        let mut site = Site {
1906            origin,
1907            name: Some(name.clone()),
1908            kind: Some(SiteKind::SavannahTown),
1909            ..Site::default()
1910        };
1911        let road_kind = plot::RoadKind {
1912            lights: plot::RoadLights::Default,
1913            material: plot::RoadMaterial::Dirt,
1914        };
1915
1916        // place the initial plaza
1917        site.demarcate_obstacles(land);
1918        generator_stats.add(site.name(), GenStatSiteKind::SavannahTown);
1919        site.make_initial_plaza_default(land, index, &mut rng, generator_stats, &name, road_kind);
1920
1921        let mut workshops = 0;
1922        let mut airship_dock = 0;
1923        let build_chance = Lottery::from(vec![
1924            (25.0, 1),
1925            (5.0, 2),
1926            (5.0, 3),
1927            (15.0, 4),
1928            (5.0, 5),
1929            (5.0, 6),
1930        ]);
1931
1932        for _ in 0..50 {
1933            match *build_chance.choose_seeded(rng.random()) {
1934                n if (n == 2 && workshops < 3) || workshops == 0 => {
1935                    // SavannahWorkshop
1936                    let size = (4.0 + rng.random::<f32>().powf(5.0) * 1.5).round() as u32;
1937                    generator_stats.attempt(site.name(), GenStatPlotKind::Workshop);
1938                    if let Some((aabr, door_tile, door_dir, alt)) = attempt(32, || {
1939                        site.find_roadside_aabr(
1940                            &mut rng,
1941                            4..(size + 1).pow(2),
1942                            Extent2::broadcast(size),
1943                        )
1944                    }) {
1945                        let savannah_workshop = plot::SavannahWorkshop::generate(
1946                            land,
1947                            &mut reseed(&mut rng),
1948                            &site,
1949                            door_tile,
1950                            door_dir,
1951                            aabr,
1952                            alt,
1953                        );
1954                        let savannah_workshop_alt = savannah_workshop.alt;
1955                        let plot = site.create_plot(Plot {
1956                            kind: PlotKind::SavannahWorkshop(savannah_workshop),
1957                            root_tile: aabr.center(),
1958                            tiles: aabr_tiles(aabr).collect(),
1959                        });
1960
1961                        site.blit_aabr(aabr, Tile {
1962                            kind: TileKind::Building,
1963                            plot: Some(plot),
1964                            hard_alt: Some(savannah_workshop_alt),
1965                        });
1966                        workshops += 1;
1967                        generator_stats.success(site.name(), GenStatPlotKind::Workshop);
1968                    } else {
1969                        site.make_plaza(land, index, &mut rng, generator_stats, &name, road_kind);
1970                    }
1971                },
1972                1 => {
1973                    // SavannahHut
1974                    let size = (4.0 + rng.random::<f32>().powf(5.0) * 1.5).round() as u32;
1975                    generator_stats.attempt(site.name(), GenStatPlotKind::House);
1976                    if let Some((aabr, door_tile, door_dir, alt)) = attempt(32, || {
1977                        site.find_roadside_aabr(
1978                            &mut rng,
1979                            4..(size + 1).pow(2),
1980                            Extent2::broadcast(size),
1981                        )
1982                    }) {
1983                        let savannah_hut = plot::SavannahHut::generate(
1984                            land,
1985                            &mut reseed(&mut rng),
1986                            &site,
1987                            door_tile,
1988                            door_dir,
1989                            aabr,
1990                            alt,
1991                        );
1992                        let savannah_hut_alt = savannah_hut.alt;
1993                        let plot = site.create_plot(Plot {
1994                            kind: PlotKind::SavannahHut(savannah_hut),
1995                            root_tile: aabr.center(),
1996                            tiles: aabr_tiles(aabr).collect(),
1997                        });
1998
1999                        site.blit_aabr(aabr, Tile {
2000                            kind: TileKind::Building,
2001                            plot: Some(plot),
2002                            hard_alt: Some(savannah_hut_alt),
2003                        });
2004                        generator_stats.success(site.name(), GenStatPlotKind::House);
2005                    } else {
2006                        site.make_plaza(land, index, &mut rng, generator_stats, &name, road_kind);
2007                    }
2008                },
2009                3 if airship_dock < 1 => {
2010                    // SavannahAirshipDock
2011                    let size = 9u32;
2012                    generator_stats.attempt(site.name(), GenStatPlotKind::AirshipDock);
2013                    if let Some((aabr, door_tile, _, _)) = attempt(48, || {
2014                        site.find_roadside_aabr(&mut rng, 81..82, Extent2::broadcast(size))
2015                    }) {
2016                        let savannah_airship_dock = plot::SavannahAirshipDock::generate(
2017                            land,
2018                            &mut reseed(&mut rng),
2019                            &site,
2020                            door_tile,
2021                            aabr,
2022                        );
2023                        let savannah_airship_dock_alt = savannah_airship_dock.alt;
2024                        let plot = site.create_plot(Plot {
2025                            kind: PlotKind::SavannahAirshipDock(savannah_airship_dock),
2026                            root_tile: aabr.center(),
2027                            tiles: aabr_tiles(aabr).collect(),
2028                        });
2029
2030                        site.blit_aabr(aabr, Tile {
2031                            kind: TileKind::Building,
2032                            plot: Some(plot),
2033                            hard_alt: Some(savannah_airship_dock_alt),
2034                        });
2035                        airship_dock += 1;
2036                        generator_stats.success(site.name(), GenStatPlotKind::AirshipDock);
2037                    } else {
2038                        site.make_plaza(land, index, &mut rng, generator_stats, &name, road_kind);
2039                    }
2040                },
2041                // Field
2042                4 => {
2043                    Self::generate_farm(false, &mut rng, &mut site, land);
2044                },
2045                5 => {
2046                    Self::generate_barn(false, &mut rng, &mut site, land, index);
2047                },
2048                6 => {
2049                    // SavannahGuardHut
2050                    let size = (4.0 + rng.random::<f32>().powf(5.0) * 1.5).round() as u32;
2051                    generator_stats.attempt(site.name(), GenStatPlotKind::House);
2052                    if let Some((aabr, door_tile, door_dir, alt)) = attempt(32, || {
2053                        site.find_roadside_aabr(
2054                            &mut rng,
2055                            2..(size + 1).pow(2),
2056                            Extent2::broadcast(size),
2057                        )
2058                    }) {
2059                        let savannah_guard_hut = plot::SavannahGuardHut::generate(
2060                            land,
2061                            &mut reseed(&mut rng),
2062                            &site,
2063                            door_tile,
2064                            door_dir,
2065                            aabr,
2066                            alt,
2067                        );
2068                        let savannah_guard_hut_alt = savannah_guard_hut.alt;
2069                        let plot = site.create_plot(Plot {
2070                            kind: PlotKind::SavannahGuardHut(savannah_guard_hut),
2071                            root_tile: aabr.center(),
2072                            tiles: aabr_tiles(aabr).collect(),
2073                        });
2074
2075                        site.blit_aabr(aabr, Tile {
2076                            kind: TileKind::Building,
2077                            plot: Some(plot),
2078                            hard_alt: Some(savannah_guard_hut_alt),
2079                        });
2080                        generator_stats.success(site.name(), GenStatPlotKind::House);
2081                    } else {
2082                        site.make_plaza(land, index, &mut rng, generator_stats, &name, road_kind);
2083                    }
2084                },
2085                _ => {},
2086            }
2087        }
2088        site
2089    }
2090
2091    pub fn generate_coastal_town(
2092        land: &Land,
2093        index: IndexRef,
2094        rng: &mut impl Rng,
2095        origin: Vec2<i32>,
2096        generator_stats: &mut SitesGenMeta,
2097    ) -> Self {
2098        let mut rng = reseed(rng);
2099        let name = NameGen::location(&mut rng).generate_danari();
2100        let mut site = Site {
2101            origin,
2102            name: Some(name.clone()),
2103            kind: Some(SiteKind::CoastalTown),
2104            ..Site::default()
2105        };
2106        let road_kind = plot::RoadKind {
2107            lights: plot::RoadLights::Default,
2108            material: plot::RoadMaterial::Marble,
2109        };
2110
2111        // place the initial plaza
2112        site.demarcate_obstacles(land);
2113        generator_stats.add(site.name(), GenStatSiteKind::CoastalTown);
2114        site.make_initial_plaza_default(land, index, &mut rng, generator_stats, &name, road_kind);
2115
2116        let mut workshops = 0;
2117        let build_chance = Lottery::from(vec![(38.0, 1), (5.0, 2), (15.0, 3), (15.0, 4), (5.0, 5)]);
2118        let mut airship_docks = 0;
2119        for _ in 0..55 {
2120            match *build_chance.choose_seeded(rng.random()) {
2121                n if (n == 2 && workshops < 3) || workshops == 0 => {
2122                    // CoastalWorkshop
2123                    let size = (7.0 + rng.random::<f32>().powf(5.0) * 1.5).round() as u32;
2124                    generator_stats.attempt(site.name(), GenStatPlotKind::Workshop);
2125                    if let Some((aabr, door_tile, door_dir, alt)) = attempt(32, || {
2126                        site.find_roadside_aabr(
2127                            &mut rng,
2128                            7..(size + 1).pow(2),
2129                            Extent2::broadcast(size),
2130                        )
2131                    }) {
2132                        let coastal_workshop = plot::CoastalWorkshop::generate(
2133                            land,
2134                            &mut reseed(&mut rng),
2135                            &site,
2136                            door_tile,
2137                            door_dir,
2138                            aabr,
2139                            alt,
2140                        );
2141                        let coastal_workshop_alt = coastal_workshop.alt;
2142                        let plot = site.create_plot(Plot {
2143                            kind: PlotKind::CoastalWorkshop(coastal_workshop),
2144                            root_tile: aabr.center(),
2145                            tiles: aabr_tiles(aabr).collect(),
2146                        });
2147
2148                        site.blit_aabr(aabr, Tile {
2149                            kind: TileKind::Building,
2150                            plot: Some(plot),
2151                            hard_alt: Some(coastal_workshop_alt),
2152                        });
2153                        workshops += 1;
2154                        generator_stats.success(site.name(), GenStatPlotKind::Workshop);
2155                    } else {
2156                        site.make_plaza(land, index, &mut rng, generator_stats, &name, road_kind);
2157                    }
2158                },
2159                1 => {
2160                    // CoastalHouse
2161                    let size = (7.0 + rng.random::<f32>().powf(5.0) * 1.5).round() as u32;
2162                    generator_stats.attempt(site.name(), GenStatPlotKind::House);
2163                    if let Some((aabr, door_tile, door_dir, alt)) = attempt(32, || {
2164                        site.find_roadside_aabr(
2165                            &mut rng,
2166                            7..(size + 1).pow(2),
2167                            Extent2::broadcast(size),
2168                        )
2169                    }) {
2170                        let coastal_house = plot::CoastalHouse::generate(
2171                            land,
2172                            &mut reseed(&mut rng),
2173                            &site,
2174                            door_tile,
2175                            door_dir,
2176                            aabr,
2177                            alt,
2178                        );
2179                        let coastal_house_alt = coastal_house.alt;
2180                        let plot = site.create_plot(Plot {
2181                            kind: PlotKind::CoastalHouse(coastal_house),
2182                            root_tile: aabr.center(),
2183                            tiles: aabr_tiles(aabr).collect(),
2184                        });
2185
2186                        site.blit_aabr(aabr, Tile {
2187                            kind: TileKind::Building,
2188                            plot: Some(plot),
2189                            hard_alt: Some(coastal_house_alt),
2190                        });
2191
2192                        generator_stats.success(site.name(), GenStatPlotKind::House);
2193                    } else {
2194                        site.make_plaza(land, index, &mut rng, generator_stats, &name, road_kind);
2195                    }
2196                },
2197                3 if airship_docks < 1 => {
2198                    // CoastalAirshipDock
2199                    // The airship dock rendered size is 10x10.
2200                    // The rendering code allows for a margin of 1 tile
2201                    // and we want to keep the dock footprint as small as possible.
2202                    // The area range for the aabr is fixed at size squared (81) since the
2203                    // dock structure is square.
2204                    let size = 9u32;
2205                    generator_stats.attempt(site.name(), GenStatPlotKind::AirshipDock);
2206                    if let Some((aabr, door_tile, _, _)) = attempt(32, || {
2207                        site.find_roadside_aabr(&mut rng, 81..82, Extent2::broadcast(size))
2208                    }) {
2209                        let coastal_airship_dock = plot::CoastalAirshipDock::generate(
2210                            land,
2211                            &mut reseed(&mut rng),
2212                            &site,
2213                            door_tile,
2214                            aabr,
2215                        );
2216                        let coastal_airship_dock_alt = coastal_airship_dock.alt;
2217                        let plot = site.create_plot(Plot {
2218                            kind: PlotKind::CoastalAirshipDock(coastal_airship_dock),
2219                            root_tile: aabr.center(),
2220                            tiles: aabr_tiles(aabr).collect(),
2221                        });
2222
2223                        site.blit_aabr(aabr, Tile {
2224                            kind: TileKind::Building,
2225                            plot: Some(plot),
2226                            hard_alt: Some(coastal_airship_dock_alt),
2227                        });
2228                        airship_docks += 1;
2229                        generator_stats.success(site.name(), GenStatPlotKind::AirshipDock);
2230                    } else {
2231                        site.make_plaza(land, index, &mut rng, generator_stats, &name, road_kind);
2232                    }
2233                },
2234                // Field
2235                4 => {
2236                    Self::generate_farm(false, &mut rng, &mut site, land);
2237                },
2238                5 => {
2239                    Self::generate_barn(false, &mut rng, &mut site, land, index);
2240                },
2241                _ => {},
2242            }
2243        }
2244        site
2245    }
2246
2247    pub fn generate_desert_city(
2248        land: &Land,
2249        index: IndexRef,
2250        rng: &mut impl Rng,
2251        origin: Vec2<i32>,
2252        generator_stats: &mut SitesGenMeta,
2253    ) -> Self {
2254        let mut rng = reseed(rng);
2255
2256        let name = NameGen::location(&mut rng).generate_arabic();
2257        let mut site = Site {
2258            origin,
2259            name: Some(name.clone()),
2260            kind: Some(SiteKind::DesertCity),
2261            ..Site::default()
2262        };
2263        let road_kind = plot::RoadKind {
2264            lights: plot::RoadLights::Default,
2265            material: plot::RoadMaterial::Sandstone,
2266        };
2267
2268        // place the initial plaza
2269        site.demarcate_obstacles(land);
2270        // The desert_city_arena is 17 tiles in radius, so the plaza should be outside
2271        // the palace.
2272        const DESERT_CITY_PLAZA_RADIUS: u32 = 3;
2273        const DESERT_CITY_PLAZA_SEARCH_INNER: u32 = 19;
2274        const DESERT_CITY_PLAZA_SEARCH_WIDTH: u32 = 12;
2275        generator_stats.add(site.name(), GenStatSiteKind::DesertCity);
2276        site.make_initial_plaza(
2277            land,
2278            index,
2279            &mut rng,
2280            DESERT_CITY_PLAZA_RADIUS,
2281            DESERT_CITY_PLAZA_SEARCH_INNER,
2282            DESERT_CITY_PLAZA_SEARCH_WIDTH,
2283            generator_stats,
2284            &name,
2285            road_kind,
2286        );
2287
2288        let size = 17.0 as i32;
2289        let aabr = Aabr {
2290            min: Vec2::broadcast(-size),
2291            max: Vec2::broadcast(size),
2292        };
2293
2294        let desert_city_arena =
2295            plot::DesertCityArena::generate(land, &mut reseed(&mut rng), &site, aabr);
2296
2297        let desert_city_arena_alt = desert_city_arena.alt;
2298        let plot = site.create_plot(Plot {
2299            kind: PlotKind::DesertCityArena(desert_city_arena),
2300            root_tile: aabr.center(),
2301            tiles: aabr_tiles(aabr).collect(),
2302        });
2303
2304        site.blit_aabr(aabr, Tile {
2305            kind: TileKind::Building,
2306            plot: Some(plot),
2307            hard_alt: Some(desert_city_arena_alt),
2308        });
2309
2310        let build_chance =
2311            Lottery::from(vec![(20.0, 1), (10.0, 2), (15.0, 3), (10.0, 4), (0.0, 5)]);
2312
2313        let mut temples = 0;
2314        let mut airship_docks = 0;
2315        let mut campfires = 0;
2316
2317        for _ in 0..35 {
2318            match *build_chance.choose_seeded(rng.random()) {
2319                // DesertCityMultiplot
2320                1 => {
2321                    let size = (9.0 + rng.random::<f32>().powf(5.0) * 1.5).round() as u32;
2322                    generator_stats.attempt(site.name(), GenStatPlotKind::MultiPlot);
2323                    let campfire = campfires < 4;
2324                    if let Some((aabr, door_tile, door_dir, alt)) = attempt(32, || {
2325                        site.find_roadside_aabr(
2326                            &mut rng,
2327                            8..(size + 1).pow(2),
2328                            Extent2::broadcast(size),
2329                        )
2330                    }) {
2331                        let desert_city_multi_plot = plot::DesertCityMultiPlot::generate(
2332                            land,
2333                            &mut reseed(&mut rng),
2334                            &site,
2335                            door_tile,
2336                            door_dir,
2337                            aabr,
2338                            campfire,
2339                            alt,
2340                        );
2341                        let desert_city_multi_plot_alt = desert_city_multi_plot.alt;
2342                        let plot = site.create_plot(Plot {
2343                            kind: PlotKind::DesertCityMultiPlot(desert_city_multi_plot),
2344                            root_tile: aabr.center(),
2345                            tiles: aabr_tiles(aabr).collect(),
2346                        });
2347
2348                        site.blit_aabr(aabr, Tile {
2349                            kind: TileKind::Building,
2350                            plot: Some(plot),
2351                            hard_alt: Some(desert_city_multi_plot_alt),
2352                        });
2353                        campfires += 1;
2354                        generator_stats.success(site.name(), GenStatPlotKind::MultiPlot);
2355                    } else {
2356                        site.make_plaza(land, index, &mut rng, generator_stats, &name, road_kind);
2357                    }
2358                },
2359                // DesertCityTemple
2360                2 if temples < 1 => {
2361                    let size = (9.0 + rng.random::<f32>().powf(5.0) * 1.5).round() as u32;
2362                    generator_stats.attempt(site.name(), GenStatPlotKind::Temple);
2363                    if let Some((aabr, door_tile, door_dir, alt)) = attempt(32, || {
2364                        site.find_roadside_aabr(
2365                            &mut rng,
2366                            8..(size + 1).pow(2),
2367                            Extent2::broadcast(size),
2368                        )
2369                    }) {
2370                        let desert_city_temple = plot::DesertCityTemple::generate(
2371                            land,
2372                            &mut reseed(&mut rng),
2373                            &site,
2374                            door_tile,
2375                            door_dir,
2376                            aabr,
2377                            alt,
2378                        );
2379                        let desert_city_temple_alt = desert_city_temple.alt;
2380                        let plot = site.create_plot(Plot {
2381                            kind: PlotKind::DesertCityTemple(desert_city_temple),
2382                            root_tile: aabr.center(),
2383                            tiles: aabr_tiles(aabr).collect(),
2384                        });
2385
2386                        site.blit_aabr(aabr, Tile {
2387                            kind: TileKind::Building,
2388                            plot: Some(plot),
2389                            hard_alt: Some(desert_city_temple_alt),
2390                        });
2391                        temples += 1;
2392                        generator_stats.success(site.name(), GenStatPlotKind::Temple);
2393                    }
2394                },
2395                3 if airship_docks < 1 => {
2396                    // DesertCityAirshipDock
2397                    // The airship dock rendered size is 10x10.
2398                    // The rendering code allows for a margin of 1 tile
2399                    // and we want to keep the dock footprint as small as possible.
2400                    // The area range for the aabr is fixed at size squared (81) since the
2401                    // dock structure is square.
2402                    let size = 9u32;
2403                    generator_stats.attempt(site.name(), GenStatPlotKind::AirshipDock);
2404                    if let Some((aabr, door_tile, door_dir, alt)) = attempt(100, || {
2405                        site.find_roadside_aabr(&mut rng, 81..82, Extent2::broadcast(size))
2406                    }) {
2407                        let desert_city_airship_dock = plot::DesertCityAirshipDock::generate(
2408                            land,
2409                            &mut reseed(&mut rng),
2410                            &site,
2411                            door_tile,
2412                            door_dir,
2413                            aabr,
2414                            alt,
2415                        );
2416                        let desert_city_airship_dock_alt = desert_city_airship_dock.alt;
2417                        let plot = site.create_plot(Plot {
2418                            kind: PlotKind::DesertCityAirshipDock(desert_city_airship_dock),
2419                            root_tile: aabr.center(),
2420                            tiles: aabr_tiles(aabr).collect(),
2421                        });
2422
2423                        site.blit_aabr(aabr, Tile {
2424                            kind: TileKind::Building,
2425                            plot: Some(plot),
2426                            hard_alt: Some(desert_city_airship_dock_alt),
2427                        });
2428                        airship_docks += 1;
2429                        generator_stats.success(site.name(), GenStatPlotKind::AirshipDock);
2430                    } else {
2431                        site.make_plaza(land, index, &mut rng, generator_stats, &name, road_kind);
2432                    }
2433                },
2434                // cactus farm
2435                4 => {
2436                    Self::generate_farm(true, &mut rng, &mut site, land);
2437                },
2438                // desert barn - disabled for now (0.0 spawn chance)
2439                // need desert-variant sprite
2440                5 => {
2441                    Self::generate_barn(true, &mut rng, &mut site, land, index);
2442                },
2443                _ => {},
2444            }
2445        }
2446        site
2447    }
2448
2449    pub fn generate_farm(
2450        is_desert: bool,
2451        mut rng: &mut impl Rng,
2452        site: &mut Site,
2453        land: &Land,
2454    ) -> bool {
2455        let size = (3.0 + rng.random::<f32>().powf(5.0) * 6.0).round() as u32;
2456        if let Some((aabr, door_tile, door_dir, _alt)) = attempt(32, || {
2457            site.find_rural_aabr(&mut rng, 6..(size + 1).pow(2), Extent2::broadcast(size))
2458        }) {
2459            let field = plot::FarmField::generate(
2460                land,
2461                &mut reseed(&mut rng),
2462                site,
2463                door_tile,
2464                door_dir,
2465                aabr,
2466                is_desert,
2467            );
2468
2469            let field_alt = field.alt;
2470            let plot = site.create_plot(Plot {
2471                kind: PlotKind::FarmField(field),
2472                root_tile: aabr.center(),
2473                tiles: aabr_tiles(aabr).collect(),
2474            });
2475
2476            site.blit_aabr(aabr, Tile {
2477                kind: TileKind::Field,
2478                plot: Some(plot),
2479                hard_alt: Some(field_alt),
2480            });
2481            true
2482        } else {
2483            false
2484        }
2485    }
2486
2487    pub fn generate_barn(
2488        is_desert: bool,
2489        mut rng: &mut impl Rng,
2490        site: &mut Site,
2491        land: &Land,
2492        index: IndexRef,
2493    ) -> bool {
2494        let size = (7.0 + rng.random::<f32>().powf(5.0) * 1.5).round() as u32;
2495        if let Some((aabr, door_tile, door_dir, _alt)) = attempt(32, || {
2496            site.find_rural_aabr(&mut rng, 7..(size + 1).pow(2), Extent2::broadcast(size))
2497        }) {
2498            let bounds = Aabr {
2499                min: site.tile_wpos(aabr.min),
2500                max: site.tile_wpos(aabr.max),
2501            };
2502
2503            // Barns don't place very well in hilly sections of land due to
2504            // their size. They need a relatively flat area.
2505            let gradient_avg = get_gradient_average(bounds, land);
2506
2507            if gradient_avg > 0.05 {
2508                false
2509            } else {
2510                let barn = plot::Barn::generate(
2511                    land,
2512                    index,
2513                    &mut reseed(&mut rng),
2514                    site,
2515                    door_tile,
2516                    door_dir,
2517                    aabr,
2518                    is_desert,
2519                );
2520                let barn_alt = barn.alt;
2521                let plot = site.create_plot(Plot {
2522                    kind: PlotKind::Barn(barn),
2523                    root_tile: aabr.center(),
2524                    tiles: aabr_tiles(aabr).collect(),
2525                });
2526
2527                site.blit_aabr(aabr, Tile {
2528                    kind: TileKind::Building,
2529                    plot: Some(plot),
2530                    hard_alt: Some(barn_alt),
2531                });
2532
2533                true
2534            }
2535        } else {
2536            false
2537        }
2538    }
2539
2540    pub fn generate_haniwa(land: &Land, rng: &mut impl Rng, origin: Vec2<i32>) -> Self {
2541        let mut rng = reseed(rng);
2542        let mut site = Site {
2543            origin,
2544            name: Some(format!(
2545                "{} {}",
2546                NameGen::location(&mut rng).generate_haniwa(),
2547                [
2548                    "Catacombs",
2549                    "Crypt",
2550                    "Tomb",
2551                    "Gravemound",
2552                    "Tunnels",
2553                    "Vault",
2554                    "Chambers",
2555                    "Halls",
2556                    "Tumulus",
2557                    "Barrow",
2558                ]
2559                .choose(&mut rng)
2560                .unwrap()
2561            )),
2562            kind: Some(SiteKind::Haniwa),
2563            ..Site::default()
2564        };
2565        let size = 24.0 as i32;
2566        let aabr = Aabr {
2567            min: Vec2::broadcast(-size),
2568            max: Vec2::broadcast(size),
2569        };
2570        {
2571            let haniwa = plot::Haniwa::generate(land, &mut reseed(&mut rng), &site, aabr);
2572            let haniwa_alt = haniwa.alt;
2573            let plot = site.create_plot(Plot {
2574                kind: PlotKind::Haniwa(haniwa),
2575                root_tile: aabr.center(),
2576                tiles: aabr_tiles(aabr).collect(),
2577            });
2578
2579            site.blit_aabr(aabr, Tile {
2580                kind: TileKind::Building,
2581                plot: Some(plot),
2582                hard_alt: Some(haniwa_alt),
2583            });
2584        }
2585        site
2586    }
2587
2588    pub fn generate_chapel_site(land: &Land, rng: &mut impl Rng, origin: Vec2<i32>) -> Self {
2589        let mut rng = reseed(rng);
2590        let mut site = Site {
2591            origin,
2592            name: Some(NameGen::location(&mut rng).generate_danari()),
2593            kind: Some(SiteKind::ChapelSite),
2594            ..Site::default()
2595        };
2596
2597        // SeaChapel
2598        let size = 10.0 as i32;
2599        let aabr = Aabr {
2600            min: Vec2::broadcast(-size),
2601            max: Vec2::broadcast(size),
2602        };
2603        {
2604            let sea_chapel = plot::SeaChapel::generate(land, &mut reseed(&mut rng), &site, aabr);
2605            let sea_chapel_alt = sea_chapel.alt;
2606            let plot = site.create_plot(Plot {
2607                kind: PlotKind::SeaChapel(sea_chapel),
2608                root_tile: aabr.center(),
2609                tiles: aabr_tiles(aabr).collect(),
2610            });
2611
2612            site.blit_aabr(aabr, Tile {
2613                kind: TileKind::Building,
2614                plot: Some(plot),
2615                hard_alt: Some(sea_chapel_alt),
2616            });
2617        }
2618        site
2619    }
2620
2621    pub fn generate_pirate_hideout(land: &Land, rng: &mut impl Rng, origin: Vec2<i32>) -> Self {
2622        let mut rng = reseed(rng);
2623        let mut site = Site {
2624            origin,
2625            name: None,
2626            kind: Some(SiteKind::PirateHideout),
2627            ..Site::default()
2628        };
2629
2630        let size = 8.0 as i32;
2631        let aabr = Aabr {
2632            min: Vec2::broadcast(-size),
2633            max: Vec2::broadcast(size),
2634        };
2635        {
2636            let pirate_hideout =
2637                plot::PirateHideout::generate(land, &mut reseed(&mut rng), &site, aabr);
2638            let pirate_hideout_alt = pirate_hideout.alt;
2639            let plot = site.create_plot(Plot {
2640                kind: PlotKind::PirateHideout(pirate_hideout),
2641                root_tile: aabr.center(),
2642                tiles: aabr_tiles(aabr).collect(),
2643            });
2644
2645            site.blit_aabr(aabr, Tile {
2646                kind: TileKind::Building,
2647                plot: Some(plot),
2648                hard_alt: Some(pirate_hideout_alt),
2649            });
2650        }
2651        site
2652    }
2653
2654    pub fn generate_jungle_ruin(land: &Land, rng: &mut impl Rng, origin: Vec2<i32>) -> Self {
2655        let mut rng = reseed(rng);
2656        let mut site = Site {
2657            origin,
2658            name: None,
2659            kind: Some(SiteKind::JungleRuin),
2660            ..Site::default()
2661        };
2662        let size = 8.0 as i32;
2663        let aabr = Aabr {
2664            min: Vec2::broadcast(-size),
2665            max: Vec2::broadcast(size),
2666        };
2667        {
2668            let jungle_ruin = plot::JungleRuin::generate(land, &mut reseed(&mut rng), &site, aabr);
2669            let jungle_ruin_alt = jungle_ruin.alt;
2670            let plot = site.create_plot(Plot {
2671                kind: PlotKind::JungleRuin(jungle_ruin),
2672                root_tile: aabr.center(),
2673                tiles: aabr_tiles(aabr).collect(),
2674            });
2675
2676            site.blit_aabr(aabr, Tile {
2677                kind: TileKind::Building,
2678                plot: Some(plot),
2679                hard_alt: Some(jungle_ruin_alt),
2680            });
2681        }
2682        site
2683    }
2684
2685    pub fn generate_rock_circle(land: &Land, rng: &mut impl Rng, origin: Vec2<i32>) -> Self {
2686        let mut rng = reseed(rng);
2687        let mut site = Site {
2688            origin,
2689            kind: Some(SiteKind::RockCircle),
2690            ..Site::default()
2691        };
2692        let size = 8.0 as i32;
2693        let aabr = Aabr {
2694            min: Vec2::broadcast(-size),
2695            max: Vec2::broadcast(size),
2696        };
2697        {
2698            let rock_circle = plot::RockCircle::generate(land, &mut reseed(&mut rng), &site, aabr);
2699            let rock_circle_alt = rock_circle.alt;
2700            let plot = site.create_plot(Plot {
2701                kind: PlotKind::RockCircle(rock_circle),
2702                root_tile: aabr.center(),
2703                tiles: aabr_tiles(aabr).collect(),
2704            });
2705
2706            site.blit_aabr(aabr, Tile {
2707                kind: TileKind::Building,
2708                plot: Some(plot),
2709                hard_alt: Some(rock_circle_alt),
2710            });
2711        }
2712        site
2713    }
2714
2715    pub fn generate_troll_cave(land: &Land, rng: &mut impl Rng, origin: Vec2<i32>) -> Self {
2716        let mut rng = reseed(rng);
2717        let mut site = Site {
2718            origin,
2719            name: None,
2720            kind: Some(SiteKind::TrollCave),
2721            ..Site::default()
2722        };
2723        let size = 2.0 as i32;
2724        let aabr = Aabr {
2725            min: Vec2::broadcast(-size),
2726            max: Vec2::broadcast(size),
2727        };
2728        let site_temp = temp_at_wpos(land, origin);
2729        {
2730            let troll_cave =
2731                plot::TrollCave::generate(land, &mut reseed(&mut rng), &site, aabr, site_temp);
2732            let troll_cave_alt = troll_cave.alt;
2733            let plot = site.create_plot(Plot {
2734                kind: PlotKind::TrollCave(troll_cave),
2735                root_tile: aabr.center(),
2736                tiles: aabr_tiles(aabr).collect(),
2737            });
2738
2739            site.blit_aabr(aabr, Tile {
2740                kind: TileKind::Building,
2741                plot: Some(plot),
2742                hard_alt: Some(troll_cave_alt),
2743            });
2744        }
2745        site
2746    }
2747
2748    pub fn generate_camp(land: &Land, rng: &mut impl Rng, origin: Vec2<i32>) -> Self {
2749        let mut rng = reseed(rng);
2750        let mut site = Site {
2751            origin,
2752            name: None,
2753            kind: Some(SiteKind::Camp),
2754            ..Site::default()
2755        };
2756        let size = 2.0 as i32;
2757        let aabr = Aabr {
2758            min: Vec2::broadcast(-size),
2759            max: Vec2::broadcast(size),
2760        };
2761        let site_temp = temp_at_wpos(land, origin);
2762        {
2763            let camp = plot::Camp::generate(land, &mut reseed(&mut rng), &site, aabr, site_temp);
2764            let camp_alt = camp.alt;
2765            let plot = site.create_plot(Plot {
2766                kind: PlotKind::Camp(camp),
2767                root_tile: aabr.center(),
2768                tiles: aabr_tiles(aabr).collect(),
2769            });
2770
2771            site.blit_aabr(aabr, Tile {
2772                kind: TileKind::Building,
2773                plot: Some(plot),
2774                hard_alt: Some(camp_alt),
2775            });
2776        }
2777        site
2778    }
2779
2780    pub fn generate_cultist(land: &Land, rng: &mut impl Rng, origin: Vec2<i32>) -> Self {
2781        let mut rng = reseed(rng);
2782        let mut site = Site {
2783            origin,
2784            name: Some({
2785                let name = NameGen::location(&mut rng).generate();
2786                match rng.random_range(0..5) {
2787                    0 => format!("{} Dungeon", name),
2788                    1 => format!("{} Lair", name),
2789                    2 => format!("{} Crib", name),
2790                    3 => format!("{} Catacombs", name),
2791                    _ => format!("{} Pit", name),
2792                }
2793            }),
2794            kind: Some(SiteKind::Cultist),
2795            ..Site::default()
2796        };
2797        let size = 22.0 as i32;
2798        let aabr = Aabr {
2799            min: Vec2::broadcast(-size),
2800            max: Vec2::broadcast(size),
2801        };
2802        {
2803            let cultist = plot::Cultist::generate(land, &mut reseed(&mut rng), &site, aabr);
2804            let cultist_alt = cultist.alt;
2805            let plot = site.create_plot(Plot {
2806                kind: PlotKind::Cultist(cultist),
2807                root_tile: aabr.center(),
2808                tiles: aabr_tiles(aabr).collect(),
2809            });
2810
2811            site.blit_aabr(aabr, Tile {
2812                kind: TileKind::Building,
2813                plot: Some(plot),
2814                hard_alt: Some(cultist_alt),
2815            });
2816        }
2817        site
2818    }
2819
2820    pub fn generate_sahagin(
2821        land: &Land,
2822        index: IndexRef,
2823        rng: &mut impl Rng,
2824        origin: Vec2<i32>,
2825    ) -> Self {
2826        let mut rng = reseed(rng);
2827        let mut site = Site {
2828            origin,
2829            name: Some({
2830                let name = NameGen::location(&mut rng).generate();
2831                match rng.random_range(0..5) {
2832                    0 => format!("{} Isle", name),
2833                    1 => format!("{} Islet", name),
2834                    2 => format!("{} Key", name),
2835                    3 => format!("{} Cay", name),
2836                    _ => format!("{} Rock", name),
2837                }
2838            }),
2839            kind: Some(SiteKind::Sahagin),
2840            ..Site::default()
2841        };
2842        let size = 16.0 as i32;
2843        let aabr = Aabr {
2844            min: Vec2::broadcast(-size),
2845            max: Vec2::broadcast(size),
2846        };
2847        {
2848            let sahagin = plot::Sahagin::generate(land, index, &mut reseed(&mut rng), &site, aabr);
2849            let sahagin_alt = sahagin.alt;
2850            let plot = site.create_plot(Plot {
2851                kind: PlotKind::Sahagin(sahagin),
2852                root_tile: aabr.center(),
2853                tiles: aabr_tiles(aabr).collect(),
2854            });
2855
2856            site.blit_aabr(aabr, Tile {
2857                kind: TileKind::Building,
2858                plot: Some(plot),
2859                hard_alt: Some(sahagin_alt),
2860            });
2861        }
2862        site
2863    }
2864
2865    pub fn generate_vampire_castle(land: &Land, rng: &mut impl Rng, origin: Vec2<i32>) -> Self {
2866        let mut rng = reseed(rng);
2867        let mut site = Site {
2868            origin,
2869            name: Some({
2870                let name = NameGen::location(&mut rng).generate_vampire();
2871                match rng.random_range(0..4) {
2872                    0 => format!("{} Keep", name),
2873                    1 => format!("{} Chateau", name),
2874                    2 => format!("{} Manor", name),
2875                    _ => format!("{} Palace", name),
2876                }
2877            }),
2878            kind: Some(SiteKind::VampireCastle),
2879            ..Site::default()
2880        };
2881        let size = 22.0 as i32;
2882        let aabr = Aabr {
2883            min: Vec2::broadcast(-size),
2884            max: Vec2::broadcast(size),
2885        };
2886        {
2887            let vampire_castle =
2888                plot::VampireCastle::generate(land, &mut reseed(&mut rng), &site, aabr);
2889            let vampire_castle_alt = vampire_castle.alt;
2890            let plot = site.create_plot(Plot {
2891                kind: PlotKind::VampireCastle(vampire_castle),
2892                root_tile: aabr.center(),
2893                tiles: aabr_tiles(aabr).collect(),
2894            });
2895
2896            site.blit_aabr(aabr, Tile {
2897                kind: TileKind::Building,
2898                plot: Some(plot),
2899                hard_alt: Some(vampire_castle_alt),
2900            });
2901        }
2902        site
2903    }
2904
2905    pub fn generate_bridge(
2906        land: &Land,
2907        index: IndexRef,
2908        rng: &mut impl Rng,
2909        start_chunk: Vec2<i32>,
2910        end_chunk: Vec2<i32>,
2911    ) -> Self {
2912        let mut rng = reseed(rng);
2913        let start = TerrainChunkSize::center_wpos(start_chunk);
2914        let end = TerrainChunkSize::center_wpos(end_chunk);
2915        let origin = (start + end) / 2;
2916
2917        let mut site = Site {
2918            origin,
2919            name: Some(format!(
2920                "Bridge of {}",
2921                NameGen::location(&mut rng).generate_town()
2922            )),
2923            kind: Some(SiteKind::Bridge(start_chunk, end_chunk)),
2924            ..Site::default()
2925        };
2926
2927        let start_tile = site.wpos_tile_pos(start);
2928        let end_tile = site.wpos_tile_pos(end);
2929
2930        let width = 1;
2931
2932        let orth = (start_tile - end_tile).yx().map(|dir| dir.signum().abs());
2933
2934        let start_aabr = Aabr {
2935            min: start_tile.map2(end_tile, |a, b| a.min(b)) - orth * width,
2936            max: start_tile.map2(end_tile, |a, b| a.max(b)) + 1 + orth * width,
2937        };
2938
2939        let bridge = plot::Bridge::generate(land, index, &mut rng, &site, start_tile, end_tile);
2940
2941        let start_tile = site.wpos_tile_pos(bridge.start.xy());
2942        let end_tile = site.wpos_tile_pos(bridge.end.xy());
2943
2944        let width = (bridge.width() + TILE_SIZE as i32 / 2) / TILE_SIZE as i32;
2945        let aabr = Aabr {
2946            min: start_tile.map2(end_tile, |a, b| a.min(b)) - orth * width,
2947            max: start_tile.map2(end_tile, |a, b| a.max(b)) + 1 + orth * width,
2948        };
2949
2950        let line = LineSegment2 {
2951            start: site.tile_wpos(bridge.dir.select_aabr_with(start_aabr, start_aabr.center())),
2952            end: site.tile_wpos(
2953                bridge
2954                    .dir
2955                    .opposite()
2956                    .select_aabr_with(start_aabr, start_aabr.center()),
2957            ),
2958        }
2959        .as_();
2960
2961        for y in start_aabr.min.y..start_aabr.max.y {
2962            for x in start_aabr.min.x..start_aabr.max.x {
2963                let tpos = Vec2::new(x, y);
2964                let tile_aabr = Aabr {
2965                    min: site.tile_wpos(tpos),
2966                    max: site.tile_wpos(tpos + 1) - 1,
2967                };
2968                if let Some(tile) = site.tiles.get_mut(tpos) {
2969                    let closest_point = line.projected_point(tile_aabr.center().as_());
2970                    let w = TILE_SIZE as f32;
2971                    if tile_aabr
2972                        .as_()
2973                        .projected_point(closest_point)
2974                        .distance_squared(closest_point)
2975                        < w.powi(2)
2976                    {
2977                        tile.kind = TileKind::Path {
2978                            closest_pos: closest_point,
2979                            path: Path { width: w },
2980                        };
2981                    }
2982                }
2983            }
2984        }
2985
2986        let plot = site.create_plot(Plot {
2987            kind: PlotKind::Bridge(bridge),
2988            root_tile: start_tile,
2989            tiles: aabr_tiles(aabr).collect(),
2990        });
2991
2992        site.blit_aabr(aabr, Tile {
2993            kind: TileKind::Bridge,
2994            plot: Some(plot),
2995            hard_alt: None,
2996        });
2997
2998        site
2999    }
3000
3001    pub fn wpos_tile_pos(&self, wpos2d: Vec2<i32>) -> Vec2<i32> {
3002        (wpos2d - self.origin).map(|e| e.div_euclid(TILE_SIZE as i32))
3003    }
3004
3005    pub fn wpos_tile(&self, wpos2d: Vec2<i32>) -> &Tile {
3006        self.tiles.get(self.wpos_tile_pos(wpos2d))
3007    }
3008
3009    pub fn tile_wpos(&self, tile: Vec2<i32>) -> Vec2<i32> { self.origin + tile * TILE_SIZE as i32 }
3010
3011    pub fn tile_center_wpos(&self, tile: Vec2<i32>) -> Vec2<i32> {
3012        self.origin + tile * TILE_SIZE as i32 + TILE_SIZE as i32 / 2
3013    }
3014
3015    pub fn render_tile(&self, canvas: &mut Canvas, tpos: Vec2<i32>) {
3016        let tile = self.tiles.get(tpos);
3017        let twpos = self.tile_wpos(tpos);
3018        let border = TILE_SIZE as i32;
3019        let cols = (-border..TILE_SIZE as i32 + border).flat_map(|y| {
3020            (-border..TILE_SIZE as i32 + border)
3021                .map(move |x| (twpos + Vec2::new(x, y), Vec2::new(x, y)))
3022        });
3023        if let TileKind::Path { closest_pos, path } = &tile.kind {
3024            let near_connections = CARDINALS.iter().filter_map(|rpos| {
3025                let npos = tpos + rpos;
3026                let tile = self.tiles.get(npos);
3027                let tile_aabr = Aabr {
3028                    min: self.tile_wpos(tpos).map(|e| e as f32),
3029                    max: self.tile_wpos(tpos + 1).map(|e| e as f32) - 1.0,
3030                };
3031                match tile.kind {
3032                    TileKind::Road { a, b, w } => {
3033                        if let Some(PlotKind::Road(road)) = tile.plot.map(|p| &self.plot(p).kind) {
3034                            let start = road.path.nodes[a as usize];
3035                            let end = road.path.nodes[b as usize];
3036                            let dir = Dir2::from_vec2(end - start);
3037                            let orth = dir.orthogonal();
3038                            let aabr = Aabr {
3039                                min: self.tile_center_wpos(start)
3040                                    - w as i32 * 2 * orth.to_vec2()
3041                                    - dir.to_vec2() * TILE_SIZE as i32 / 2,
3042                                max: self.tile_center_wpos(end)
3043                                    + w as i32 * 2 * orth.to_vec2()
3044                                    + dir.to_vec2() * TILE_SIZE as i32 / 2,
3045                            }
3046                            .made_valid()
3047                            .as_();
3048                            Some(aabr)
3049                        } else {
3050                            None
3051                        }
3052                    },
3053                    TileKind::Bridge | TileKind::Plaza => Some(tile_aabr),
3054                    _ => tile
3055                        .plot
3056                        .and_then(|plot| self.plot(plot).kind().meta())
3057                        .and_then(|meta| meta.door_tile())
3058                        .is_some_and(|door_tile| door_tile == npos)
3059                        .then_some(tile_aabr),
3060                }
3061            });
3062            cols.for_each(|(wpos2d, _offs)| {
3063                let wpos2df = wpos2d.map(|e| e as f32);
3064
3065                if closest_pos.distance_squared(wpos2d.as_()) < path.width.powi(2)
3066                    || near_connections
3067                        .clone()
3068                        .map(|aabr| aabr.distance_to_point(wpos2df))
3069                        .min_by_key(|d| (*d * 100.0) as i32)
3070                        .is_some_and(|d| d <= 1.5)
3071                {
3072                    let alt = canvas.col(wpos2d).map_or(0, |col| col.alt as i32);
3073                    let sub_surface_color = canvas
3074                        .col(wpos2d)
3075                        .map_or(Rgb::zero(), |col| col.sub_surface_color);
3076                    for z in -8..6 {
3077                        let wpos = Vec3::new(wpos2d.x, wpos2d.y, alt + z);
3078                        canvas.map(wpos, |b| {
3079                            if b.kind() == BlockKind::Snow {
3080                                b.into_vacant()
3081                            } else if b.is_filled() {
3082                                if b.is_terrain() {
3083                                    Block::new(
3084                                        BlockKind::Earth,
3085                                        path.surface_color((sub_surface_color * 255.0).as_(), wpos),
3086                                    )
3087                                } else {
3088                                    b
3089                                }
3090                            } else {
3091                                b.into_vacant()
3092                            }
3093                        })
3094                    }
3095                }
3096            });
3097        }
3098    }
3099
3100    pub fn render(&self, canvas: &mut Canvas, dynamic_rng: &mut impl Rng) {
3101        let tile_aabr = Aabr {
3102            min: self.wpos_tile_pos(canvas.wpos()) - 1,
3103            max: self
3104                .wpos_tile_pos(canvas.wpos() + TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + 2)
3105                + 3, // Round up, uninclusive, border
3106        };
3107
3108        // Don't double-generate the same plot per chunk!
3109        let mut plots = DHashSet::default();
3110
3111        for y in tile_aabr.min.y..tile_aabr.max.y {
3112            for x in tile_aabr.min.x..tile_aabr.max.x {
3113                self.render_tile(canvas, Vec2::new(x, y));
3114
3115                if let Some(plot) = self.tiles.get(Vec2::new(x, y)).plot {
3116                    plots.insert(plot);
3117                }
3118            }
3119        }
3120
3121        canvas.foreach_col(|canvas, wpos2d, col| {
3122            let tile = self.wpos_tile(wpos2d);
3123            for z_off in (-2..4).rev() {
3124                if let Some(plot) = tile.plot.map(|p| &self.plots[p]) {
3125                    canvas.map_resource(
3126                        Vec3::new(
3127                            wpos2d.x,
3128                            wpos2d.y,
3129                            foreach_plot!(&plot.kind, plot => plot.rel_terrain_offset(col)) + z_off,
3130                        ),
3131                        |block| {
3132                            foreach_plot!(
3133                                &plot.kind,
3134                                plot => plot.terrain_surface_at(
3135                                    wpos2d,
3136                                    block,
3137                                    dynamic_rng,
3138                                    col,
3139                                    z_off,
3140                                    self,
3141                                ).unwrap_or(block),
3142                            )
3143                        },
3144                    );
3145                }
3146            }
3147        });
3148
3149        // TODO: Solve the 'trees are too big' problem and remove this
3150        for (id, plot) in self.plots.iter() {
3151            if matches!(&plot.kind, PlotKind::GiantTree(_)) {
3152                plots.insert(id);
3153            }
3154        }
3155
3156        let mut plots_to_render = plots.into_iter().collect::<Vec<_>>();
3157        // First sort by priority, then id.
3158        plots_to_render
3159            .sort_unstable_by_key(|plot| (self.plots[*plot].kind.render_ordering(), *plot));
3160
3161        let wpos2d = canvas.info().wpos();
3162        let chunk_aabr = Aabr {
3163            min: wpos2d,
3164            max: wpos2d + TerrainChunkSize::RECT_SIZE.as_::<i32>(),
3165        };
3166
3167        let info = canvas.info();
3168
3169        for plot in plots_to_render {
3170            let (prim_tree, fills, mut entities) =
3171                foreach_plot!(&self.plots[plot].kind, plot => plot.render_collect(self, canvas));
3172
3173            let mut spawn = |pos, last_block| {
3174                if let Some(entity) = match &self.plots[plot].kind {
3175                    PlotKind::GiantTree(tree) => tree.entity_at(pos, &last_block, dynamic_rng),
3176                    _ => None,
3177                } {
3178                    entities.push(entity);
3179                }
3180            };
3181
3182            let mut entities_from_structure_blocks = Vec::<EntityInfo>::new();
3183
3184            for (prim, fill) in fills {
3185                for mut aabb in Fill::get_bounds_disjoint(&prim_tree, prim) {
3186                    aabb.min = Vec2::max(aabb.min.xy(), chunk_aabr.min).with_z(aabb.min.z);
3187                    aabb.max = Vec2::min(aabb.max.xy(), chunk_aabr.max).with_z(aabb.max.z);
3188
3189                    for x in aabb.min.x..aabb.max.x {
3190                        for y in aabb.min.y..aabb.max.y {
3191                            let wpos = Vec2::new(x, y);
3192                            let col_tile = self.wpos_tile(wpos);
3193                            if
3194                            /* col_tile.is_building() && */
3195                            col_tile
3196                                .plot
3197                                .and_then(|p| self.plots[p].z_range())
3198                                .zip(self.plots[plot].z_range())
3199                                .is_some_and(|(a, b)| a.end > b.end)
3200                            {
3201                                continue;
3202                            }
3203                            let mut last_block = None;
3204
3205                            let col = canvas
3206                                .col(wpos)
3207                                .map(|col| col.get_info())
3208                                .unwrap_or_default();
3209
3210                            for z in aabb.min.z..aabb.max.z {
3211                                let pos = Vec3::new(x, y, z);
3212
3213                                let mut sprite_cfg = None;
3214
3215                                let map = |block| {
3216                                    let (current_block, _sb, entity_path) = fill.sample_at(
3217                                        &prim_tree,
3218                                        prim,
3219                                        pos,
3220                                        &info,
3221                                        block,
3222                                        &mut sprite_cfg,
3223                                        &col,
3224                                    );
3225
3226                                    if let Some(spec) = entity_path {
3227                                        let entity = EntityInfo::at(pos.as_());
3228                                        let mut loadout_rng = rand::rng();
3229                                        entities_from_structure_blocks.push(
3230                                            entity.with_asset_expect(&spec, &mut loadout_rng, None),
3231                                        );
3232                                    };
3233
3234                                    if let (Some(last_block), None) = (last_block, current_block) {
3235                                        spawn(pos, last_block);
3236                                    }
3237                                    last_block = current_block;
3238                                    current_block.unwrap_or(block)
3239                                };
3240
3241                                match fill {
3242                                    Fill::ResourceSprite { .. } | Fill::Prefab(..) => {
3243                                        canvas.map_resource(pos, map)
3244                                    },
3245                                    _ => canvas.map(pos, map),
3246                                };
3247
3248                                if let Some(sprite_cfg) = sprite_cfg {
3249                                    canvas.set_sprite_cfg(pos, sprite_cfg);
3250                                }
3251                            }
3252                            if let Some(block) = last_block {
3253                                spawn(Vec3::new(x, y, aabb.max.z), block);
3254                            }
3255                        }
3256                    }
3257                }
3258            }
3259
3260            for entity in entities {
3261                canvas.spawn(EntitySpawn::Entity(Box::new(entity)));
3262            }
3263
3264            for entity in entities_from_structure_blocks {
3265                canvas.spawn(EntitySpawn::Entity(Box::new(entity)));
3266            }
3267        }
3268    }
3269
3270    pub fn apply_supplement(
3271        &self,
3272        dynamic_rng: &mut impl Rng,
3273        wpos2d: Vec2<i32>,
3274        supplement: &mut crate::ChunkSupplement,
3275    ) {
3276        for (_, plot) in self.plots.iter() {
3277            match &plot.kind {
3278                PlotKind::Gnarling(g) => g.apply_supplement(dynamic_rng, wpos2d, supplement),
3279                PlotKind::Adlet(a) => a.apply_supplement(dynamic_rng, wpos2d, supplement),
3280                _ => {},
3281            }
3282        }
3283    }
3284}
3285
3286pub fn test_site() -> Site {
3287    let index = crate::index::Index::new(0);
3288    let index_ref = IndexRef {
3289        colors: &index.colors(),
3290        features: &index.features(),
3291        index: &index,
3292    };
3293    let mut gen_meta = SitesGenMeta::new(0);
3294    Site::generate_city(
3295        &Land::empty(),
3296        index_ref,
3297        &mut rand::rng(),
3298        Vec2::zero(),
3299        0.5,
3300        None,
3301        &mut gen_meta,
3302    )
3303}
3304
3305fn wpos_is_hazard(land: &Land, wpos: Vec2<i32>) -> Option<HazardKind> {
3306    if land
3307        .get_chunk_wpos(wpos)
3308        .is_none_or(|c| c.river.near_water())
3309    {
3310        Some(HazardKind::Water)
3311    } else {
3312        Some(land.get_gradient_approx(wpos))
3313            .filter(|g| *g > 0.8)
3314            .map(|gradient| HazardKind::Hill { gradient })
3315    }
3316}
3317
3318fn temp_at_wpos(land: &Land, wpos: Vec2<i32>) -> f32 {
3319    land.get_chunk_wpos(wpos)
3320        .map(|c| c.temp)
3321        .unwrap_or(CONFIG.temperate_temp)
3322}
3323
3324pub fn aabr_tiles(aabr: Aabr<i32>) -> impl Iterator<Item = Vec2<i32>> {
3325    (0..aabr.size().h)
3326        .flat_map(move |y| (0..aabr.size().w).map(move |x| aabr.min + Vec2::new(x, y)))
3327}
3328
3329/// Returns the average gradient of the chunks neighboring the center of the
3330/// given `aabr`, as well as the gradient of the chunk at the center of the
3331/// `aabr` itself.
3332fn get_gradient_average(aabr: Aabr<i32>, land: &Land) -> f32 {
3333    let chunk_size = TerrainChunkSize::RECT_SIZE.reduce_max() as i32;
3334
3335    let mut gradient_sum = 0.0;
3336    let mut gradient_sample_count = 0;
3337
3338    let aabr_center = aabr.center();
3339    let range_x_min = aabr_center.x - chunk_size;
3340    let range_x_max = aabr_center.x + chunk_size;
3341    let range_y_min = aabr_center.y - chunk_size;
3342    let range_y_max = aabr_center.y + chunk_size;
3343
3344    for x_pos in (range_x_min..=range_x_max).step_by(chunk_size as usize) {
3345        for y_pos in (range_y_min..=range_y_max).step_by(chunk_size as usize) {
3346            let gradient_at_pos = land.get_gradient_approx(Vec2::new(x_pos, y_pos));
3347            gradient_sum += gradient_at_pos;
3348            gradient_sample_count += 1;
3349        }
3350    }
3351
3352    gradient_sum / (gradient_sample_count as f32)
3353}