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