veloren_world/site/
mod.rs

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