veloren_world/civ/
mod.rs

1#![expect(dead_code)]
2
3pub mod airship_travel;
4mod econ;
5
6use crate::{
7    Index, IndexRef, Land,
8    civ::airship_travel::Airships,
9    config::CONFIG,
10    sim::WorldSim,
11    site::{Castle, Settlement, Site as WorldSite, Tree, namegen::NameGen},
12    site2,
13    site2::genstat::SitesGenMeta,
14    util::{DHashMap, NEIGHBORS, attempt, seed_expan},
15};
16use common::{
17    astar::Astar,
18    calendar::Calendar,
19    path::Path,
20    spiral::Spiral2d,
21    store::{Id, Store},
22    terrain::{
23        BiomeKind, CoordinateConversions, MapSizeLg, TERRAIN_CHUNK_BLOCKS_LG, TerrainChunkSize,
24        uniform_idx_as_vec2,
25    },
26    vol::RectVolSize,
27};
28use common_base::prof_span;
29use core::{fmt, hash::BuildHasherDefault, ops::Range};
30use fxhash::FxHasher64;
31use rand::prelude::*;
32use rand_chacha::ChaChaRng;
33use tracing::{debug, info, warn};
34use vek::*;
35
36fn initial_civ_count(map_size_lg: MapSizeLg) -> u32 {
37    // NOTE: since map_size_lg's dimensions must fit in a u16, we can safely add
38    // them here.
39    //
40    // NOTE: 48 at "default" scale of 10 × 10 chunk bits (1024 × 1024 chunks).
41    let cnt = (3 << (map_size_lg.vec().x + map_size_lg.vec().y)) >> 16;
42    cnt.max(1) // we need at least one civ in order to generate a starting site
43}
44
45#[derive(Default)]
46pub struct Civs {
47    pub civs: Store<Civ>,
48    pub places: Store<Place>,
49    pub pois: Store<PointOfInterest>,
50
51    pub tracks: Store<Track>,
52    /// We use this hasher (FxHasher64) because
53    /// (1) we don't care about DDOS attacks (ruling out SipHash);
54    /// (2) we care about determinism across computers (ruling out AAHash);
55    /// (3) we have 8-byte keys (for which FxHash is fastest).
56    pub track_map: DHashMap<Id<Site>, DHashMap<Id<Site>, Id<Track>>>,
57
58    pub bridges: DHashMap<Vec2<i32>, (Vec2<i32>, Id<Site>)>,
59
60    pub sites: Store<Site>,
61    pub airships: Airships,
62}
63
64// Change this to get rid of particularly horrid seeds
65const SEED_SKIP: u8 = 5;
66const POI_THINNING_DIST_SQRD: i32 = 300;
67
68pub struct GenCtx<'a, R: Rng> {
69    sim: &'a mut WorldSim,
70    rng: R,
71}
72
73struct ProximitySpec {
74    location: Vec2<i32>,
75    min_distance: Option<i32>,
76    max_distance: Option<i32>,
77}
78
79impl ProximitySpec {
80    pub fn satisfied_by(&self, site: Vec2<i32>) -> bool {
81        let distance_squared = site.distance_squared(self.location);
82        let min_ok = self
83            .min_distance
84            .map(|mind| distance_squared > (mind * mind))
85            .unwrap_or(true);
86        let max_ok = self
87            .max_distance
88            .map(|maxd| distance_squared < (maxd * maxd))
89            .unwrap_or(true);
90        min_ok && max_ok
91    }
92
93    pub fn avoid(location: Vec2<i32>, min_distance: i32) -> Self {
94        ProximitySpec {
95            location,
96            min_distance: Some(min_distance),
97            max_distance: None,
98        }
99    }
100
101    pub fn be_near(location: Vec2<i32>, max_distance: i32) -> Self {
102        ProximitySpec {
103            location,
104            min_distance: None,
105            max_distance: Some(max_distance),
106        }
107    }
108}
109
110struct ProximityRequirementsBuilder {
111    all_of: Vec<ProximitySpec>,
112    any_of: Vec<ProximitySpec>,
113}
114
115impl ProximityRequirementsBuilder {
116    pub fn finalize(self, world_dims: &Aabr<i32>) -> ProximityRequirements {
117        let location_hint = self.location_hint(world_dims);
118        ProximityRequirements {
119            all_of: self.all_of,
120            any_of: self.any_of,
121            location_hint,
122        }
123    }
124
125    fn location_hint(&self, world_dims: &Aabr<i32>) -> Aabr<i32> {
126        let bounding_box_of_point = |point: Vec2<i32>, max_distance: i32| Aabr {
127            min: Vec2 {
128                x: point.x - max_distance,
129                y: point.y - max_distance,
130            },
131            max: Vec2 {
132                x: point.x + max_distance,
133                y: point.y + max_distance,
134            },
135        };
136        let any_of_hint = self
137            .any_of
138            .iter()
139            .fold(None, |acc, spec| match spec.max_distance {
140                None => acc,
141                Some(max_distance) => {
142                    let bounding_box_of_new_point =
143                        bounding_box_of_point(spec.location, max_distance);
144                    match acc {
145                        None => Some(bounding_box_of_new_point),
146                        Some(acc) => Some(acc.union(bounding_box_of_new_point)),
147                    }
148                },
149            })
150            .map(|hint| hint.intersection(*world_dims))
151            .unwrap_or_else(|| world_dims.to_owned());
152        let hint = self
153            .all_of
154            .iter()
155            .fold(any_of_hint, |acc, spec| match spec.max_distance {
156                None => acc,
157                Some(max_distance) => {
158                    let bounding_box_of_new_point =
159                        bounding_box_of_point(spec.location, max_distance);
160                    acc.intersection(bounding_box_of_new_point)
161                },
162            });
163        hint
164    }
165
166    pub fn new() -> Self {
167        Self {
168            all_of: Vec::new(),
169            any_of: Vec::new(),
170        }
171    }
172
173    pub fn avoid_all_of(
174        mut self,
175        locations: impl Iterator<Item = Vec2<i32>>,
176        distance: i32,
177    ) -> Self {
178        let specs = locations.map(|loc| ProximitySpec::avoid(loc, distance));
179        self.all_of.extend(specs);
180        self
181    }
182
183    pub fn close_to_one_of(
184        mut self,
185        locations: impl Iterator<Item = Vec2<i32>>,
186        distance: i32,
187    ) -> Self {
188        let specs = locations.map(|loc| ProximitySpec::be_near(loc, distance));
189        self.any_of.extend(specs);
190        self
191    }
192}
193
194struct ProximityRequirements {
195    all_of: Vec<ProximitySpec>,
196    any_of: Vec<ProximitySpec>,
197    location_hint: Aabr<i32>,
198}
199
200impl ProximityRequirements {
201    pub fn satisfied_by(&self, site: Vec2<i32>) -> bool {
202        if self.location_hint.contains_point(site) {
203            let all_of_compliance = self.all_of.iter().all(|spec| spec.satisfied_by(site));
204            let any_of_compliance =
205                self.any_of.is_empty() || self.any_of.iter().any(|spec| spec.satisfied_by(site));
206            all_of_compliance && any_of_compliance
207        } else {
208            false
209        }
210    }
211}
212
213impl<R: Rng> GenCtx<'_, R> {
214    pub fn reseed(&mut self) -> GenCtx<'_, impl Rng> {
215        let mut entropy = self.rng.gen::<[u8; 32]>();
216        entropy[0] = entropy[0].wrapping_add(SEED_SKIP); // Skip bad seeds
217        GenCtx {
218            sim: self.sim,
219            rng: ChaChaRng::from_seed(entropy),
220        }
221    }
222}
223
224#[derive(Debug)]
225pub enum WorldCivStage {
226    /// Civilization creation, how many out of how many civilizations have been
227    /// generated yet
228    CivCreation(u32, u32),
229    SiteGeneration,
230}
231
232impl Civs {
233    pub fn generate(
234        seed: u32,
235        sim: &mut WorldSim,
236        index: &mut Index,
237        calendar: Option<&Calendar>,
238        report_stage: &dyn Fn(WorldCivStage),
239    ) -> Self {
240        prof_span!("Civs::generate");
241        let mut this = Self::default();
242        let rng = ChaChaRng::from_seed(seed_expan::rng_state(seed));
243        let name_rng = rng.clone();
244        let mut name_ctx = GenCtx { sim, rng: name_rng };
245        if index.features().peak_naming {
246            info!("starting peak naming");
247            this.name_peaks(&mut name_ctx);
248        }
249        if index.features().biome_naming {
250            info!("starting biome naming");
251            this.name_biomes(&mut name_ctx);
252        }
253
254        let initial_civ_count = initial_civ_count(sim.map_size_lg());
255        let mut ctx = GenCtx { sim, rng };
256
257        // info!("starting cave generation");
258        // this.generate_caves(&mut ctx);
259
260        info!("starting civilisation creation");
261        prof_span!(guard, "create civs");
262        for i in 0..initial_civ_count {
263            prof_span!("create civ");
264            debug!("Creating civilisation...");
265            if this.birth_civ(&mut ctx.reseed()).is_none() {
266                warn!("Failed to find starting site for civilisation.");
267            }
268            report_stage(WorldCivStage::CivCreation(i, initial_civ_count));
269        }
270        drop(guard);
271        info!(?initial_civ_count, "all civilisations created");
272
273        report_stage(WorldCivStage::SiteGeneration);
274        prof_span!(guard, "find locations and establish sites");
275        let world_dims = ctx.sim.get_aabr();
276        for _ in 0..initial_civ_count * 3 {
277            attempt(5, || {
278                let (loc, kind) = match ctx.rng.gen_range(0..116) {
279                    0..=4 => {
280                        if index.features().site2_giant_trees {
281                            (
282                                find_site_loc(
283                                    &mut ctx,
284                                    &ProximityRequirementsBuilder::new()
285                                        .avoid_all_of(this.tree_enemies(), 40)
286                                        .finalize(&world_dims),
287                                    &SiteKind::GiantTree,
288                                )?,
289                                SiteKind::GiantTree,
290                            )
291                        } else {
292                            (
293                                find_site_loc(
294                                    &mut ctx,
295                                    &ProximityRequirementsBuilder::new()
296                                        .avoid_all_of(this.tree_enemies(), 40)
297                                        .finalize(&world_dims),
298                                    &SiteKind::Tree,
299                                )?,
300                                SiteKind::Tree,
301                            )
302                        }
303                    },
304                    5..=15 => (
305                        find_site_loc(
306                            &mut ctx,
307                            &ProximityRequirementsBuilder::new()
308                                .avoid_all_of(this.gnarling_enemies(), 40)
309                                .finalize(&world_dims),
310                            &SiteKind::Gnarling,
311                        )?,
312                        SiteKind::Gnarling,
313                    ),
314                    16..=20 => (
315                        find_site_loc(
316                            &mut ctx,
317                            &ProximityRequirementsBuilder::new()
318                                .avoid_all_of(this.chapel_site_enemies(), 40)
319                                .finalize(&world_dims),
320                            &SiteKind::ChapelSite,
321                        )?,
322                        SiteKind::ChapelSite,
323                    ),
324                    21..=27 => (
325                        find_site_loc(
326                            &mut ctx,
327                            &ProximityRequirementsBuilder::new()
328                                .avoid_all_of(this.gnarling_enemies(), 40)
329                                .finalize(&world_dims),
330                            &SiteKind::Adlet,
331                        )?,
332                        SiteKind::Adlet,
333                    ),
334                    28..=38 => (
335                        find_site_loc(
336                            &mut ctx,
337                            &ProximityRequirementsBuilder::new()
338                                .avoid_all_of(this.pirate_hideout_enemies(), 40)
339                                .finalize(&world_dims),
340                            &SiteKind::PirateHideout,
341                        )?,
342                        SiteKind::PirateHideout,
343                    ),
344                    39..=45 => (
345                        find_site_loc(
346                            &mut ctx,
347                            &ProximityRequirementsBuilder::new()
348                                .avoid_all_of(this.jungle_ruin_enemies(), 40)
349                                .finalize(&world_dims),
350                            &SiteKind::JungleRuin,
351                        )?,
352                        SiteKind::JungleRuin,
353                    ),
354                    46..=55 => (
355                        find_site_loc(
356                            &mut ctx,
357                            &ProximityRequirementsBuilder::new()
358                                .avoid_all_of(this.rock_circle_enemies(), 40)
359                                .finalize(&world_dims),
360                            &SiteKind::RockCircle,
361                        )?,
362                        SiteKind::RockCircle,
363                    ),
364                    56..=66 => (
365                        find_site_loc(
366                            &mut ctx,
367                            &ProximityRequirementsBuilder::new()
368                                .avoid_all_of(this.troll_cave_enemies(), 40)
369                                .finalize(&world_dims),
370                            &SiteKind::TrollCave,
371                        )?,
372                        SiteKind::TrollCave,
373                    ),
374                    67..=72 => (
375                        find_site_loc(
376                            &mut ctx,
377                            &ProximityRequirementsBuilder::new()
378                                .avoid_all_of(this.camp_enemies(), 40)
379                                .finalize(&world_dims),
380                            &SiteKind::Camp,
381                        )?,
382                        SiteKind::Camp,
383                    ),
384                    73..=76 => (
385                        find_site_loc(
386                            &mut ctx,
387                            &ProximityRequirementsBuilder::new()
388                                .avoid_all_of(this.mine_site_enemies(), 40)
389                                .finalize(&world_dims),
390                            &SiteKind::Haniwa,
391                        )?,
392                        SiteKind::Haniwa,
393                    ),
394                    77..=81 => (
395                        find_site_loc(
396                            &mut ctx,
397                            &ProximityRequirementsBuilder::new()
398                                .avoid_all_of(this.terracotta_enemies(), 40)
399                                .finalize(&world_dims),
400                            &SiteKind::Terracotta,
401                        )?,
402                        SiteKind::Terracotta,
403                    ),
404                    82..=87 => (
405                        find_site_loc(
406                            &mut ctx,
407                            &ProximityRequirementsBuilder::new()
408                                .avoid_all_of(this.mine_site_enemies(), 40)
409                                .finalize(&world_dims),
410                            &SiteKind::DwarvenMine,
411                        )?,
412                        SiteKind::DwarvenMine,
413                    ),
414                    88..=91 => (
415                        find_site_loc(
416                            &mut ctx,
417                            &ProximityRequirementsBuilder::new()
418                                .avoid_all_of(this.cultist_enemies(), 40)
419                                .finalize(&world_dims),
420                            &SiteKind::Cultist,
421                        )?,
422                        SiteKind::Cultist,
423                    ),
424                    92..=96 => (
425                        find_site_loc(
426                            &mut ctx,
427                            &ProximityRequirementsBuilder::new()
428                                .avoid_all_of(this.sahagin_enemies(), 40)
429                                .finalize(&world_dims),
430                            &SiteKind::Sahagin,
431                        )?,
432                        SiteKind::Sahagin,
433                    ),
434                    97..=102 => (
435                        find_site_loc(
436                            &mut ctx,
437                            &ProximityRequirementsBuilder::new()
438                                .avoid_all_of(this.vampire_castle_enemies(), 40)
439                                .finalize(&world_dims),
440                            &SiteKind::VampireCastle,
441                        )?,
442                        SiteKind::VampireCastle,
443                    ),
444                    103..108 => (
445                        find_site_loc(
446                            &mut ctx,
447                            &ProximityRequirementsBuilder::new().finalize(&world_dims),
448                            &SiteKind::GliderCourse,
449                        )?,
450                        SiteKind::GliderCourse,
451                    ),
452                    /*103..=108 => (
453                        find_site_loc(
454                            &mut ctx,
455                            &ProximityRequirementsBuilder::new()
456                                .avoid_all_of(this.castle_enemies(), 40)
457                                .close_to_one_of(this.towns(), 20)
458                                .finalize(&world_dims),
459                            &SiteKind::Castle,
460                        )?,
461                        SiteKind::Castle,
462                    ),
463                    109..=114 => (SiteKind::Citadel, (&castle_enemies, 20)),
464                    */
465                    _ => (
466                        find_site_loc(
467                            &mut ctx,
468                            &ProximityRequirementsBuilder::new()
469                                .avoid_all_of(this.myrmidon_enemies(), 40)
470                                .finalize(&world_dims),
471                            &SiteKind::Myrmidon,
472                        )?,
473                        SiteKind::Myrmidon,
474                    ),
475                };
476                Some(this.establish_site(&mut ctx.reseed(), loc, |place| Site {
477                    kind,
478                    center: loc,
479                    place,
480                    site_tmp: None,
481                }))
482            });
483        }
484        drop(guard);
485
486        // Tick
487        //=== old economy is gone
488
489        // Flatten ground around sites
490        prof_span!(guard, "Flatten ground around sites");
491        for site in this.sites.values() {
492            let wpos = site.center * TerrainChunkSize::RECT_SIZE.map(|e: u32| e as i32);
493
494            let (radius, flatten_radius) = match &site.kind {
495                SiteKind::Settlement => (32i32, 10.0f32),
496                SiteKind::Castle => (16i32, 5.0),
497                SiteKind::Refactor => (32i32, 10.0),
498                SiteKind::CliffTown => (2i32, 1.0),
499                SiteKind::SavannahTown => (48i32, 25.0),
500                SiteKind::CoastalTown => (64i32, 35.0),
501                SiteKind::JungleRuin => (8i32, 3.0),
502                SiteKind::DesertCity => (64i32, 25.0),
503                SiteKind::ChapelSite => (36i32, 10.0),
504                SiteKind::Terracotta => (64i32, 35.0),
505                SiteKind::Tree => (12i32, 8.0),
506                SiteKind::GiantTree => (12i32, 8.0),
507                SiteKind::Gnarling => (16i32, 10.0),
508                SiteKind::Citadel => (16i32, 0.0),
509                SiteKind::Bridge(_, _) => (0, 0.0),
510                SiteKind::Adlet => (16i32, 0.0),
511                SiteKind::Haniwa => (32i32, 16.0),
512                SiteKind::PirateHideout => (8i32, 3.0),
513                SiteKind::RockCircle => (8i32, 3.0),
514                SiteKind::TrollCave => (4i32, 1.5),
515                SiteKind::Camp => (4i32, 1.5),
516                SiteKind::DwarvenMine => (8i32, 3.0),
517                SiteKind::Cultist => (24i32, 10.0),
518                SiteKind::Sahagin => (8i32, 3.0),
519                SiteKind::VampireCastle => (10i32, 16.0),
520                SiteKind::GliderCourse => (0, 0.0),
521                SiteKind::Myrmidon => (64i32, 35.0),
522            };
523
524            let (raise, raise_dist, make_waypoint): (f32, i32, bool) = match &site.kind {
525                SiteKind::Settlement => (10.0, 6, true),
526                SiteKind::Castle => (0.0, 6, true),
527                _ => (0.0, 0, false),
528            };
529
530            // Flatten ground
531            if let Some(center_alt) = ctx.sim.get_alt_approx(wpos) {
532                for offs in Spiral2d::new().take(radius.pow(2) as usize) {
533                    let center_alt = center_alt
534                        + if offs.magnitude_squared() <= raise_dist.pow(2) {
535                            raise
536                        } else {
537                            0.0
538                        }; // Raise the town centre up a little
539                    let pos = site.center + offs;
540                    let factor = ((1.0
541                        - (site.center - pos).map(|e| e as f32).magnitude()
542                            / flatten_radius.max(0.01))
543                        * 1.25)
544                        .min(1.0);
545                    let rng = &mut ctx.rng;
546                    ctx.sim
547                        .get_mut(pos)
548                        // Don't disrupt chunks that are near water
549                        .filter(|chunk| !chunk.river.near_water())
550                        .map(|chunk| {
551                            let diff = Lerp::lerp_precise(chunk.alt, center_alt, factor) - chunk.alt;
552                            // Make sure we don't fall below sea level (fortunately, we don't have
553                            // to worry about the case where water_alt is already set to a correct
554                            // value higher than alt, since this chunk should have been filtered
555                            // out in that case).
556                            chunk.water_alt = CONFIG.sea_level.max(chunk.water_alt + diff);
557                            chunk.alt += diff;
558                            chunk.basement += diff;
559                            chunk.rockiness = 0.0;
560                            chunk.surface_veg *= 1.0 - factor * rng.gen_range(0.25..0.9);
561
562                            if make_waypoint && offs == Vec2::zero() {
563                                chunk.contains_waypoint = true;
564                            }
565                        });
566                }
567            }
568        }
569        drop(guard);
570
571        // Place sites in world
572        prof_span!(guard, "Place sites in world");
573        let mut cnt = 0;
574        let mut gen_meta = SitesGenMeta::new(seed);
575        for sim_site in this.sites.values_mut() {
576            cnt += 1;
577            let wpos = sim_site
578                .center
579                .map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| {
580                    e * sz as i32 + sz as i32 / 2
581                });
582
583            let mut rng = ctx.reseed().rng;
584            let site = index.sites.insert({
585                let index_ref = IndexRef {
586                    colors: &index.colors(),
587                    features: &index.features(),
588                    index,
589                };
590                match &sim_site.kind {
591                    SiteKind::Settlement => {
592                        WorldSite::settlement(Settlement::generate(wpos, Some(ctx.sim), &mut rng))
593                    },
594                    SiteKind::Castle => {
595                        WorldSite::castle(Castle::generate(wpos, Some(ctx.sim), &mut rng))
596                    },
597                    SiteKind::Refactor => {
598                        let size = Lerp::lerp(0.03, 1.0, rng.gen_range(0.0..1f32).powi(5));
599                        WorldSite::refactor(site2::Site::generate_city(
600                            &Land::from_sim(ctx.sim),
601                            index_ref,
602                            &mut rng,
603                            wpos,
604                            size,
605                            calendar,
606                            &mut gen_meta,
607                        ))
608                    },
609                    SiteKind::GliderCourse => {
610                        WorldSite::glider_course(site2::Site::generate_glider_course(
611                            &Land::from_sim(ctx.sim),
612                            index_ref,
613                            &mut rng,
614                            wpos,
615                        ))
616                    },
617                    SiteKind::CliffTown => WorldSite::cliff_town(site2::Site::generate_cliff_town(
618                        &Land::from_sim(ctx.sim),
619                        index_ref,
620                        &mut rng,
621                        wpos,
622                        &mut gen_meta,
623                    )),
624                    SiteKind::SavannahTown => {
625                        WorldSite::savannah_town(site2::Site::generate_savannah_town(
626                            &Land::from_sim(ctx.sim),
627                            index_ref,
628                            &mut rng,
629                            wpos,
630                            &mut gen_meta,
631                        ))
632                    },
633                    SiteKind::CoastalTown => {
634                        WorldSite::coastal_town(site2::Site::generate_coastal_town(
635                            &Land::from_sim(ctx.sim),
636                            index_ref,
637                            &mut rng,
638                            wpos,
639                            &mut gen_meta,
640                        ))
641                    },
642                    SiteKind::PirateHideout => {
643                        WorldSite::pirate_hideout(site2::Site::generate_pirate_hideout(
644                            &Land::from_sim(ctx.sim),
645                            &mut rng,
646                            wpos,
647                        ))
648                    },
649                    SiteKind::JungleRuin => WorldSite::jungle_ruin(
650                        site2::Site::generate_jungle_ruin(&Land::from_sim(ctx.sim), &mut rng, wpos),
651                    ),
652                    SiteKind::RockCircle => WorldSite::rock_circle(
653                        site2::Site::generate_rock_circle(&Land::from_sim(ctx.sim), &mut rng, wpos),
654                    ),
655                    SiteKind::TrollCave => WorldSite::troll_cave(site2::Site::generate_troll_cave(
656                        &Land::from_sim(ctx.sim),
657                        &mut rng,
658                        wpos,
659                    )),
660                    SiteKind::Camp => WorldSite::troll_cave(site2::Site::generate_camp(
661                        &Land::from_sim(ctx.sim),
662                        &mut rng,
663                        wpos,
664                    )),
665                    SiteKind::DesertCity => {
666                        WorldSite::desert_city(site2::Site::generate_desert_city(
667                            &Land::from_sim(ctx.sim),
668                            index_ref,
669                            &mut rng,
670                            wpos,
671                            &mut gen_meta,
672                        ))
673                    },
674                    SiteKind::Tree => {
675                        WorldSite::tree(Tree::generate(wpos, &Land::from_sim(ctx.sim), &mut rng))
676                    },
677                    SiteKind::GiantTree => WorldSite::giant_tree(site2::Site::generate_giant_tree(
678                        &Land::from_sim(ctx.sim),
679                        &mut rng,
680                        wpos,
681                    )),
682                    SiteKind::Gnarling => WorldSite::gnarling(site2::Site::generate_gnarling(
683                        &Land::from_sim(ctx.sim),
684                        &mut rng,
685                        wpos,
686                    )),
687                    SiteKind::DwarvenMine => WorldSite::dwarven_mine(site2::Site::generate_mine(
688                        &Land::from_sim(ctx.sim),
689                        &mut rng,
690                        wpos,
691                    )),
692                    SiteKind::ChapelSite => WorldSite::chapel_site(
693                        site2::Site::generate_chapel_site(&Land::from_sim(ctx.sim), &mut rng, wpos),
694                    ),
695                    SiteKind::Terracotta => {
696                        WorldSite::terracotta(site2::Site::generate_terracotta(
697                            &Land::from_sim(ctx.sim),
698                            index_ref,
699                            &mut rng,
700                            wpos,
701                            &mut gen_meta,
702                        ))
703                    },
704                    SiteKind::Citadel => WorldSite::gnarling(site2::Site::generate_citadel(
705                        &Land::from_sim(ctx.sim),
706                        &mut rng,
707                        wpos,
708                    )),
709                    SiteKind::Bridge(a, b) => {
710                        let mut bridge_site = site2::Site::generate_bridge(
711                            &Land::from_sim(ctx.sim),
712                            index_ref,
713                            &mut rng,
714                            *a,
715                            *b,
716                        );
717
718                        // Update the path connecting to the bridge to line up better.
719                        if let Some(bridge) =
720                            bridge_site
721                                .plots
722                                .values()
723                                .find_map(|plot| match &plot.kind {
724                                    site2::PlotKind::Bridge(bridge) => Some(bridge),
725                                    _ => None,
726                                })
727                        {
728                            let mut update_offset = |original: Vec2<i32>, new: Vec2<i32>| {
729                                let chunk = original.wpos_to_cpos();
730                                if let Some(c) = ctx.sim.get_mut(chunk) {
731                                    c.path.0.offset = (new - chunk.cpos_to_wpos_center())
732                                        .map(|e| e.clamp(-16, 16) as i8);
733                                }
734                            };
735
736                            update_offset(bridge.original_start, bridge.start.xy());
737                            update_offset(bridge.original_end, bridge.end.xy());
738                        }
739                        bridge_site.demarcate_obstacles(&Land::from_sim(ctx.sim));
740                        WorldSite::bridge(bridge_site)
741                    },
742                    SiteKind::Adlet => WorldSite::adlet(site2::Site::generate_adlet(
743                        &Land::from_sim(ctx.sim),
744                        &mut rng,
745                        wpos,
746                        index_ref,
747                    )),
748                    SiteKind::Haniwa => WorldSite::haniwa(site2::Site::generate_haniwa(
749                        &Land::from_sim(ctx.sim),
750                        &mut rng,
751                        wpos,
752                    )),
753                    SiteKind::Cultist => WorldSite::cultist(site2::Site::generate_cultist(
754                        &Land::from_sim(ctx.sim),
755                        &mut rng,
756                        wpos,
757                    )),
758                    SiteKind::Myrmidon => WorldSite::myrmidon(site2::Site::generate_myrmidon(
759                        &Land::from_sim(ctx.sim),
760                        index_ref,
761                        &mut rng,
762                        wpos,
763                        &mut gen_meta,
764                    )),
765                    SiteKind::Sahagin => WorldSite::sahagin(site2::Site::generate_sahagin(
766                        &Land::from_sim(ctx.sim),
767                        index_ref,
768                        &mut rng,
769                        wpos,
770                    )),
771                    SiteKind::VampireCastle => {
772                        WorldSite::vampire_castle(site2::Site::generate_vampire_castle(
773                            &Land::from_sim(ctx.sim),
774                            &mut rng,
775                            wpos,
776                        ))
777                    },
778                }
779            });
780            sim_site.site_tmp = Some(site);
781            let site_ref = &index.sites[site];
782
783            let radius_chunks =
784                (site_ref.radius() / TerrainChunkSize::RECT_SIZE.x as f32).ceil() as usize;
785            for pos in Spiral2d::new()
786                .map(|offs| sim_site.center + offs)
787                .take((radius_chunks * 2).pow(2))
788            {
789                ctx.sim.get_mut(pos).map(|chunk| chunk.sites.push(site));
790            }
791            debug!(?sim_site.center, "Placed site at location");
792        }
793        drop(guard);
794        info!(?cnt, "all sites placed");
795        gen_meta.log();
796
797        //this.display_info();
798
799        // remember neighbor information in economy
800        for (s1, val) in this.track_map.iter() {
801            if let Some(index1) = this.sites.get(*s1).site_tmp {
802                for (s2, t) in val.iter() {
803                    if let Some(index2) = this.sites.get(*s2).site_tmp {
804                        if index.sites.get(index1).do_economic_simulation()
805                            && index.sites.get(index2).do_economic_simulation()
806                        {
807                            let cost = this.tracks.get(*t).path.len();
808                            index
809                                .sites
810                                .get_mut(index1)
811                                .economy
812                                .add_neighbor(index2, cost);
813                            index
814                                .sites
815                                .get_mut(index2)
816                                .economy
817                                .add_neighbor(index1, cost);
818                        }
819                    }
820                }
821            }
822        }
823
824        // TODO: this looks optimizable
825
826        // collect natural resources
827        prof_span!(guard, "collect natural resources");
828        let sites = &mut index.sites;
829        (0..ctx.sim.map_size_lg().chunks_len()).for_each(|posi| {
830            let chpos = uniform_idx_as_vec2(ctx.sim.map_size_lg(), posi);
831            let wpos = chpos.map(|e| e as i64) * TerrainChunkSize::RECT_SIZE.map(|e| e as i64);
832            let closest_site = (*sites)
833                .iter_mut()
834                .filter(|s| !matches!(s.1.kind, crate::site::SiteKind::Myrmidon(_)))
835                .min_by_key(|(_id, s)| s.get_origin().map(|e| e as i64).distance_squared(wpos));
836            if let Some((_id, s)) = closest_site {
837                let distance_squared = s.get_origin().map(|e| e as i64).distance_squared(wpos);
838                s.economy
839                    .add_chunk(ctx.sim.get(chpos).unwrap(), distance_squared);
840            }
841        });
842        drop(guard);
843
844        prof_span!(guard, "generate airship routes");
845        this.airships
846            .generate_airship_routes(sites, sim, index.seed);
847        drop(guard);
848
849        sites
850            .iter_mut()
851            .for_each(|(_, s)| s.economy.cache_economy());
852
853        this
854    }
855
856    pub fn place(&self, id: Id<Place>) -> &Place { self.places.get(id) }
857
858    pub fn sites(&self) -> impl Iterator<Item = &Site> + '_ { self.sites.values() }
859
860    #[expect(dead_code)]
861    fn display_info(&self) {
862        for (id, civ) in self.civs.iter() {
863            println!("# Civilisation {:?}", id);
864            println!("Name: <unnamed>");
865            println!("Homeland: {:#?}", self.places.get(civ.homeland));
866        }
867
868        for (id, site) in self.sites.iter() {
869            println!("# Site {:?}", id);
870            println!("{:#?}", site);
871        }
872    }
873
874    /// Return the direct track between two places, bool if the track should be
875    /// reversed or not
876    pub fn track_between(&self, a: Id<Site>, b: Id<Site>) -> Option<(Id<Track>, bool)> {
877        self.track_map
878            .get(&a)
879            .and_then(|dests| Some((*dests.get(&b)?, false)))
880            .or_else(|| {
881                self.track_map
882                    .get(&b)
883                    .and_then(|dests| Some((*dests.get(&a)?, true)))
884            })
885    }
886
887    /// Return an iterator over a site's neighbors
888    pub fn neighbors(&self, site: Id<Site>) -> impl Iterator<Item = Id<Site>> + '_ {
889        let to = self
890            .track_map
891            .get(&site)
892            .map(|dests| dests.keys())
893            .into_iter()
894            .flatten();
895        let fro = self
896            .track_map
897            .iter()
898            .filter(move |(_, dests)| dests.contains_key(&site))
899            .map(|(p, _)| p);
900        to.chain(fro).filter(move |p| **p != site).copied()
901    }
902
903    /// Find the cheapest route between two places
904    fn route_between(&self, a: Id<Site>, b: Id<Site>) -> Option<(Path<Id<Site>>, f32)> {
905        let heuristic = move |p: &Id<Site>| {
906            (self
907                .sites
908                .get(*p)
909                .center
910                .distance_squared(self.sites.get(b).center) as f32)
911                .sqrt()
912        };
913        let transition =
914            |a: Id<Site>, b: Id<Site>| self.tracks.get(self.track_between(a, b).unwrap().0).cost;
915        let neighbors = |p: &Id<Site>| {
916            let p = *p;
917            self.neighbors(p)
918                .map(move |neighbor| (neighbor, transition(p, neighbor)))
919        };
920        let satisfied = |p: &Id<Site>| *p == b;
921        // We use this hasher (FxHasher64) because
922        // (1) we don't care about DDOS attacks (ruling out SipHash);
923        // (2) we care about determinism across computers (ruling out AAHash);
924        // (3) we have 8-byte keys (for which FxHash is fastest).
925        let mut astar = Astar::new(100, a, BuildHasherDefault::<FxHasher64>::default());
926        astar.poll(100, heuristic, neighbors, satisfied).into_path()
927    }
928
929    fn birth_civ(&mut self, ctx: &mut GenCtx<impl Rng>) -> Option<Id<Civ>> {
930        // TODO: specify SiteKind based on where a suitable location is found
931        let kind = match ctx.rng.gen_range(0..64) {
932            0..=8 => SiteKind::CliffTown,
933            9..=17 => SiteKind::DesertCity,
934            18..=23 => SiteKind::SavannahTown,
935            24..=33 => SiteKind::CoastalTown,
936            _ => SiteKind::Refactor,
937        };
938        let world_dims = ctx.sim.get_aabr();
939        let avoid_town_enemies = ProximityRequirementsBuilder::new()
940            .avoid_all_of(self.town_enemies(), 60)
941            .finalize(&world_dims);
942        let loc = (0..100)
943            .flat_map(|_| {
944                find_site_loc(ctx, &avoid_town_enemies, &kind).and_then(|loc| {
945                    town_attributes_of_site(loc, ctx.sim)
946                        .map(|town_attrs| (loc, town_attrs.score()))
947                })
948            })
949            .reduce(|a, b| if a.1 > b.1 { a } else { b })?
950            .0;
951
952        let site = self.establish_site(ctx, loc, |place| Site {
953            kind,
954            site_tmp: None,
955            center: loc,
956            place,
957            /* most economic members have moved to site/Economy */
958            /* last_exports: Stocks::from_default(0.0),
959             * export_targets: Stocks::from_default(0.0),
960             * //trade_states: Stocks::default(), */
961        });
962
963        let civ = self.civs.insert(Civ {
964            capital: site,
965            homeland: self.sites.get(site).place,
966        });
967
968        Some(civ)
969    }
970
971    fn establish_place(
972        &mut self,
973        _ctx: &mut GenCtx<impl Rng>,
974        loc: Vec2<i32>,
975        _area: Range<usize>,
976    ) -> Id<Place> {
977        self.places.insert(Place { center: loc })
978    }
979
980    /// Adds lake POIs and names them
981    fn name_biomes(&mut self, ctx: &mut GenCtx<impl Rng>) {
982        prof_span!("name_biomes");
983        let map_size_lg = ctx.sim.map_size_lg();
984        let world_size = map_size_lg.chunks();
985        let mut biomes: Vec<(common::terrain::BiomeKind, Vec<usize>)> = Vec::new();
986        let mut explored = vec![false; world_size.x as usize * world_size.y as usize];
987        let mut to_floodfill = Vec::new();
988        let mut to_explore = Vec::new();
989        // TODO: have start point in center and ignore ocean?
990        let start_point = 0;
991        to_explore.push(start_point);
992
993        while let Some(exploring) = to_explore.pop() {
994            if explored[exploring] {
995                continue;
996            }
997            to_floodfill.push(exploring);
998            // Should always be a chunk on the map
999            let biome = ctx.sim.chunks[exploring].get_biome();
1000            let mut filled = Vec::new();
1001
1002            while let Some(filling) = to_floodfill.pop() {
1003                explored[filling] = true;
1004                filled.push(filling);
1005                for neighbour in common::terrain::neighbors(map_size_lg, filling) {
1006                    if explored[neighbour] {
1007                        continue;
1008                    }
1009                    let n_biome = ctx.sim.chunks[neighbour].get_biome();
1010                    if n_biome == biome {
1011                        to_floodfill.push(neighbour);
1012                    } else {
1013                        to_explore.push(neighbour);
1014                    }
1015                }
1016            }
1017
1018            biomes.push((biome, filled));
1019        }
1020
1021        prof_span!("after flood fill");
1022        let mut biome_count = 0;
1023        for biome in biomes {
1024            let name = match biome.0 {
1025                common::terrain::BiomeKind::Lake if biome.1.len() as u32 > 200 => Some(format!(
1026                    "{} {}",
1027                    ["Lake", "Loch"].choose(&mut ctx.rng).unwrap(),
1028                    NameGen::location(&mut ctx.rng).generate_lake_custom()
1029                )),
1030                common::terrain::BiomeKind::Lake if biome.1.len() as u32 > 10 => Some(format!(
1031                    "{} {}",
1032                    NameGen::location(&mut ctx.rng).generate_lake_custom(),
1033                    ["Pool", "Well", "Pond"].choose(&mut ctx.rng).unwrap()
1034                )),
1035                common::terrain::BiomeKind::Grassland if biome.1.len() as u32 > 750 => {
1036                    Some(format!(
1037                        "{} {}",
1038                        [
1039                            NameGen::location(&mut ctx.rng).generate_grassland_engl(),
1040                            NameGen::location(&mut ctx.rng).generate_grassland_custom()
1041                        ]
1042                        .choose(&mut ctx.rng)
1043                        .unwrap(),
1044                        [
1045                            "Grasslands",
1046                            "Plains",
1047                            "Meadows",
1048                            "Fields",
1049                            "Heath",
1050                            "Hills",
1051                            "Prairie",
1052                            "Lowlands",
1053                            "Steppe",
1054                            "Downs",
1055                            "Greens",
1056                        ]
1057                        .choose(&mut ctx.rng)
1058                        .unwrap()
1059                    ))
1060                },
1061                common::terrain::BiomeKind::Ocean if biome.1.len() as u32 > 750 => Some(format!(
1062                    "{} {}",
1063                    [
1064                        NameGen::location(&mut ctx.rng).generate_ocean_engl(),
1065                        NameGen::location(&mut ctx.rng).generate_ocean_custom()
1066                    ]
1067                    .choose(&mut ctx.rng)
1068                    .unwrap(),
1069                    ["Sea", "Bay", "Gulf", "Deep", "Depths", "Ocean", "Blue",]
1070                        .choose(&mut ctx.rng)
1071                        .unwrap()
1072                )),
1073                common::terrain::BiomeKind::Mountain if biome.1.len() as u32 > 750 => {
1074                    Some(format!(
1075                        "{} {}",
1076                        [
1077                            NameGen::location(&mut ctx.rng).generate_mountain_engl(),
1078                            NameGen::location(&mut ctx.rng).generate_mountain_custom()
1079                        ]
1080                        .choose(&mut ctx.rng)
1081                        .unwrap(),
1082                        [
1083                            "Mountains",
1084                            "Range",
1085                            "Reach",
1086                            "Massif",
1087                            "Rocks",
1088                            "Cliffs",
1089                            "Peaks",
1090                            "Heights",
1091                            "Bluffs",
1092                            "Ridge",
1093                            "Canyon",
1094                            "Plateau",
1095                        ]
1096                        .choose(&mut ctx.rng)
1097                        .unwrap()
1098                    ))
1099                },
1100                common::terrain::BiomeKind::Snowland if biome.1.len() as u32 > 750 => {
1101                    Some(format!(
1102                        "{} {}",
1103                        [
1104                            NameGen::location(&mut ctx.rng).generate_snowland_engl(),
1105                            NameGen::location(&mut ctx.rng).generate_snowland_custom()
1106                        ]
1107                        .choose(&mut ctx.rng)
1108                        .unwrap(),
1109                        [
1110                            "Snowlands",
1111                            "Glacier",
1112                            "Tundra",
1113                            "Drifts",
1114                            "Snowfields",
1115                            "Hills",
1116                            "Downs",
1117                            "Uplands",
1118                            "Highlands",
1119                        ]
1120                        .choose(&mut ctx.rng)
1121                        .unwrap()
1122                    ))
1123                },
1124                common::terrain::BiomeKind::Desert if biome.1.len() as u32 > 750 => Some(format!(
1125                    "{} {}",
1126                    [
1127                        NameGen::location(&mut ctx.rng).generate_desert_engl(),
1128                        NameGen::location(&mut ctx.rng).generate_desert_custom()
1129                    ]
1130                    .choose(&mut ctx.rng)
1131                    .unwrap(),
1132                    [
1133                        "Desert", "Sands", "Sandsea", "Drifts", "Dunes", "Droughts", "Flats",
1134                    ]
1135                    .choose(&mut ctx.rng)
1136                    .unwrap()
1137                )),
1138                common::terrain::BiomeKind::Swamp if biome.1.len() as u32 > 200 => Some(format!(
1139                    "{} {}",
1140                    NameGen::location(&mut ctx.rng).generate_swamp_engl(),
1141                    [
1142                        "Swamp",
1143                        "Swamps",
1144                        "Swamplands",
1145                        "Marsh",
1146                        "Marshlands",
1147                        "Morass",
1148                        "Mire",
1149                        "Bog",
1150                        "Wetlands",
1151                        "Fen",
1152                        "Moors",
1153                    ]
1154                    .choose(&mut ctx.rng)
1155                    .unwrap()
1156                )),
1157                common::terrain::BiomeKind::Jungle if biome.1.len() as u32 > 85 => Some(format!(
1158                    "{} {}",
1159                    [
1160                        NameGen::location(&mut ctx.rng).generate_jungle_engl(),
1161                        NameGen::location(&mut ctx.rng).generate_jungle_custom()
1162                    ]
1163                    .choose(&mut ctx.rng)
1164                    .unwrap(),
1165                    [
1166                        "Jungle",
1167                        "Rainforest",
1168                        "Greatwood",
1169                        "Wilds",
1170                        "Wildwood",
1171                        "Tangle",
1172                        "Tanglewood",
1173                        "Bush",
1174                    ]
1175                    .choose(&mut ctx.rng)
1176                    .unwrap()
1177                )),
1178                common::terrain::BiomeKind::Forest if biome.1.len() as u32 > 750 => Some(format!(
1179                    "{} {}",
1180                    [
1181                        NameGen::location(&mut ctx.rng).generate_forest_engl(),
1182                        NameGen::location(&mut ctx.rng).generate_forest_custom()
1183                    ]
1184                    .choose(&mut ctx.rng)
1185                    .unwrap(),
1186                    ["Forest", "Woodlands", "Woods", "Glades", "Grove", "Weald",]
1187                        .choose(&mut ctx.rng)
1188                        .unwrap()
1189                )),
1190                common::terrain::BiomeKind::Savannah if biome.1.len() as u32 > 750 => {
1191                    Some(format!(
1192                        "{} {}",
1193                        [
1194                            NameGen::location(&mut ctx.rng).generate_savannah_engl(),
1195                            NameGen::location(&mut ctx.rng).generate_savannah_custom()
1196                        ]
1197                        .choose(&mut ctx.rng)
1198                        .unwrap(),
1199                        [
1200                            "Savannah",
1201                            "Shrublands",
1202                            "Sierra",
1203                            "Prairie",
1204                            "Lowlands",
1205                            "Flats",
1206                        ]
1207                        .choose(&mut ctx.rng)
1208                        .unwrap()
1209                    ))
1210                },
1211                common::terrain::BiomeKind::Taiga if biome.1.len() as u32 > 750 => Some(format!(
1212                    "{} {}",
1213                    [
1214                        NameGen::location(&mut ctx.rng).generate_taiga_engl(),
1215                        NameGen::location(&mut ctx.rng).generate_taiga_custom()
1216                    ]
1217                    .choose(&mut ctx.rng)
1218                    .unwrap(),
1219                    [
1220                        "Forest",
1221                        "Woodlands",
1222                        "Woods",
1223                        "Timberlands",
1224                        "Highlands",
1225                        "Uplands",
1226                    ]
1227                    .choose(&mut ctx.rng)
1228                    .unwrap()
1229                )),
1230                _ => None,
1231            };
1232            if let Some(name) = name {
1233                // find average center of the biome
1234                let center = biome
1235                    .1
1236                    .iter()
1237                    .map(|b| {
1238                        uniform_idx_as_vec2(map_size_lg, *b).as_::<f32>() / biome.1.len() as f32
1239                    })
1240                    .sum::<Vec2<f32>>()
1241                    .as_::<i32>();
1242                // Select the point closest to the center
1243                let idx = *biome
1244                    .1
1245                    .iter()
1246                    .min_by_key(|&b| center.distance_squared(uniform_idx_as_vec2(map_size_lg, *b)))
1247                    .unwrap();
1248                let id = self.pois.insert(PointOfInterest {
1249                    name,
1250                    loc: uniform_idx_as_vec2(map_size_lg, idx),
1251                    kind: PoiKind::Biome(biome.1.len() as u32),
1252                });
1253                for chunk in biome.1 {
1254                    ctx.sim.chunks[chunk].poi = Some(id);
1255                }
1256                biome_count += 1;
1257            }
1258        }
1259
1260        info!(?biome_count, "all biomes named");
1261    }
1262
1263    /// Adds mountain POIs and name them
1264    fn name_peaks(&mut self, ctx: &mut GenCtx<impl Rng>) {
1265        prof_span!("name_peaks");
1266        let map_size_lg = ctx.sim.map_size_lg();
1267        const MIN_MOUNTAIN_ALT: f32 = 600.0;
1268        const MIN_MOUNTAIN_CHAOS: f32 = 0.35;
1269        let rng = &mut ctx.rng;
1270        let sim_chunks = &ctx.sim.chunks;
1271        let peaks = sim_chunks
1272            .iter()
1273            .enumerate()
1274            .filter(|(posi, chunk)| {
1275                let neighbor_alts_max = common::terrain::neighbors(map_size_lg, *posi)
1276                    .map(|i| sim_chunks[i].alt as u32)
1277                    .max();
1278                chunk.alt > MIN_MOUNTAIN_ALT
1279                    && chunk.chaos > MIN_MOUNTAIN_CHAOS
1280                    && neighbor_alts_max.is_some_and(|n_alt| chunk.alt as u32 > n_alt)
1281            })
1282            .map(|(posi, chunk)| {
1283                (
1284                    posi,
1285                    uniform_idx_as_vec2(map_size_lg, posi),
1286                    (chunk.alt - CONFIG.sea_level) as u32,
1287                )
1288            })
1289            .collect::<Vec<(usize, Vec2<i32>, u32)>>();
1290        let mut num_peaks = 0;
1291        let mut removals = vec![false; peaks.len()];
1292        for (i, peak) in peaks.iter().enumerate() {
1293            for (k, n_peak) in peaks.iter().enumerate() {
1294                // If the difference in position of this peak and another is
1295                // below a threshold and this peak's altitude is lower, remove the
1296                // peak from the list
1297                if i != k
1298                    && (peak.1).distance_squared(n_peak.1) < POI_THINNING_DIST_SQRD
1299                    && peak.2 <= n_peak.2
1300                {
1301                    // Remove this peak
1302                    // This cannot panic as `removals` is the same length as `peaks`
1303                    // i is the index in `peaks`
1304                    removals[i] = true;
1305                }
1306            }
1307        }
1308        peaks
1309            .iter()
1310            .enumerate()
1311            .filter(|&(i, _)| !removals[i])
1312            .for_each(|(_, (_, loc, alt))| {
1313                num_peaks += 1;
1314                self.pois.insert(PointOfInterest {
1315                    name: {
1316                        let name = NameGen::location(rng).generate();
1317                        if *alt < 1000 {
1318                            match rng.gen_range(0..6) {
1319                                0 => format!("{} Bluff", name),
1320                                1 => format!("{} Crag", name),
1321                                _ => format!("{} Hill", name),
1322                            }
1323                        } else {
1324                            match rng.gen_range(0..8) {
1325                                0 => format!("{}'s Peak", name),
1326                                1 => format!("{} Peak", name),
1327                                2 => format!("{} Summit", name),
1328                                _ => format!("Mount {}", name),
1329                            }
1330                        }
1331                    },
1332                    kind: PoiKind::Peak(*alt),
1333                    loc: *loc,
1334                });
1335            });
1336        info!(?num_peaks, "all peaks named");
1337    }
1338
1339    fn establish_site(
1340        &mut self,
1341        ctx: &mut GenCtx<impl Rng>,
1342        loc: Vec2<i32>,
1343        site_fn: impl FnOnce(Id<Place>) -> Site,
1344    ) -> Id<Site> {
1345        prof_span!("establish_site");
1346        const SITE_AREA: Range<usize> = 1..4; //64..256;
1347
1348        fn establish_site(
1349            civs: &mut Civs,
1350            ctx: &mut GenCtx<impl Rng>,
1351            loc: Vec2<i32>,
1352            site_fn: impl FnOnce(Id<Place>) -> Site,
1353        ) -> Id<Site> {
1354            let place = match ctx.sim.get(loc).and_then(|site| site.place) {
1355                Some(place) => place,
1356                None => civs.establish_place(ctx, loc, SITE_AREA),
1357            };
1358
1359            civs.sites.insert(site_fn(place))
1360        }
1361
1362        let site = establish_site(self, ctx, loc, site_fn);
1363
1364        // Find neighbors
1365        // Note, the maximum distance that I have so far observed not hitting the
1366        // iteration limit in `find_path` is 364. So I think this is a reasonable
1367        // limit (although the relationship between distance and pathfinding iterations
1368        // can be a bit variable). Note, I have seen paths reach the iteration limit
1369        // with distances as small as 137, so this certainly doesn't catch all
1370        // cases that would fail.
1371        const MAX_NEIGHBOR_DISTANCE: f32 = 400.0;
1372        let mut nearby = self
1373            .sites
1374            .iter()
1375            .filter(|&(id, _)| id != site)
1376            .filter(|(_, p)| {
1377                matches!(
1378                    p.kind,
1379                    SiteKind::Refactor
1380                        | SiteKind::Settlement
1381                        | SiteKind::CliffTown
1382                        | SiteKind::SavannahTown
1383                        | SiteKind::CoastalTown
1384                        | SiteKind::DesertCity
1385                        | SiteKind::Castle
1386                )
1387            })
1388            .map(|(id, p)| (id, (p.center.distance_squared(loc) as f32).sqrt()))
1389            .filter(|(_, dist)| *dist < MAX_NEIGHBOR_DISTANCE)
1390            .collect::<Vec<_>>();
1391        nearby.sort_by_key(|(_, dist)| *dist as i32);
1392
1393        if let SiteKind::Refactor
1394        | SiteKind::Settlement
1395        | SiteKind::CliffTown
1396        | SiteKind::SavannahTown
1397        | SiteKind::CoastalTown
1398        | SiteKind::DesertCity
1399        | SiteKind::Castle = self.sites[site].kind
1400        {
1401            for (nearby, _) in nearby.into_iter().take(4) {
1402                prof_span!("for nearby");
1403                // Find a route using existing paths
1404                //
1405                // If the novel path isn't efficient compared to this, don't use it
1406                let max_novel_cost = self
1407                    .route_between(site, nearby)
1408                    .map_or(f32::MAX, |(_, route_cost)| route_cost / 3.0);
1409
1410                let start = loc;
1411                let end = self.sites.get(nearby).center;
1412                // Find a novel path.
1413                let get_bridge = |start| self.bridges.get(&start).map(|(end, _)| *end);
1414                if let Some((path, cost)) = find_path(ctx, get_bridge, start, end, max_novel_cost) {
1415                    // Write the track to the world as a path
1416                    for locs in path.nodes().windows(3) {
1417                        if let Some((i, _)) = NEIGHBORS
1418                            .iter()
1419                            .enumerate()
1420                            .find(|(_, dir)| **dir == locs[0] - locs[1])
1421                        {
1422                            ctx.sim.get_mut(locs[0]).unwrap().path.0.neighbors |=
1423                                1 << ((i as u8 + 4) % 8);
1424                            ctx.sim.get_mut(locs[1]).unwrap().path.0.neighbors |= 1 << (i as u8);
1425                        }
1426
1427                        if let Some((i, _)) = NEIGHBORS
1428                            .iter()
1429                            .enumerate()
1430                            .find(|(_, dir)| **dir == locs[2] - locs[1])
1431                        {
1432                            ctx.sim.get_mut(locs[2]).unwrap().path.0.neighbors |=
1433                                1 << ((i as u8 + 4) % 8);
1434
1435                            ctx.sim.get_mut(locs[1]).unwrap().path.0.neighbors |= 1 << (i as u8);
1436                            ctx.sim.get_mut(locs[1]).unwrap().path.0.offset =
1437                                Vec2::new(ctx.rng.gen_range(-16..17), ctx.rng.gen_range(-16..17));
1438                        } else if !self.bridges.contains_key(&locs[1]) {
1439                            let center = (locs[1] + locs[2]) / 2;
1440                            let id =
1441                                establish_site(self, &mut ctx.reseed(), center, move |place| {
1442                                    Site {
1443                                        kind: SiteKind::Bridge(locs[1], locs[2]),
1444                                        site_tmp: None,
1445                                        center,
1446                                        place,
1447                                    }
1448                                });
1449                            self.bridges.insert(locs[1], (locs[2], id));
1450                            self.bridges.insert(locs[2], (locs[1], id));
1451                        }
1452                        /*
1453                        let to_prev_idx = NEIGHBORS
1454                            .iter()
1455                            .enumerate()
1456                            .find(|(_, dir)| **dir == (locs[0] - locs[1]).map(|e| e.signum()))
1457                            .expect("Track locations must be neighbors")
1458                            .0;
1459
1460                        let to_next_idx = NEIGHBORS
1461                            .iter()
1462                            .enumerate()
1463                            .find(|(_, dir)| **dir == (locs[2] - locs[1]).map(|e| e.signum()))
1464                            .expect("Track locations must be neighbors")
1465                            .0;
1466
1467                        ctx.sim.get_mut(locs[0]).unwrap().path.0.neighbors |=
1468                            1 << ((to_prev_idx as u8 + 4) % 8);
1469                        ctx.sim.get_mut(locs[2]).unwrap().path.0.neighbors |=
1470                            1 << ((to_next_idx as u8 + 4) % 8);
1471                        let mut chunk = ctx.sim.get_mut(locs[1]).unwrap();
1472                        chunk.path.0.neighbors |=
1473                            (1 << (to_prev_idx as u8)) | (1 << (to_next_idx as u8));
1474                        */
1475                    }
1476
1477                    // Take note of the track
1478                    let track = self.tracks.insert(Track { cost, path });
1479                    self.track_map
1480                        .entry(site)
1481                        .or_default()
1482                        .insert(nearby, track);
1483                }
1484            }
1485        }
1486
1487        site
1488    }
1489
1490    fn gnarling_enemies(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
1491        self.sites().filter_map(|s| match s.kind {
1492            SiteKind::Tree | SiteKind::GiantTree => None,
1493            _ => Some(s.center),
1494        })
1495    }
1496
1497    fn adlet_enemies(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
1498        self.sites().filter_map(|s| match s.kind {
1499            SiteKind::Tree | SiteKind::GiantTree => None,
1500            _ => Some(s.center),
1501        })
1502    }
1503
1504    fn haniwa_enemies(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
1505        self.sites().filter_map(|s| match s.kind {
1506            SiteKind::Tree | SiteKind::GiantTree => None,
1507            _ => Some(s.center),
1508        })
1509    }
1510
1511    fn chapel_site_enemies(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
1512        self.sites().filter_map(|s| match s.kind {
1513            SiteKind::Tree | SiteKind::GiantTree => None,
1514            _ => Some(s.center),
1515        })
1516    }
1517
1518    fn mine_site_enemies(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
1519        self.sites().filter_map(|s| match s.kind {
1520            SiteKind::Tree | SiteKind::GiantTree => None,
1521            _ => Some(s.center),
1522        })
1523    }
1524
1525    fn terracotta_enemies(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
1526        self.sites().filter_map(|s| match s.kind {
1527            SiteKind::Tree | SiteKind::GiantTree => None,
1528            _ => Some(s.center),
1529        })
1530    }
1531
1532    fn cultist_enemies(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
1533        self.sites().filter_map(|s| match s.kind {
1534            SiteKind::Tree | SiteKind::GiantTree => None,
1535            _ => Some(s.center),
1536        })
1537    }
1538
1539    fn myrmidon_enemies(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
1540        self.sites().filter_map(|s| match s.kind {
1541            SiteKind::Tree | SiteKind::GiantTree => None,
1542            _ => Some(s.center),
1543        })
1544    }
1545
1546    fn vampire_castle_enemies(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
1547        self.sites().filter_map(|s| match s.kind {
1548            SiteKind::Tree | SiteKind::GiantTree => None,
1549            _ => Some(s.center),
1550        })
1551    }
1552
1553    fn tree_enemies(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
1554        self.sites().map(|s| s.center)
1555    }
1556
1557    fn castle_enemies(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
1558        self.sites().filter_map(|s| {
1559            if s.is_settlement() {
1560                None
1561            } else {
1562                Some(s.center)
1563            }
1564        })
1565    }
1566
1567    fn jungle_ruin_enemies(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
1568        self.sites().filter_map(|s| match s.kind {
1569            SiteKind::Tree | SiteKind::GiantTree => None,
1570            _ => Some(s.center),
1571        })
1572    }
1573
1574    fn town_enemies(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
1575        self.sites().filter_map(|s| match s.kind {
1576            SiteKind::Castle | SiteKind::Citadel => None,
1577            _ => Some(s.center),
1578        })
1579    }
1580
1581    fn towns(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
1582        self.sites().filter_map(|s| {
1583            if s.is_settlement() {
1584                Some(s.center)
1585            } else {
1586                None
1587            }
1588        })
1589    }
1590
1591    fn pirate_hideout_enemies(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
1592        self.sites().filter_map(|s| match s.kind {
1593            SiteKind::Tree | SiteKind::GiantTree => None,
1594            _ => Some(s.center),
1595        })
1596    }
1597
1598    fn sahagin_enemies(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
1599        self.sites().filter_map(|s| match s.kind {
1600            SiteKind::Tree | SiteKind::GiantTree => None,
1601            _ => Some(s.center),
1602        })
1603    }
1604
1605    fn rock_circle_enemies(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
1606        self.sites().filter_map(|s| match s.kind {
1607            SiteKind::Tree | SiteKind::GiantTree => None,
1608            _ => Some(s.center),
1609        })
1610    }
1611
1612    fn troll_cave_enemies(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
1613        self.sites().filter_map(|s| match s.kind {
1614            SiteKind::Tree | SiteKind::GiantTree => None,
1615            _ => Some(s.center),
1616        })
1617    }
1618
1619    fn camp_enemies(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
1620        self.sites().filter_map(|s| match s.kind {
1621            SiteKind::Tree | SiteKind::GiantTree => None,
1622            _ => Some(s.center),
1623        })
1624    }
1625}
1626
1627/// Attempt to find a path between two locations
1628fn find_path(
1629    ctx: &mut GenCtx<impl Rng>,
1630    get_bridge: impl Fn(Vec2<i32>) -> Option<Vec2<i32>>,
1631    a: Vec2<i32>,
1632    b: Vec2<i32>,
1633    max_path_cost: f32,
1634) -> Option<(Path<Vec2<i32>>, f32)> {
1635    prof_span!("find_path");
1636    const MAX_PATH_ITERS: usize = 100_000;
1637    let sim = &ctx.sim;
1638    // NOTE: If heuristic overestimates the actual cost, then A* is not guaranteed
1639    // to produce the least-cost path (since it will explore partially based on
1640    // the heuristic).
1641    // TODO: heuristic can be larger than actual cost, since existing bridges cost
1642    // 1.0 (after the 1.0 that is added to everthting), but they can cover
1643    // multiple chunks.
1644    let heuristic = move |l: &Vec2<i32>| (l.distance_squared(b) as f32).sqrt();
1645    let neighbors = |l: &Vec2<i32>| {
1646        let l = *l;
1647        let bridge = get_bridge(l);
1648        let potential = walk_in_all_dirs(sim, bridge, l);
1649        potential
1650            .into_iter()
1651            .filter_map(|p| p.map(|(node, cost)| (node, cost + 1.0)))
1652    };
1653    let satisfied = |l: &Vec2<i32>| *l == b;
1654    // We use this hasher (FxHasher64) because
1655    // (1) we don't care about DDOS attacks (ruling out SipHash);
1656    // (2) we care about determinism across computers (ruling out AAHash);
1657    // (3) we have 8-byte keys (for which FxHash is fastest).
1658    let mut astar = Astar::new(
1659        MAX_PATH_ITERS,
1660        a,
1661        BuildHasherDefault::<FxHasher64>::default(),
1662    )
1663    .with_max_cost(max_path_cost);
1664    astar
1665        .poll(MAX_PATH_ITERS, heuristic, neighbors, satisfied)
1666        .into_path()
1667}
1668
1669/// Return Some if travel between a location and a chunk next to it is permitted
1670/// If permitted, the approximate relative const of traversal is given
1671// (TODO: by whom?)
1672/// Return tuple: (final location, cost)
1673///
1674/// For efficiency, this computes for all 8 directions at once.
1675fn walk_in_all_dirs(
1676    sim: &WorldSim,
1677    bridge: Option<Vec2<i32>>,
1678    a: Vec2<i32>,
1679) -> [Option<(Vec2<i32>, f32)>; 8] {
1680    let mut potential = [None; 8];
1681
1682    let adjacents = NEIGHBORS.map(|dir| a + dir);
1683
1684    let Some(a_chunk) = sim.get(a) else {
1685        return potential;
1686    };
1687    let mut chunks = [None; 8];
1688    for i in 0..8 {
1689        if loc_suitable_for_walking(sim, adjacents[i]) {
1690            chunks[i] = sim.get(adjacents[i]);
1691        }
1692    }
1693
1694    for i in 0..8 {
1695        let Some(b_chunk) = chunks[i] else { continue };
1696
1697        let hill_cost = ((b_chunk.alt - a_chunk.alt).abs() / 5.0).powi(2);
1698        let water_cost = (b_chunk.water_alt - b_chunk.alt + 8.0).clamped(0.0, 8.0) * 3.0; // Try not to path swamps / tidal areas
1699        let wild_cost = if b_chunk.path.0.is_way() {
1700            0.0 // Traversing existing paths has no additional cost!
1701        } else {
1702            3.0 // + (1.0 - b_chunk.tree_density) * 20.0 // Prefer going through forests, for aesthetics
1703        };
1704
1705        let cost = 1.0 + hill_cost + water_cost + wild_cost;
1706        potential[i] = Some((adjacents[i], cost));
1707    }
1708
1709    // Look for potential bridge spots in the cardinal directions if
1710    // `loc_suitable_for_wallking` was false for the adjacent chunk.
1711    for (i, &dir) in NEIGHBORS.iter().enumerate() {
1712        let is_cardinal_dir = dir.x == 0 || dir.y == 0;
1713        if is_cardinal_dir && potential[i].is_none() {
1714            // if we can skip over unsuitable area with a bridge
1715            potential[i] = (4..=5).find_map(|i| {
1716                loc_suitable_for_walking(sim, a + dir * i)
1717                    .then(|| (a + dir * i, 120.0 + (i - 4) as f32 * 10.0))
1718            });
1719        }
1720    }
1721
1722    // If current position is a bridge, skip to its destination.
1723    if let Some(p) = bridge {
1724        let dir = (p - a).map(|e| e.signum());
1725        if let Some((dir_index, _)) = NEIGHBORS
1726            .iter()
1727            .enumerate()
1728            .find(|(_, n_dir)| **n_dir == dir)
1729        {
1730            potential[dir_index] = Some((p, (p - a).map(|e| e.abs()).reduce_max() as f32));
1731        }
1732    }
1733
1734    potential
1735}
1736
1737/// Return true if a position is suitable for walking on
1738fn loc_suitable_for_walking(sim: &WorldSim, loc: Vec2<i32>) -> bool {
1739    if sim.get(loc).is_some() {
1740        NEIGHBORS.iter().all(|n| {
1741            sim.get(loc + *n)
1742                .is_some_and(|chunk| !chunk.river.near_water())
1743        })
1744    } else {
1745        false
1746    }
1747}
1748
1749/// Attempt to search for a location that's suitable for site construction
1750// FIXME when a `close_to_one_of` requirement is passed in, we should start with
1751// just the chunks around those locations instead of random sampling the entire
1752// map
1753fn find_site_loc(
1754    ctx: &mut GenCtx<impl Rng>,
1755    proximity_reqs: &ProximityRequirements,
1756    site_kind: &SiteKind,
1757) -> Option<Vec2<i32>> {
1758    prof_span!("find_site_loc");
1759    const MAX_ATTEMPTS: usize = 10000;
1760    let mut loc = None;
1761    let location_hint = proximity_reqs.location_hint;
1762    for _ in 0..MAX_ATTEMPTS {
1763        let test_loc = loc.unwrap_or_else(|| {
1764            Vec2::new(
1765                ctx.rng.gen_range(location_hint.min.x..location_hint.max.x),
1766                ctx.rng.gen_range(location_hint.min.y..location_hint.max.y),
1767            )
1768        });
1769
1770        let is_suitable_loc = site_kind.is_suitable_loc(test_loc, ctx.sim);
1771        if is_suitable_loc && proximity_reqs.satisfied_by(test_loc) {
1772            if site_kind.exclusion_radius_clear(ctx.sim, test_loc) {
1773                return Some(test_loc);
1774            }
1775
1776            // If the current location is suitable and meets proximity requirements,
1777            // try nearby spot downhill.
1778            loc = ctx.sim.get(test_loc).and_then(|c| c.downhill);
1779        }
1780    }
1781
1782    debug!("Failed to place site {:?}.", site_kind);
1783    None
1784}
1785
1786fn town_attributes_of_site(loc: Vec2<i32>, sim: &WorldSim) -> Option<TownSiteAttributes> {
1787    sim.get(loc).map(|chunk| {
1788        const RESOURCE_RADIUS: i32 = 1;
1789        let mut river_chunks = 0;
1790        let mut lake_chunks = 0;
1791        let mut ocean_chunks = 0;
1792        let mut rock_chunks = 0;
1793        let mut tree_chunks = 0;
1794        let mut farmable_chunks = 0;
1795        let mut farmable_needs_irrigation_chunks = 0;
1796        let mut land_chunks = 0;
1797        for x in (-RESOURCE_RADIUS)..RESOURCE_RADIUS {
1798            for y in (-RESOURCE_RADIUS)..RESOURCE_RADIUS {
1799                let check_loc = loc + Vec2::new(x, y).cpos_to_wpos();
1800                sim.get(check_loc).map(|c| {
1801                    if num::abs(chunk.alt - c.alt) < 200.0 {
1802                        if c.river.is_river() {
1803                            river_chunks += 1;
1804                        }
1805                        if c.river.is_lake() {
1806                            lake_chunks += 1;
1807                        }
1808                        if c.river.is_ocean() {
1809                            ocean_chunks += 1;
1810                        }
1811                        if c.tree_density > 0.7 {
1812                            tree_chunks += 1;
1813                        }
1814                        if c.rockiness < 0.3 && c.temp > CONFIG.snow_temp {
1815                            if c.surface_veg > 0.5 {
1816                                farmable_chunks += 1;
1817                            } else {
1818                                match c.get_biome() {
1819                                    common::terrain::BiomeKind::Savannah => {
1820                                        farmable_needs_irrigation_chunks += 1
1821                                    },
1822                                    common::terrain::BiomeKind::Desert => {
1823                                        farmable_needs_irrigation_chunks += 1
1824                                    },
1825                                    _ => (),
1826                                }
1827                            }
1828                        }
1829                        if !c.river.is_river() && !c.river.is_lake() && !c.river.is_ocean() {
1830                            land_chunks += 1;
1831                        }
1832                    }
1833                    // Mining is different since presumably you dig into the hillside
1834                    if c.rockiness > 0.7 && c.alt - chunk.alt > -10.0 {
1835                        rock_chunks += 1;
1836                    }
1837                });
1838            }
1839        }
1840        let has_river = river_chunks > 1;
1841        let has_lake = lake_chunks > 1;
1842        let vegetation_implies_potable_water = chunk.tree_density > 0.4
1843            && !matches!(chunk.get_biome(), common::terrain::BiomeKind::Swamp);
1844        let has_many_rocks = chunk.rockiness > 1.2;
1845        let warm_or_firewood = chunk.temp > CONFIG.snow_temp || tree_chunks > 2;
1846        let has_potable_water =
1847            { has_river || (has_lake && chunk.alt > 100.0) || vegetation_implies_potable_water };
1848        let has_building_materials = tree_chunks > 0
1849            || rock_chunks > 0
1850            || chunk.temp > CONFIG.tropical_temp && (has_river || has_lake);
1851        let water_rich = lake_chunks + river_chunks > 2;
1852        let can_grow_rice = water_rich
1853            && chunk.humidity + 1.0 > CONFIG.jungle_hum
1854            && chunk.temp + 1.0 > CONFIG.tropical_temp;
1855        let farming_score = if can_grow_rice {
1856            farmable_chunks * 2
1857        } else {
1858            farmable_chunks
1859        } + if water_rich {
1860            farmable_needs_irrigation_chunks
1861        } else {
1862            0
1863        };
1864        let fish_score = lake_chunks + ocean_chunks;
1865        let food_score = farming_score + fish_score;
1866        let mining_score = if tree_chunks > 1 { rock_chunks } else { 0 };
1867        let forestry_score = if has_river { tree_chunks } else { 0 };
1868        let trading_score = std::cmp::min(std::cmp::min(land_chunks, ocean_chunks), river_chunks);
1869        TownSiteAttributes {
1870            food_score,
1871            mining_score,
1872            forestry_score,
1873            trading_score,
1874            heating: warm_or_firewood,
1875            potable_water: has_potable_water,
1876            building_materials: has_building_materials,
1877            aquifer: has_many_rocks,
1878        }
1879    })
1880}
1881
1882pub struct TownSiteAttributes {
1883    food_score: i32,
1884    mining_score: i32,
1885    forestry_score: i32,
1886    trading_score: i32,
1887    heating: bool,
1888    potable_water: bool,
1889    building_materials: bool,
1890    aquifer: bool,
1891}
1892
1893impl TownSiteAttributes {
1894    pub fn score(&self) -> f32 {
1895        3.0 * (self.food_score as f32 + 1.0).log2()
1896            + 2.0 * (self.forestry_score as f32 + 1.0).log2()
1897            + (self.mining_score as f32 + 1.0).log2()
1898            + (self.trading_score as f32 + 1.0).log2()
1899    }
1900}
1901
1902#[derive(Debug)]
1903pub struct Civ {
1904    capital: Id<Site>,
1905    homeland: Id<Place>,
1906}
1907
1908#[derive(Debug)]
1909pub struct Place {
1910    pub center: Vec2<i32>,
1911    /* act sort of like territory with sites belonging to it
1912     * nat_res/NaturalResources was moved to Economy
1913     *    nat_res: NaturalResources, */
1914}
1915
1916pub struct Track {
1917    /// Cost of using this track relative to other paths. This cost is an
1918    /// arbitrary unit and doesn't make sense unless compared to other track
1919    /// costs.
1920    pub cost: f32,
1921    path: Path<Vec2<i32>>,
1922}
1923
1924impl Track {
1925    pub fn path(&self) -> &Path<Vec2<i32>> { &self.path }
1926}
1927
1928#[derive(Debug)]
1929pub struct Site {
1930    pub kind: SiteKind,
1931    // TODO: Remove this field when overhauling
1932    pub site_tmp: Option<Id<crate::site::Site>>,
1933    pub center: Vec2<i32>,
1934    pub place: Id<Place>,
1935}
1936
1937impl fmt::Display for Site {
1938    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1939        writeln!(f, "{:?}", self.kind)?;
1940
1941        Ok(())
1942    }
1943}
1944
1945#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1946pub enum SiteKind {
1947    Settlement,
1948    Castle,
1949    Refactor,
1950    CliffTown,
1951    SavannahTown,
1952    CoastalTown,
1953    DesertCity,
1954    ChapelSite,
1955    Terracotta,
1956    Tree,
1957    GiantTree,
1958    Gnarling,
1959    Citadel,
1960    Bridge(Vec2<i32>, Vec2<i32>),
1961    Adlet,
1962    Haniwa,
1963    PirateHideout,
1964    RockCircle,
1965    TrollCave,
1966    Camp,
1967    DwarvenMine,
1968    JungleRuin,
1969    Cultist,
1970    Sahagin,
1971    VampireCastle,
1972    GliderCourse,
1973    Myrmidon,
1974}
1975
1976impl SiteKind {
1977    pub fn is_suitable_loc(&self, loc: Vec2<i32>, sim: &WorldSim) -> bool {
1978        let on_land = || -> bool {
1979            if let Some(chunk) = sim.get(loc) {
1980                !chunk.river.is_ocean()
1981                    && !chunk.river.is_lake()
1982                    && !chunk.river.is_river()
1983                    && !chunk.is_underwater()
1984                    && !matches!(
1985                        chunk.get_biome(),
1986                        common::terrain::BiomeKind::Lake | common::terrain::BiomeKind::Ocean
1987                    )
1988            } else {
1989                false
1990            }
1991        };
1992        let on_flat_terrain = || -> bool {
1993            sim.get_gradient_approx(loc)
1994                .map(|grad| grad < 1.0)
1995                .unwrap_or(false)
1996        };
1997
1998        sim.get(loc).is_some_and(|chunk| {
1999            let suitable_for_town = || -> bool {
2000                let attributes = town_attributes_of_site(loc, sim);
2001                attributes.is_some_and(|attributes| {
2002                    // aquifer and has_many_rocks was added to make mesa clifftowns suitable for towns
2003                    (attributes.potable_water || (attributes.aquifer && matches!(self, SiteKind::CliffTown)))
2004                        && attributes.building_materials
2005                        && attributes.heating
2006                        // Because of how the algorithm for site2 towns work, they have to start on land.
2007                        && on_land()
2008                })
2009            };
2010            match self {
2011                SiteKind::Gnarling => {
2012                    on_land()
2013                        && on_flat_terrain()
2014                        && (-0.3..0.4).contains(&chunk.temp)
2015                        && chunk.tree_density > 0.75
2016                },
2017                SiteKind::Adlet => chunk.temp < -0.2 && chunk.cliff_height > 25.0,
2018                SiteKind::DwarvenMine => {
2019                    matches!(chunk.get_biome(), BiomeKind::Forest | BiomeKind::Desert)
2020                        && !chunk.near_cliffs()
2021                        && !chunk.river.near_water()
2022                        && on_flat_terrain()
2023                },
2024                SiteKind::Haniwa => {
2025                    on_land()
2026                        && on_flat_terrain()
2027                        && (-0.3..0.4).contains(&chunk.temp)
2028                },
2029                SiteKind::GiantTree | SiteKind::Tree => {
2030                    on_land()
2031                        && on_flat_terrain()
2032                        && chunk.tree_density > 0.4
2033                        && (-0.3..0.4).contains(&chunk.temp)
2034                },
2035                SiteKind::Citadel => true,
2036                SiteKind::CliffTown => {
2037                    chunk.temp >= CONFIG.desert_temp
2038                        && chunk.cliff_height > 40.0
2039                        && chunk.rockiness > 1.2
2040                        && suitable_for_town()
2041                },
2042                SiteKind::GliderCourse => {
2043                    chunk.alt > 1400.0
2044                },
2045                SiteKind::SavannahTown => {
2046                    matches!(chunk.get_biome(), BiomeKind::Savannah)
2047                        && !chunk.near_cliffs()
2048                        && !chunk.river.near_water()
2049                        && suitable_for_town()
2050                },
2051                SiteKind::CoastalTown => {
2052                    (2.0..3.5).contains(&(chunk.water_alt - CONFIG.sea_level))
2053                        && suitable_for_town()
2054                },
2055                SiteKind::PirateHideout => {
2056                    (0.5..3.5).contains(&(chunk.water_alt - CONFIG.sea_level))
2057                },
2058                SiteKind::Sahagin => {
2059                    matches!(chunk.get_biome(), BiomeKind::Ocean)
2060                    && (40.0..45.0).contains(&(CONFIG.sea_level - chunk.alt))
2061                },
2062                SiteKind::JungleRuin => {
2063                    matches!(chunk.get_biome(), BiomeKind::Jungle)
2064                },
2065                SiteKind::RockCircle => !chunk.near_cliffs() && !chunk.river.near_water(),
2066                SiteKind::TrollCave => {
2067                    !chunk.near_cliffs()
2068                        && on_flat_terrain()
2069                        && !chunk.river.near_water()
2070                        && chunk.temp < 0.6
2071                },
2072                SiteKind::Camp => {
2073                    !chunk.near_cliffs() && on_flat_terrain() && !chunk.river.near_water()
2074                },
2075                SiteKind::DesertCity => {
2076                    (0.9..1.0).contains(&chunk.temp) && !chunk.near_cliffs() && suitable_for_town()
2077                        && on_land()
2078                        && !chunk.river.near_water()
2079                },
2080                SiteKind::ChapelSite => {
2081                    matches!(chunk.get_biome(), BiomeKind::Ocean)
2082                        && CONFIG.sea_level < chunk.alt + 1.0
2083                },
2084                SiteKind::Terracotta => {
2085                    (0.9..1.0).contains(&chunk.temp)
2086                        && on_land()
2087                        && (chunk.water_alt - CONFIG.sea_level) > 50.0
2088                        && on_flat_terrain()
2089                        && !chunk.river.near_water()
2090                        && !chunk.near_cliffs()
2091                },
2092                SiteKind::Myrmidon => {
2093                    (0.9..1.0).contains(&chunk.temp)
2094                        && on_land()
2095                        && (chunk.water_alt - CONFIG.sea_level) > 50.0
2096                        && on_flat_terrain()
2097                        && !chunk.river.near_water()
2098                        && !chunk.near_cliffs()
2099                },
2100                SiteKind::Cultist => on_land() && chunk.temp < 0.5 && chunk.near_cliffs(),
2101                SiteKind::VampireCastle => on_land() && chunk.temp <= -0.8 && chunk.near_cliffs(),
2102                SiteKind::Castle => {
2103                    if chunk.tree_density > 0.4 || chunk.river.near_water() || chunk.near_cliffs() {
2104                        return false;
2105                    }
2106                    const HILL_RADIUS: i32 = 3 * TERRAIN_CHUNK_BLOCKS_LG as i32;
2107                    for x in (-HILL_RADIUS)..HILL_RADIUS {
2108                        for y in (-HILL_RADIUS)..HILL_RADIUS {
2109                            let check_loc = loc + Vec2::new(x, y);
2110                            if let Some(true) = sim
2111                                .get_alt_approx(check_loc)
2112                                .map(|surrounding_alt| surrounding_alt > chunk.alt + 1.0)
2113                            {
2114                                return false;
2115                            }
2116                            // Castles are really big, so to avoid parts of them ending up
2117                            // underwater or in other awkward positions
2118                            // we have to do this
2119                            if sim
2120                                .get(check_loc).is_none_or(|c| c.is_underwater() || c.near_cliffs())
2121                            {
2122                                return false;
2123                            }
2124                        }
2125                    }
2126                    true
2127                },
2128                SiteKind::Refactor | SiteKind::Settlement => suitable_for_town(),
2129                SiteKind::Bridge(_, _) => true,
2130            }
2131        })
2132    }
2133
2134    pub fn exclusion_radius(&self) -> i32 {
2135        // FIXME: Provide specific values for each individual SiteKind
2136        match self {
2137            SiteKind::Myrmidon => 7,
2138            _ => 8, // This is just an arbitrary value
2139        }
2140    }
2141
2142    pub fn exclusion_radius_clear(&self, sim: &WorldSim, loc: Vec2<i32>) -> bool {
2143        let radius = self.exclusion_radius();
2144        for x in (-radius)..radius {
2145            for y in (-radius)..radius {
2146                let check_loc = loc + Vec2::new(x, y);
2147                if sim.get(check_loc).is_some_and(|c| !c.sites.is_empty()) {
2148                    return false;
2149                }
2150            }
2151        }
2152        true
2153    }
2154}
2155
2156impl Site {
2157    pub fn is_dungeon(&self) -> bool {
2158        matches!(
2159            self.kind,
2160            SiteKind::Adlet
2161                | SiteKind::Gnarling
2162                | SiteKind::ChapelSite
2163                | SiteKind::Terracotta
2164                | SiteKind::Haniwa
2165                | SiteKind::Myrmidon
2166                | SiteKind::DwarvenMine
2167                | SiteKind::Cultist
2168                | SiteKind::Sahagin
2169                | SiteKind::VampireCastle
2170        )
2171    }
2172
2173    pub fn is_settlement(&self) -> bool {
2174        matches!(
2175            self.kind,
2176            SiteKind::Settlement
2177                | SiteKind::Refactor
2178                | SiteKind::CliffTown
2179                | SiteKind::DesertCity
2180                | SiteKind::SavannahTown
2181                | SiteKind::CoastalTown
2182        )
2183    }
2184
2185    pub fn is_castle(&self) -> bool { matches!(self.kind, SiteKind::Castle) }
2186
2187    pub fn is_bridge(&self) -> bool { matches!(self.kind, SiteKind::Bridge(_, _)) }
2188}
2189
2190#[derive(PartialEq, Eq, Debug, Clone)]
2191pub struct PointOfInterest {
2192    pub name: String,
2193    pub kind: PoiKind,
2194    pub loc: Vec2<i32>,
2195}
2196
2197#[derive(PartialEq, Eq, Debug, Clone)]
2198pub enum PoiKind {
2199    /// Peak stores the altitude
2200    Peak(u32),
2201    /// Lake stores a metric relating to size
2202    Biome(u32),
2203}
2204
2205#[cfg(test)]
2206mod tests {
2207    use super::*;
2208
2209    #[test]
2210    fn empty_proximity_requirements() {
2211        let world_dims = Aabr {
2212            min: Vec2 { x: 0, y: 0 },
2213            max: Vec2 {
2214                x: 200_i32,
2215                y: 200_i32,
2216            },
2217        };
2218        let reqs = ProximityRequirementsBuilder::new().finalize(&world_dims);
2219        assert!(reqs.satisfied_by(Vec2 { x: 0, y: 0 }));
2220    }
2221
2222    #[test]
2223    fn avoid_proximity_requirements() {
2224        let world_dims = Aabr {
2225            min: Vec2 {
2226                x: -200_i32,
2227                y: -200_i32,
2228            },
2229            max: Vec2 {
2230                x: 200_i32,
2231                y: 200_i32,
2232            },
2233        };
2234        let reqs = ProximityRequirementsBuilder::new()
2235            .avoid_all_of(vec![Vec2 { x: 0, y: 0 }].into_iter(), 10)
2236            .finalize(&world_dims);
2237        assert!(reqs.satisfied_by(Vec2 { x: 8, y: -8 }));
2238        assert!(!reqs.satisfied_by(Vec2 { x: -1, y: 1 }));
2239    }
2240
2241    #[test]
2242    fn near_proximity_requirements() {
2243        let world_dims = Aabr {
2244            min: Vec2 {
2245                x: -200_i32,
2246                y: -200_i32,
2247            },
2248            max: Vec2 {
2249                x: 200_i32,
2250                y: 200_i32,
2251            },
2252        };
2253        let reqs = ProximityRequirementsBuilder::new()
2254            .close_to_one_of(vec![Vec2 { x: 0, y: 0 }].into_iter(), 10)
2255            .finalize(&world_dims);
2256        assert!(reqs.satisfied_by(Vec2 { x: 1, y: -1 }));
2257        assert!(!reqs.satisfied_by(Vec2 { x: -8, y: 8 }));
2258    }
2259
2260    #[test]
2261    fn complex_proximity_requirements() {
2262        let a_site = Vec2 { x: 572, y: 724 };
2263        let world_dims = Aabr {
2264            min: Vec2 { x: 0, y: 0 },
2265            max: Vec2 {
2266                x: 1000_i32,
2267                y: 1000_i32,
2268            },
2269        };
2270        let reqs = ProximityRequirementsBuilder::new()
2271            .close_to_one_of(vec![a_site].into_iter(), 60)
2272            .avoid_all_of(vec![a_site].into_iter(), 40)
2273            .finalize(&world_dims);
2274        assert!(reqs.satisfied_by(Vec2 { x: 572, y: 774 }));
2275        assert!(!reqs.satisfied_by(a_site));
2276    }
2277
2278    #[test]
2279    fn location_hint() {
2280        let reqs = ProximityRequirementsBuilder::new().close_to_one_of(
2281            vec![Vec2 { x: 1, y: 0 }, Vec2 { x: 13, y: 12 }].into_iter(),
2282            10,
2283        );
2284        let expected = Aabr {
2285            min: Vec2 { x: 0, y: 0 },
2286            max: Vec2 { x: 23, y: 22 },
2287        };
2288        let map_dims = Aabr {
2289            min: Vec2 { x: 0, y: 0 },
2290            max: Vec2 { x: 200, y: 300 },
2291        };
2292        assert_eq!(expected, reqs.location_hint(&map_dims));
2293    }
2294}