Skip to main content

veloren_world/site/
mod.rs

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