veloren_world/site/settlement/
mod.rs

1pub mod building;
2mod town;
3
4use self::{
5    building::{Building, House, Keep},
6    town::{District, Town},
7};
8use super::SpawnRules;
9use crate::{
10    IndexRef,
11    column::ColumnSample,
12    sim::WorldSim,
13    site::namegen::NameGen,
14    util::{RandomField, Sampler, StructureGen2d},
15};
16use common::{
17    astar::Astar,
18    calendar::Calendar,
19    comp::{
20        self, Item, agent, bird_medium,
21        inventory::{
22            loadout_builder::LoadoutBuilder, slot::ArmorSlot, trade_pricing::TradePricing,
23        },
24        quadruped_small,
25    },
26    generation::{ChunkSupplement, EntityInfo},
27    path::Path,
28    resources::TimeOfDay,
29    spiral::Spiral2d,
30    store::{Id, Store},
31    terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize},
32    trade::{Good, SiteInformation},
33    vol::{ReadVol, RectSizedVol, RectVolSize, WriteVol},
34};
35
36use fxhash::FxHasher64;
37use hashbrown::{HashMap, HashSet};
38use rand::prelude::*;
39use serde::Deserialize;
40use std::{collections::VecDeque, f32, hash::BuildHasherDefault};
41use vek::*;
42
43#[derive(Deserialize)]
44pub struct Colors {
45    pub building: building::Colors,
46
47    pub plot_town_path: (u8, u8, u8),
48
49    pub plot_field_dirt: (u8, u8, u8),
50    pub plot_field_mound: (u8, u8, u8),
51
52    pub wall_low: (u8, u8, u8),
53    pub wall_high: (u8, u8, u8),
54
55    pub tower_color: (u8, u8, u8),
56
57    pub plot_dirt: (u8, u8, u8),
58    pub plot_grass: (u8, u8, u8),
59    pub plot_water: (u8, u8, u8),
60    pub plot_town: (u8, u8, u8),
61}
62
63pub fn gradient(line: [Vec2<f32>; 2]) -> f32 {
64    let r = (line[0].y - line[1].y) / (line[0].x - line[1].x);
65    if r.is_nan() { 100000.0 } else { r }
66}
67
68pub fn intersect(a: [Vec2<f32>; 2], b: [Vec2<f32>; 2]) -> Option<Vec2<f32>> {
69    let ma = gradient(a);
70    let mb = gradient(b);
71
72    let ca = a[0].y - ma * a[0].x;
73    let cb = b[0].y - mb * b[0].x;
74
75    if (ma - mb).abs() < 0.0001 || (ca - cb).abs() < 0.0001 {
76        None
77    } else {
78        let x = (cb - ca) / (ma - mb);
79        let y = ma * x + ca;
80
81        Some(Vec2::new(x, y))
82    }
83}
84
85pub fn center_of(p: [Vec2<f32>; 3]) -> Vec2<f32> {
86    let ma = -1.0 / gradient([p[0], p[1]]);
87    let mb = -1.0 / gradient([p[1], p[2]]);
88
89    let pa = (p[0] + p[1]) * 0.5;
90    let pb = (p[1] + p[2]) * 0.5;
91
92    let ca = pa.y - ma * pa.x;
93    let cb = pb.y - mb * pb.x;
94
95    let x = (cb - ca) / (ma - mb);
96    let y = ma * x + ca;
97
98    Vec2::new(x, y)
99}
100
101impl WorldSim {
102    fn can_host_settlement(&self, pos: Vec2<i32>) -> bool {
103        self.get(pos)
104            .map(|chunk| !chunk.river.is_river() && !chunk.river.is_lake())
105            .unwrap_or(false)
106            && self
107                .get_gradient_approx(pos)
108                .map(|grad| grad < 0.75)
109                .unwrap_or(false)
110    }
111}
112
113const AREA_SIZE: u32 = 32;
114
115fn to_tile(e: i32) -> i32 { ((e as f32).div_euclid(AREA_SIZE as f32)).floor() as i32 }
116
117pub enum StructureKind {
118    House(Building<House>),
119    Keep(Building<Keep>),
120}
121
122pub struct Structure {
123    kind: StructureKind,
124}
125
126impl Structure {
127    pub fn bounds_2d(&self) -> Aabr<i32> {
128        match &self.kind {
129            StructureKind::House(house) => house.bounds_2d(),
130            StructureKind::Keep(keep) => keep.bounds_2d(),
131        }
132    }
133
134    pub fn bounds(&self) -> Aabb<i32> {
135        match &self.kind {
136            StructureKind::House(house) => house.bounds(),
137            StructureKind::Keep(keep) => keep.bounds(),
138        }
139    }
140
141    pub fn sample(&self, index: IndexRef, rpos: Vec3<i32>) -> Option<Block> {
142        match &self.kind {
143            StructureKind::House(house) => house.sample(index, rpos),
144            StructureKind::Keep(keep) => keep.sample(index, rpos),
145        }
146    }
147}
148
149pub struct Settlement {
150    name: String,
151    seed: u32,
152    origin: Vec2<i32>,
153    land: Land,
154    farms: Store<Farm>,
155    structures: Vec<Structure>,
156    town: Option<Town>,
157    noise: RandomField,
158}
159
160pub struct Farm {
161    #[expect(dead_code)]
162    base_tile: Vec2<i32>,
163}
164
165pub struct GenCtx<'a, R: Rng> {
166    sim: Option<&'a WorldSim>,
167    rng: &'a mut R,
168}
169
170impl Settlement {
171    pub fn generate(wpos: Vec2<i32>, sim: Option<&WorldSim>, rng: &mut impl Rng) -> Self {
172        let mut ctx = GenCtx { sim, rng };
173        let mut this = Self {
174            name: NameGen::location(ctx.rng).generate(),
175            seed: ctx.rng.gen(),
176            origin: wpos,
177            land: Land::new(ctx.rng),
178            farms: Store::default(),
179            structures: Vec::new(),
180            town: None,
181            noise: RandomField::new(ctx.rng.gen()),
182        };
183
184        if let Some(sim) = ctx.sim {
185            this.designate_from_world(sim, ctx.rng);
186        }
187
188        //this.place_river(rng);
189
190        this.place_farms(&mut ctx);
191        this.place_town(&mut ctx);
192        //this.place_paths(ctx.rng);
193        this.place_buildings(&mut ctx);
194
195        this
196    }
197
198    pub fn name(&self) -> &str { &self.name }
199
200    pub fn get_origin(&self) -> Vec2<i32> { self.origin }
201
202    /// Designate hazardous terrain based on world data
203    pub fn designate_from_world(&mut self, sim: &WorldSim, rng: &mut impl Rng) {
204        let tile_radius = self.radius() as i32 / AREA_SIZE as i32;
205        let hazard = self.land.hazard;
206        Spiral2d::new()
207            .take_while(|tile| tile.map(|e| e.abs()).reduce_max() < tile_radius)
208            .for_each(|tile| {
209                let wpos = self.origin + tile * AREA_SIZE as i32;
210
211                if (0..4)
212                    .flat_map(|x| (0..4).map(move |y| Vec2::new(x, y)))
213                    .any(|offs| {
214                        let wpos = wpos + offs * AREA_SIZE as i32 / 2;
215                        let cpos = wpos.map(|e| e.div_euclid(TerrainChunkSize::RECT_SIZE.x as i32));
216                        !sim.can_host_settlement(cpos)
217                    })
218                    || rng.gen_range(0..16) == 0
219                // Randomly consider some tiles inaccessible
220                {
221                    self.land.set(tile, hazard);
222                }
223            })
224    }
225
226    /// Testing only
227    pub fn place_river(&mut self, rng: &mut impl Rng) {
228        let river_dir = Vec2::new(rng.gen::<f32>() - 0.5, rng.gen::<f32>() - 0.5).normalized();
229        let radius = 500.0 + rng.gen::<f32>().powi(2) * 1000.0;
230        let river = self.land.new_plot(Plot::Water);
231        let river_offs = Vec2::new(rng.gen_range(-3..4), rng.gen_range(-3..4));
232
233        for x in (0..100).map(|e| e as f32 / 100.0) {
234            let theta0 = x * f32::consts::PI * 2.0;
235            let theta1 = (x + 0.01) * f32::consts::PI * 2.0;
236
237            let pos0 = (river_dir * radius + Vec2::new(theta0.sin(), theta0.cos()) * radius)
238                .map(|e| e.floor() as i32)
239                .map(to_tile)
240                + river_offs;
241            let pos1 = (river_dir * radius + Vec2::new(theta1.sin(), theta1.cos()) * radius)
242                .map(|e| e.floor() as i32)
243                .map(to_tile)
244                + river_offs;
245
246            if pos0.magnitude_squared() > 15i32.pow(2) {
247                continue;
248            }
249
250            if let Some(path) = self.land.find_path(pos0, pos1, |_, _| 1.0) {
251                for pos in path.iter().copied() {
252                    self.land.set(pos, river);
253                }
254            }
255        }
256    }
257
258    pub fn place_paths(&mut self, rng: &mut impl Rng) {
259        const PATH_COUNT: usize = 6;
260
261        let mut dir = Vec2::zero();
262        for _ in 0..PATH_COUNT {
263            dir = (Vec2::new(rng.gen::<f32>() - 0.5, rng.gen::<f32>() - 0.5) * 2.0 - dir)
264                .try_normalized()
265                .unwrap_or_else(Vec2::zero);
266            let origin = dir.map(|e| (e * 100.0) as i32);
267            let origin = self
268                .land
269                .find_tile_near(origin, |plot| matches!(plot, Some(&Plot::Field { .. })))
270                .unwrap();
271
272            if let Some(path) = self.town.as_ref().and_then(|town| {
273                self.land
274                    .find_path(origin, town.base_tile, |from, to| match (from, to) {
275                        (_, Some(b)) if self.land.plot(b.plot) == &Plot::Dirt => 0.0,
276                        (_, Some(b)) if self.land.plot(b.plot) == &Plot::Water => 20.0,
277                        (_, Some(b)) if self.land.plot(b.plot) == &Plot::Hazard => 50.0,
278                        (Some(a), Some(b)) if a.contains(WayKind::Wall) => {
279                            if b.contains(WayKind::Wall) {
280                                1000.0
281                            } else {
282                                10.0
283                            }
284                        },
285                        (Some(_), Some(_)) => 1.0,
286                        _ => 1000.0,
287                    })
288            }) {
289                let path = path.iter().copied().collect::<Vec<_>>();
290                self.land.write_path(&path, WayKind::Path, |_| true, false);
291            }
292        }
293    }
294
295    pub fn place_town(&mut self, ctx: &mut GenCtx<impl Rng>) {
296        const PLOT_COUNT: usize = 3;
297
298        let mut origin = Vec2::new(ctx.rng.gen_range(-2..3), ctx.rng.gen_range(-2..3));
299
300        for i in 0..PLOT_COUNT {
301            if let Some(base_tile) = self.land.find_tile_near(origin, |plot| {
302                matches!(plot, Some(Plot::Field { .. }) | Some(Plot::Dirt))
303            }) {
304                // self.land
305                //     .plot_at_mut(base_tile)
306                //     .map(|plot| *plot = Plot::Town { district: None });
307
308                if i == 0 {
309                    let town = Town::generate(self.origin, base_tile, ctx);
310
311                    for (id, district) in town.districts().iter() {
312                        let district_plot =
313                            self.land.plots.insert(Plot::Town { district: Some(id) });
314
315                        for x in district.aabr.min.x..district.aabr.max.x {
316                            for y in district.aabr.min.y..district.aabr.max.y {
317                                if !matches!(self.land.plot_at(Vec2::new(x, y)), Some(Plot::Hazard))
318                                {
319                                    self.land.set(Vec2::new(x, y), district_plot);
320                                }
321                            }
322                        }
323                    }
324
325                    self.town = Some(town);
326                    origin = base_tile;
327                }
328            }
329        }
330
331        // Boundary wall
332        /*
333        let spokes = CARDINALS
334            .iter()
335            .filter_map(|dir| {
336                self.land.find_tile_dir(origin, *dir, |plot| match plot {
337                    Some(Plot::Water) => false,
338                    Some(Plot::Town) => false,
339                    _ => true,
340                })
341            })
342            .collect::<Vec<_>>();
343        let mut wall_path = Vec::new();
344        for i in 0..spokes.len() {
345            self.land
346                .find_path(spokes[i], spokes[(i + 1) % spokes.len()], |_, to| match to
347                    .map(|to| self.land.plot(to.plot))
348                {
349                    Some(Plot::Hazard) => 200.0,
350                    Some(Plot::Water) => 40.0,
351                    Some(Plot::Town) => 10000.0,
352                    _ => 10.0,
353                })
354                .map(|path| wall_path.extend(path.iter().copied()));
355        }
356        let grass = self.land.new_plot(Plot::Grass);
357        let buildable = |plot: &Plot| match plot {
358            Plot::Water => false,
359            _ => true,
360        };
361        for pos in wall_path.iter() {
362            if self.land.tile_at(*pos).is_none() {
363                self.land.set(*pos, grass);
364            }
365            if self.land.plot_at(*pos).copied().filter(buildable).is_some() {
366                self.land
367                    .tile_at_mut(*pos)
368                    .map(|tile| tile.tower = Some(Tower::Wall));
369            }
370        }
371        if wall_path.len() > 0 {
372            wall_path.push(wall_path[0]);
373        }
374        self.land
375            .write_path(&wall_path, WayKind::Wall, buildable, true);
376        */
377    }
378
379    pub fn place_buildings(&mut self, ctx: &mut GenCtx<impl Rng>) {
380        let town_center = if let Some(town) = self.town.as_ref() {
381            town.base_tile
382        } else {
383            return;
384        };
385
386        for tile in Spiral2d::new()
387            .map(|offs| town_center + offs)
388            .take(16usize.pow(2))
389        {
390            // This is a stupid way to decide how to place buildings
391            for i in 0..ctx.rng.gen_range(2..5) {
392                for _ in 0..25 {
393                    let house_pos = tile.map(|e| e * AREA_SIZE as i32 + AREA_SIZE as i32 / 2)
394                        + Vec2::<i32>::zero().map(|_| {
395                            ctx.rng
396                                .gen_range(-(AREA_SIZE as i32) / 4..AREA_SIZE as i32 / 4)
397                        });
398
399                    let tile_pos = house_pos.map(|e| e.div_euclid(AREA_SIZE as i32));
400                    if self
401                        .land
402                        .tile_at(tile_pos)
403                        .map(|t| t.contains(WayKind::Path))
404                        .unwrap_or(true)
405                        || ctx
406                            .sim
407                            .and_then(|sim| sim.get_nearest_path(self.origin + house_pos))
408                            .map(|(dist, _, _, _)| dist < 28.0)
409                            .unwrap_or(false)
410                    {
411                        continue;
412                    }
413
414                    let alt = if let Some(Plot::Town { district }) = self.land.plot_at(tile_pos) {
415                        district
416                            .and_then(|d| self.town.as_ref().map(|t| t.districts().get(d)))
417                            .map(|d| d.alt)
418                            .filter(|_| false) // Temporary
419                            .unwrap_or_else(|| {
420                                ctx.sim
421                                    .and_then(|sim| sim.get_alt_approx(self.origin + house_pos))
422                                    .unwrap_or(0.0)
423                                    .ceil() as i32
424                            })
425                    } else {
426                        continue;
427                    };
428
429                    let structure = Structure {
430                        kind: if tile == town_center && i == 0 {
431                            StructureKind::Keep(Building::<Keep>::generate(
432                                ctx.rng,
433                                Vec3::new(house_pos.x, house_pos.y, alt),
434                                None,
435                            ))
436                        } else {
437                            StructureKind::House(Building::<House>::generate(
438                                ctx.rng,
439                                Vec3::new(house_pos.x, house_pos.y, alt),
440                                ctx.sim.and_then(|sim| sim.calendar.as_ref()),
441                            ))
442                        },
443                    };
444
445                    let bounds = structure.bounds_2d();
446
447                    // Check for collision with other structures
448                    if self
449                        .structures
450                        .iter()
451                        .any(|s| s.bounds_2d().collides_with_aabr(bounds))
452                    {
453                        continue;
454                    }
455
456                    self.structures.push(structure);
457                    break;
458                }
459            }
460        }
461    }
462
463    pub fn place_farms(&mut self, ctx: &mut GenCtx<impl Rng>) {
464        const FARM_COUNT: usize = 6;
465        const FIELDS_PER_FARM: usize = 5;
466
467        for _ in 0..FARM_COUNT {
468            if let Some(base_tile) = self
469                .land
470                .find_tile_near(Vec2::zero(), |plot| plot.is_none())
471            {
472                // Farm
473                //let farmhouse = self.land.new_plot(Plot::Dirt);
474                //self.land.set(base_tile, farmhouse);
475
476                // Farmhouses
477                // for _ in 0..ctx.rng.gen_range(1..3) {
478                //     let house_pos = base_tile.map(|e| e * AREA_SIZE as i32 + AREA_SIZE as i32
479                // / 2)         + Vec2::new(ctx.rng.gen_range(-16..16),
480                // ctx.rng.gen_range(-16..16));
481
482                //     self.structures.push(Structure {
483                //         kind: StructureKind::House(HouseBuilding::generate(ctx.rng,
484                // Vec3::new(             house_pos.x,
485                //             house_pos.y,
486                //             ctx.sim
487                //                 .and_then(|sim| sim.get_alt_approx(self.origin + house_pos))
488                //                 .unwrap_or(0.0)
489                //                 .ceil() as i32,
490                //         ))),
491                //     });
492                // }
493
494                // Fields
495                let farmland = self.farms.insert(Farm { base_tile });
496                for _ in 0..FIELDS_PER_FARM {
497                    self.place_field(farmland, base_tile, ctx.rng);
498                }
499            }
500        }
501    }
502
503    pub fn place_field(
504        &mut self,
505        farm: Id<Farm>,
506        origin: Vec2<i32>,
507        rng: &mut impl Rng,
508    ) -> Option<Id<Plot>> {
509        const MAX_FIELD_SIZE: usize = 24;
510
511        if let Some(center) = self.land.find_tile_near(origin, |plot| plot.is_none()) {
512            let field = self.land.new_plot(Plot::Field {
513                farm,
514                seed: rng.gen(),
515                crop: match rng.gen_range(0..8) {
516                    0 => Crop::Corn,
517                    1 => Crop::Wheat,
518                    2 => Crop::Cabbage,
519                    3 => Crop::Pumpkin,
520                    4 => Crop::Flax,
521                    5 => Crop::Carrot,
522                    6 => Crop::Tomato,
523                    7 => Crop::Radish,
524                    _ => Crop::Sunflower,
525                },
526            });
527            let tiles =
528                self.land
529                    .grow_from(center, rng.gen_range(5..MAX_FIELD_SIZE), rng, |plot| {
530                        plot.is_none()
531                    });
532            for pos in tiles.into_iter() {
533                self.land.set(pos, field);
534            }
535            Some(field)
536        } else {
537            None
538        }
539    }
540
541    pub fn radius(&self) -> f32 { 400.0 }
542
543    pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
544        SpawnRules {
545            trees: self
546                .land
547                .get_at_block(wpos - self.origin)
548                .plot
549                .map(|p| matches!(p, Plot::Hazard))
550                .unwrap_or(true),
551            ..SpawnRules::default()
552        }
553    }
554
555    pub fn apply_to<'a>(
556        &'a self,
557        index: IndexRef,
558        wpos2d: Vec2<i32>,
559        mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
560        vol: &mut (impl RectSizedVol<Vox = Block> + ReadVol + WriteVol),
561    ) {
562        let colors = &index.colors.site.settlement;
563
564        for y in 0..vol.size_xy().y as i32 {
565            for x in 0..vol.size_xy().x as i32 {
566                let offs = Vec2::new(x, y);
567
568                let wpos2d = wpos2d + offs;
569                let rpos = wpos2d - self.origin;
570
571                // Sample terrain
572                let col_sample = if let Some(col_sample) = get_column(offs) {
573                    col_sample
574                } else {
575                    continue;
576                };
577                let land_surface_z = col_sample.riverless_alt.floor() as i32;
578                let mut surface_z = land_surface_z;
579
580                // Sample settlement
581                let sample = self.land.get_at_block(rpos);
582
583                let noisy_color = move |col: Rgb<u8>, factor: u32| {
584                    let nz = self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, surface_z));
585                    col.map(|e| {
586                        (e as u32 + nz % (factor * 2))
587                            .saturating_sub(factor)
588                            .min(255) as u8
589                    })
590                };
591
592                // District alt
593                if let Some(Plot::Town { district }) = sample.plot {
594                    if let Some(d) = district
595                        .and_then(|d| self.town.as_ref().map(|t| t.districts().get(d)))
596                        .filter(|_| false)
597                    // Temporary
598                    {
599                        let other = self
600                            .land
601                            .plot_at(sample.second_closest)
602                            .and_then(|p| match p {
603                                Plot::Town { district } => *district,
604                                _ => None,
605                            })
606                            .and_then(|d| {
607                                self.town.as_ref().map(|t| t.districts().get(d).alt as f32)
608                            })
609                            .filter(|_| false)
610                            .unwrap_or(surface_z as f32);
611                        surface_z = Lerp::lerp(
612                            (other + d.alt as f32) / 2.0,
613                            d.alt as f32,
614                            (1.25 * sample.edge_dist / (d.alt as f32 - other).abs()).min(1.0),
615                        ) as i32;
616                    }
617                }
618
619                {
620                    let mut surface_sprite = None;
621
622                    let roll =
623                        |seed, n| self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, seed * 5)) % n;
624
625                    let color = match sample.plot {
626                        Some(Plot::Dirt) => Some(colors.plot_dirt.into()),
627                        Some(Plot::Grass) => Some(colors.plot_grass.into()),
628                        Some(Plot::Water) => Some(colors.plot_water.into()),
629                        //Some(Plot::Town { district }) => None,
630                        Some(Plot::Town { .. }) => {
631                            if let Some((_, path_nearest, _, _)) = col_sample.path {
632                                let path_dir = (path_nearest - wpos2d.map(|e| e as f32))
633                                    .rotated_z(f32::consts::PI / 2.0)
634                                    .normalized();
635                                let is_lamp = if path_dir.x.abs() > path_dir.y.abs() {
636                                    wpos2d.x as f32 % 15.0 / path_dir.dot(Vec2::unit_y()).abs()
637                                        <= 1.0
638                                } else {
639                                    (wpos2d.y as f32 + 10.0) % 15.0
640                                        / path_dir.dot(Vec2::unit_x()).abs()
641                                        <= 1.0
642                                };
643                                if (col_sample.path.map(|(dist, _, _, _)| dist > 6.0 && dist < 7.0).unwrap_or(false) && is_lamp) //roll(0, 50) == 0)
644                                    || (roll(0, 750) == 0 && col_sample.path.map(|(dist, _, _, _)| dist > 20.0).unwrap_or(true))
645                                {
646                                    surface_sprite = Some(SpriteKind::StreetLamp);
647                                }
648                            }
649
650                            Some(
651                                Rgb::from(colors.plot_town_path)
652                                    .map2(Rgb::iota(), |e: u8, _i: i32| {
653                                        e.saturating_add(0_u8).saturating_sub(8)
654                                    }),
655                            )
656                        },
657                        Some(Plot::Field { seed, crop, .. }) => {
658                            let furrow_dirs = [
659                                Vec2::new(1, 0),
660                                Vec2::new(0, 1),
661                                Vec2::new(1, 1),
662                                Vec2::new(-1, 1),
663                            ];
664                            let furrow_dir = furrow_dirs[*seed as usize % furrow_dirs.len()];
665                            let in_furrow = (wpos2d * furrow_dir).sum().rem_euclid(5) < 2;
666
667                            let dirt = Rgb::<u8>::from(colors.plot_field_dirt).map(|e| {
668                                e + (self.noise.get(Vec3::broadcast((seed % 4096 + 0) as i32)) % 32)
669                                    as u8
670                            });
671                            let mound = Rgb::<u8>::from(colors.plot_field_mound)
672                                .map(|e| e + roll(0, 8) as u8)
673                                .map(|e| {
674                                    e + (self.noise.get(Vec3::broadcast((seed % 4096 + 1) as i32))
675                                        % 32) as u8
676                                });
677
678                            if in_furrow {
679                                if roll(0, 5) == 0 {
680                                    surface_sprite = match crop {
681                                        Crop::Corn => Some(SpriteKind::Corn),
682                                        Crop::Wheat if roll(1, 2) == 0 => {
683                                            Some(SpriteKind::WheatYellow)
684                                        },
685                                        Crop::Wheat => Some(SpriteKind::WheatGreen),
686                                        Crop::Cabbage if roll(2, 2) == 0 => {
687                                            Some(SpriteKind::Cabbage)
688                                        },
689                                        Crop::Pumpkin if roll(3, 2) == 0 => {
690                                            Some(SpriteKind::Pumpkin)
691                                        },
692                                        Crop::Flax if roll(4, 2) == 0 => Some(SpriteKind::Flax),
693                                        Crop::Carrot if roll(5, 2) == 0 => Some(SpriteKind::Carrot),
694                                        Crop::Tomato if roll(6, 2) == 0 => Some(SpriteKind::Tomato),
695                                        Crop::Radish if roll(7, 2) == 0 => Some(SpriteKind::Radish),
696                                        Crop::Turnip if roll(8, 2) == 0 => Some(SpriteKind::Turnip),
697                                        Crop::Sunflower => Some(SpriteKind::Sunflower),
698                                        _ => surface_sprite,
699                                    }
700                                    .or_else(|| {
701                                        if roll(9, 400) == 0 {
702                                            Some(SpriteKind::Scarecrow)
703                                        } else {
704                                            None
705                                        }
706                                    });
707                                }
708                            } else if roll(0, 20) == 0 {
709                                surface_sprite = Some(SpriteKind::ShortGrass);
710                            } else if roll(1, 30) == 0 {
711                                surface_sprite = Some(SpriteKind::MediumGrass);
712                            }
713
714                            Some(if in_furrow { dirt } else { mound })
715                        },
716                        _ => None,
717                    };
718
719                    if let Some(color) = color {
720                        let is_path = col_sample
721                            .path
722                            .map(|(dist, _, path, _)| dist < path.width)
723                            .unwrap_or(false);
724
725                        if col_sample.water_dist.map(|dist| dist > 2.0).unwrap_or(true) && !is_path
726                        {
727                            let diff = (surface_z - land_surface_z).abs();
728
729                            for z in -8 - diff..8 + diff {
730                                let pos = Vec3::new(offs.x, offs.y, surface_z + z);
731                                let block = if let Ok(&block) = vol.get(pos) {
732                                    // TODO: Figure out whether extra filters are needed.
733                                    block
734                                } else {
735                                    break;
736                                };
737
738                                if let (0, Some(sprite)) = (z, surface_sprite) {
739                                    let _ = vol.set(
740                                        pos,
741                                        // TODO: Make more principled.
742                                        if block.is_fluid() {
743                                            block.with_sprite(sprite)
744                                        } else {
745                                            Block::air(sprite)
746                                        },
747                                    );
748                                } else if z >= 0 {
749                                    if [
750                                        BlockKind::Air,
751                                        BlockKind::Grass,
752                                        BlockKind::Earth,
753                                        BlockKind::Sand,
754                                        BlockKind::Snow,
755                                        BlockKind::Rock,
756                                    ]
757                                    .contains(&block.kind())
758                                    {
759                                        let _ = vol.set(pos, Block::air(SpriteKind::Empty));
760                                    }
761                                } else {
762                                    let _ = vol.set(
763                                        pos,
764                                        Block::new(BlockKind::Earth, noisy_color(color, 4)),
765                                    );
766                                }
767                            }
768                        }
769                    }
770                }
771
772                // Walls
773                if let Some((WayKind::Wall, dist, _)) = sample.way {
774                    let color = Lerp::lerp(
775                        Rgb::<u8>::from(colors.wall_low).map(i32::from),
776                        Rgb::<u8>::from(colors.wall_high).map(i32::from),
777                        (RandomField::new(0).get(wpos2d.into()) % 256) as f32 / 256.0,
778                    )
779                    .map(|e| (e % 256) as u8);
780
781                    let z_offset = if let Some(water_dist) = col_sample.water_dist {
782                        // Water gate
783                        ((water_dist.max(0.0) * 0.45).min(f32::consts::PI).cos() + 1.0) * 4.0
784                    } else {
785                        0.0
786                    } as i32;
787
788                    for z in z_offset..12 {
789                        if dist / WayKind::Wall.width() < ((1.0 - z as f32 / 12.0) * 2.0).min(1.0) {
790                            let _ = vol.set(
791                                Vec3::new(offs.x, offs.y, surface_z + z),
792                                Block::new(BlockKind::Wood, color),
793                            );
794                        }
795                    }
796                }
797
798                // Towers
799                if let Some((Tower::Wall, _pos)) = sample.tower {
800                    for z in -2..16 {
801                        let _ = vol.set(
802                            Vec3::new(offs.x, offs.y, surface_z + z),
803                            Block::new(BlockKind::Rock, colors.tower_color.into()),
804                        );
805                    }
806                }
807            }
808        }
809
810        // Apply structures
811        for structure in &self.structures {
812            let bounds = structure.bounds_2d();
813
814            // Skip this structure if it's not near this chunk
815            if !bounds.collides_with_aabr(Aabr {
816                min: wpos2d - self.origin,
817                max: wpos2d - self.origin + vol.size_xy().map(|e| e as i32 + 1),
818            }) {
819                continue;
820            }
821
822            let bounds = structure.bounds();
823
824            for x in bounds.min.x..bounds.max.x + 1 {
825                for y in bounds.min.y..bounds.max.y + 1 {
826                    let col = if let Some(col) = get_column(self.origin + Vec2::new(x, y) - wpos2d)
827                    {
828                        col
829                    } else {
830                        continue;
831                    };
832
833                    for z in bounds.min.z.min(col.alt.floor() as i32 - 1)..bounds.max.z + 1 {
834                        let rpos = Vec3::new(x, y, z);
835                        let wpos = Vec3::from(self.origin) + rpos;
836                        let coffs = wpos - Vec3::from(wpos2d);
837
838                        if let Some(block) = structure.sample(index, rpos) {
839                            let _ = vol.set(coffs, block);
840                        }
841                    }
842                }
843            }
844        }
845    }
846
847    pub fn apply_supplement<'a>(
848        &'a self,
849        // NOTE: Used only for dynamic elements like chests and entities!
850        dynamic_rng: &mut impl Rng,
851        wpos2d: Vec2<i32>,
852        mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
853        supplement: &mut ChunkSupplement,
854        economy: SiteInformation,
855        time: Option<&(TimeOfDay, Calendar)>,
856    ) {
857        // let economy: HashMap<Good, (f32, f32)> = SiteInformation::economy
858        //     .values
859        //     .iter()
860        //     .map(|(g, v)| {
861        //         (
862        //             g,
863        //             (
864        //                 v.unwrap_or(Economy::MINIMUM_PRICE),
865        //                 economy.stocks[g] + economy.surplus[g],
866        //             ),
867        //         )
868        //     })
869        //     .collect();
870        for y in 0..TerrainChunkSize::RECT_SIZE.y as i32 {
871            for x in 0..TerrainChunkSize::RECT_SIZE.x as i32 {
872                let offs = Vec2::new(x, y);
873
874                let wpos2d = wpos2d + offs;
875                let rpos = wpos2d - self.origin;
876
877                // Sample terrain
878                let col_sample = if let Some(col_sample) = get_column(offs) {
879                    col_sample
880                } else {
881                    continue;
882                };
883
884                let sample = self.land.get_at_block(rpos);
885
886                let entity_wpos = Vec3::new(wpos2d.x as f32, wpos2d.y as f32, col_sample.alt + 3.0);
887
888                if matches!(sample.plot, Some(Plot::Town { .. }))
889                    && RandomField::new(self.seed).chance(Vec3::from(wpos2d), 1.0 / (20.0 * 40.0))
890                {
891                    let is_dummy =
892                        RandomField::new(self.seed + 1).chance(Vec3::from(wpos2d), 1.0 / 15.0);
893                    let entity = if is_dummy {
894                        EntityInfo::at(entity_wpos)
895                            .with_agency(false)
896                            .with_asset_expect("common.entity.village.dummy", dynamic_rng, time)
897                    } else {
898                        match dynamic_rng.gen_range(0..=4) {
899                            0 => barnyard(entity_wpos, dynamic_rng),
900                            1 => bird(entity_wpos, dynamic_rng),
901                            _ => humanoid(entity_wpos, &economy, dynamic_rng, time),
902                        }
903                    };
904
905                    supplement.add_entity(entity);
906                }
907            }
908        }
909    }
910
911    pub fn get_color(&self, index: IndexRef, pos: Vec2<i32>) -> Option<Rgb<u8>> {
912        let colors = &index.colors.site.settlement;
913
914        let sample = self.land.get_at_block(pos);
915
916        match sample.plot {
917            Some(Plot::Dirt) => return Some(colors.plot_dirt.into()),
918            Some(Plot::Grass) => return Some(colors.plot_grass.into()),
919            Some(Plot::Water) => return Some(colors.plot_water.into()),
920            Some(Plot::Town { .. }) => {
921                return Some(
922                    Rgb::from(colors.plot_town).map2(Rgb::iota(), |e: u8, i: i32| {
923                        e.saturating_add(
924                            (self.noise.get(Vec3::new(pos.x, pos.y, i * 5)) % 16) as u8,
925                        )
926                        .saturating_sub(8)
927                    }),
928                );
929            },
930            Some(Plot::Field { seed, .. }) => {
931                let furrow_dirs = [
932                    Vec2::new(1, 0),
933                    Vec2::new(0, 1),
934                    Vec2::new(1, 1),
935                    Vec2::new(-1, 1),
936                ];
937                let furrow_dir = furrow_dirs[*seed as usize % furrow_dirs.len()];
938                let furrow = (pos * furrow_dir).sum().rem_euclid(6) < 3;
939                // NOTE: Very hard to understand how to make this dynamically configurable.  The
940                // base values can easily cause the others to go out of range, and there's some
941                // weird scaling going on.  For now, we just let these remain hardcoded.
942                //
943                // FIXME: Rewrite this so that validity is not so heavily dependent on the exact
944                // color values.
945                return Some(Rgb::new(
946                    if furrow {
947                        100
948                    } else {
949                        32 + seed.to_le_bytes()[0] % 64
950                    },
951                    64 + seed.to_le_bytes()[1] % 128,
952                    16 + seed.to_le_bytes()[2] % 32,
953                ));
954            },
955            _ => {},
956        }
957
958        None
959    }
960}
961
962fn barnyard(pos: Vec3<f32>, dynamic_rng: &mut impl Rng) -> EntityInfo {
963    //TODO: use Lottery instead of ad-hoc RNG system
964    let species = match dynamic_rng.gen_range(0..5) {
965        0 => quadruped_small::Species::Pig,
966        1 => quadruped_small::Species::Sheep,
967        2 => quadruped_small::Species::Goat,
968        3 => quadruped_small::Species::Dog,
969        _ => quadruped_small::Species::Cat,
970    };
971    EntityInfo::at(pos)
972        .with_body(comp::Body::QuadrupedSmall(
973            quadruped_small::Body::random_with(dynamic_rng, &species),
974        ))
975        .with_alignment(comp::Alignment::Tame)
976        .with_automatic_name(None)
977}
978
979fn bird(pos: Vec3<f32>, dynamic_rng: &mut impl Rng) -> EntityInfo {
980    //TODO: use Lottery instead of ad-hoc RNG system
981    let species = match dynamic_rng.gen_range(0..4) {
982        0 => bird_medium::Species::Duck,
983        1 => bird_medium::Species::Chicken,
984        2 => bird_medium::Species::Goose,
985        _ => bird_medium::Species::Peacock,
986    };
987    EntityInfo::at(pos)
988        .with_body(comp::Body::BirdMedium(bird_medium::Body::random_with(
989            dynamic_rng,
990            &species,
991        )))
992        .with_alignment(comp::Alignment::Tame)
993        .with_automatic_name(None)
994}
995
996fn humanoid(
997    pos: Vec3<f32>,
998    economy: &SiteInformation,
999    dynamic_rng: &mut impl Rng,
1000    time: Option<&(TimeOfDay, Calendar)>,
1001) -> EntityInfo {
1002    let entity = EntityInfo::at(pos);
1003    match dynamic_rng.gen_range(0..8) {
1004        0 | 1 => entity
1005            .with_agent_mark(agent::Mark::Guard)
1006            .with_asset_expect("common.entity.village.guard", dynamic_rng, time),
1007        2 => entity
1008            .with_agent_mark(agent::Mark::Merchant)
1009            .with_economy(economy)
1010            .with_lazy_loadout(merchant_loadout)
1011            .with_asset_expect("common.entity.village.merchant", dynamic_rng, time),
1012        _ => entity.with_asset_expect("common.entity.village.villager", dynamic_rng, time),
1013    }
1014}
1015
1016pub fn merchant_loadout(
1017    loadout_builder: LoadoutBuilder,
1018    economy: Option<&SiteInformation>,
1019    time: Option<&(TimeOfDay, Calendar)>,
1020) -> LoadoutBuilder {
1021    trader_loadout(
1022        loadout_builder.with_asset_expect(
1023            "common.loadout.village.merchant",
1024            &mut thread_rng(),
1025            time,
1026        ),
1027        economy,
1028        |_| true,
1029    )
1030}
1031
1032pub fn trader_loadout(
1033    loadout_builder: LoadoutBuilder,
1034    economy: Option<&SiteInformation>,
1035    mut permitted: impl FnMut(Good) -> bool,
1036) -> LoadoutBuilder {
1037    let rng = &mut thread_rng();
1038
1039    let mut backpack = Item::new_from_asset_expect("common.items.armor.misc.back.backpack");
1040    let mut bag1 = Item::new_from_asset_expect("common.items.armor.misc.bag.sturdy_red_backpack");
1041    let mut bag2 = Item::new_from_asset_expect("common.items.armor.misc.bag.sturdy_red_backpack");
1042    let mut bag3 = Item::new_from_asset_expect("common.items.armor.misc.bag.sturdy_red_backpack");
1043    let mut bag4 = Item::new_from_asset_expect("common.items.armor.misc.bag.sturdy_red_backpack");
1044    let slots = backpack.slots().len() + 4 * bag1.slots().len();
1045    let mut stockmap: HashMap<Good, f32> = economy
1046        .map(|e| {
1047            e.unconsumed_stock
1048                .clone()
1049                .into_iter()
1050                .filter(|(good, _)| permitted(*good))
1051                .collect()
1052        })
1053        .unwrap_or_default();
1054    // modify stock for better gameplay
1055
1056    // TODO: currently econsim spends all its food on population, resulting in none
1057    // for the players to buy; the `.max` is temporary to ensure that there's some
1058    // food for sale at every site, to be used until we have some solution like NPC
1059    // houses as a limit on econsim population growth
1060    if permitted(Good::Food) {
1061        stockmap
1062            .entry(Good::Food)
1063            .and_modify(|e| *e = e.max(10_000.0))
1064            .or_insert(10_000.0);
1065    }
1066    // Reduce amount of potions so merchants do not oversupply potions.
1067    // TODO: Maybe remove when merchants and their inventories are rtsim?
1068    // Note: Likely without effect now that potions are counted as food
1069    if permitted(Good::Potions) {
1070        stockmap
1071            .entry(Good::Potions)
1072            .and_modify(|e| *e = e.powf(0.25));
1073    }
1074    // It's safe to truncate here, because coins clamped to 3000 max
1075    // also we don't really want negative values here
1076    if permitted(Good::Coin) {
1077        stockmap
1078            .entry(Good::Coin)
1079            .and_modify(|e| *e = e.min(rng.gen_range(1000.0..3000.0)));
1080    }
1081    // assume roughly 10 merchants sharing a town's stock (other logic for coins)
1082    stockmap
1083        .iter_mut()
1084        .filter(|(good, _amount)| **good != Good::Coin)
1085        .for_each(|(_good, amount)| *amount *= 0.1);
1086    // Fill bags with stuff according to unclaimed stock
1087    let ability_map = &comp::tool::AbilityMap::load().read();
1088    let msm = &comp::item::MaterialStatManifest::load().read();
1089    let mut wares: Vec<Item> =
1090        TradePricing::random_items(&mut stockmap, slots as u32, true, true, 16)
1091            .iter()
1092            .filter_map(|(n, a)| {
1093                let i = Item::new_from_item_definition_id(n.as_ref(), ability_map, msm).ok();
1094                i.map(|mut i| {
1095                    i.set_amount(*a)
1096                        .map_err(|_| tracing::error!("merchant loadout amount failure"))
1097                        .ok();
1098                    i
1099                })
1100            })
1101            .collect();
1102    sort_wares(&mut wares);
1103    transfer(&mut wares, &mut backpack);
1104    transfer(&mut wares, &mut bag1);
1105    transfer(&mut wares, &mut bag2);
1106    transfer(&mut wares, &mut bag3);
1107    transfer(&mut wares, &mut bag4);
1108
1109    loadout_builder
1110        .back(Some(backpack))
1111        .bag(ArmorSlot::Bag1, Some(bag1))
1112        .bag(ArmorSlot::Bag2, Some(bag2))
1113        .bag(ArmorSlot::Bag3, Some(bag3))
1114        .bag(ArmorSlot::Bag4, Some(bag4))
1115}
1116
1117fn sort_wares(bag: &mut [Item]) {
1118    use common::comp::item::TagExampleInfo;
1119
1120    bag.sort_by(|a, b| {
1121        a.quality()
1122            .cmp(&b.quality())
1123        // sort by kind
1124        .then(
1125            Ord::cmp(
1126                a.tags().first().map_or("", |tag| tag.name()),
1127                b.tags().first().map_or("", |tag| tag.name()),
1128            )
1129        )
1130        // sort by name
1131        .then(#[expect(deprecated)] Ord::cmp(&a.name(), &b.name()))
1132    });
1133}
1134
1135fn transfer(wares: &mut Vec<Item>, bag: &mut Item) {
1136    let capacity = bag.slots().len();
1137    for (s, w) in bag
1138        .slots_mut()
1139        .iter_mut()
1140        .zip(wares.drain(0..wares.len().min(capacity)))
1141    {
1142        *s = Some(w);
1143    }
1144}
1145
1146#[derive(Copy, Clone, PartialEq, Eq)]
1147pub enum Crop {
1148    Corn,
1149    Wheat,
1150    Cabbage,
1151    Pumpkin,
1152    Flax,
1153    Carrot,
1154    Tomato,
1155    Radish,
1156    Turnip,
1157    Sunflower,
1158}
1159
1160// NOTE: No support for struct variants in make_case_elim yet, unfortunately, so
1161// we can't use it.
1162#[derive(Copy, Clone, PartialEq, Eq)]
1163pub enum Plot {
1164    Hazard,
1165    Dirt,
1166    Grass,
1167    Water,
1168    Town {
1169        district: Option<Id<District>>,
1170    },
1171    Field {
1172        farm: Id<Farm>,
1173        seed: u32,
1174        crop: Crop,
1175    },
1176}
1177
1178const CARDINALS: [Vec2<i32>; 4] = [
1179    Vec2::new(0, 1),
1180    Vec2::new(1, 0),
1181    Vec2::new(0, -1),
1182    Vec2::new(-1, 0),
1183];
1184
1185#[derive(Copy, Clone, PartialEq, Eq)]
1186pub enum WayKind {
1187    Path,
1188    Wall,
1189}
1190
1191impl WayKind {
1192    pub fn width(&self) -> f32 {
1193        match self {
1194            WayKind::Path => 4.0,
1195            WayKind::Wall => 3.0,
1196        }
1197    }
1198}
1199
1200#[derive(Copy, Clone, PartialEq, Eq)]
1201pub enum Tower {
1202    Wall,
1203}
1204
1205impl Tower {
1206    pub fn radius(&self) -> f32 {
1207        match self {
1208            Tower::Wall => 6.0,
1209        }
1210    }
1211}
1212
1213pub struct Tile {
1214    plot: Id<Plot>,
1215    ways: [Option<WayKind>; 4],
1216    tower: Option<Tower>,
1217}
1218
1219impl Tile {
1220    pub fn contains(&self, kind: WayKind) -> bool { self.ways.iter().any(|way| way == &Some(kind)) }
1221}
1222
1223#[derive(Default)]
1224pub struct Sample<'a> {
1225    plot: Option<&'a Plot>,
1226    way: Option<(&'a WayKind, f32, Vec2<f32>)>,
1227    tower: Option<(&'a Tower, Vec2<i32>)>,
1228    edge_dist: f32,
1229    second_closest: Vec2<i32>,
1230}
1231
1232pub struct Land {
1233    /// We use this hasher (FxHasher64) because
1234    /// (1) we need determinism across computers (ruling out AAHash);
1235    /// (2) we don't care about DDOS attacks (ruling out SipHash);
1236    /// (3) we have 8-byte keys (for which FxHash is fastest).
1237    tiles: HashMap<Vec2<i32>, Tile, BuildHasherDefault<FxHasher64>>,
1238    plots: Store<Plot>,
1239    sampler_warp: StructureGen2d,
1240    hazard: Id<Plot>,
1241}
1242
1243impl Land {
1244    pub fn new(rng: &mut impl Rng) -> Self {
1245        let mut plots = Store::default();
1246        let hazard = plots.insert(Plot::Hazard);
1247        Self {
1248            tiles: HashMap::default(),
1249            plots,
1250            sampler_warp: StructureGen2d::new(rng.gen(), AREA_SIZE, AREA_SIZE * 2 / 5),
1251            hazard,
1252        }
1253    }
1254
1255    pub fn get_at_block(&self, pos: Vec2<i32>) -> Sample {
1256        let mut sample = Sample::default();
1257
1258        let neighbors = self.sampler_warp.get(pos);
1259        let closest = neighbors
1260            .iter()
1261            .min_by_key(|(center, _)| center.distance_squared(pos))
1262            .unwrap()
1263            .0;
1264        let second_closest = neighbors
1265            .iter()
1266            .filter(|(center, _)| *center != closest)
1267            .min_by_key(|(center, _)| center.distance_squared(pos))
1268            .unwrap()
1269            .0;
1270        sample.second_closest = second_closest.map(to_tile);
1271        sample.edge_dist = (second_closest - pos).map(|e| e as f32).magnitude()
1272            - (closest - pos).map(|e| e as f32).magnitude();
1273
1274        let center_tile = self.tile_at(neighbors[4].0.map(to_tile));
1275
1276        if let Some(tower) = center_tile.and_then(|tile| tile.tower.as_ref()) {
1277            if (neighbors[4].0.distance_squared(pos) as f32) < tower.radius().powi(2) {
1278                sample.tower = Some((tower, neighbors[4].0));
1279            }
1280        }
1281
1282        for (i, _) in CARDINALS.iter().enumerate() {
1283            let map = [1, 5, 7, 3];
1284            let line = LineSegment2 {
1285                start: neighbors[4].0.map(|e| e as f32),
1286                end: neighbors[map[i]].0.map(|e| e as f32),
1287            };
1288            if let Some(way) = center_tile.and_then(|tile| tile.ways[i].as_ref()) {
1289                let proj_point = line.projected_point(pos.map(|e| e as f32));
1290                let dist = proj_point.distance(pos.map(|e| e as f32));
1291                if dist < way.width() {
1292                    sample.way = sample
1293                        .way
1294                        .filter(|(_, d, _)| *d < dist)
1295                        .or(Some((way, dist, proj_point)));
1296                }
1297            }
1298        }
1299
1300        sample.plot = self.plot_at(closest.map(to_tile));
1301
1302        sample
1303    }
1304
1305    pub fn tile_at(&self, pos: Vec2<i32>) -> Option<&Tile> { self.tiles.get(&pos) }
1306
1307    pub fn tile_at_mut(&mut self, pos: Vec2<i32>) -> Option<&mut Tile> { self.tiles.get_mut(&pos) }
1308
1309    pub fn plot(&self, id: Id<Plot>) -> &Plot { self.plots.get(id) }
1310
1311    pub fn plot_at(&self, pos: Vec2<i32>) -> Option<&Plot> {
1312        self.tiles.get(&pos).map(|tile| self.plots.get(tile.plot))
1313    }
1314
1315    pub fn plot_at_mut(&mut self, pos: Vec2<i32>) -> Option<&mut Plot> {
1316        self.tiles
1317            .get(&pos)
1318            .map(|tile| tile.plot)
1319            .map(move |plot| self.plots.get_mut(plot))
1320    }
1321
1322    pub fn set(&mut self, pos: Vec2<i32>, plot: Id<Plot>) {
1323        self.tiles.insert(pos, Tile {
1324            plot,
1325            ways: [None; 4],
1326            tower: None,
1327        });
1328    }
1329
1330    fn find_tile_near(
1331        &self,
1332        origin: Vec2<i32>,
1333        mut match_fn: impl FnMut(Option<&Plot>) -> bool,
1334    ) -> Option<Vec2<i32>> {
1335        Spiral2d::new()
1336            .map(|pos| origin + pos)
1337            .find(|pos| match_fn(self.plot_at(*pos)))
1338    }
1339
1340    #[expect(dead_code)]
1341    fn find_tile_dir(
1342        &self,
1343        origin: Vec2<i32>,
1344        dir: Vec2<i32>,
1345        mut match_fn: impl FnMut(Option<&Plot>) -> bool,
1346    ) -> Option<Vec2<i32>> {
1347        (0..)
1348            .map(|i| origin + dir * i)
1349            .find(|pos| match_fn(self.plot_at(*pos)))
1350    }
1351
1352    fn find_path(
1353        &self,
1354        origin: Vec2<i32>,
1355        dest: Vec2<i32>,
1356        path_cost_fn: impl Fn(Option<&Tile>, Option<&Tile>) -> f32,
1357    ) -> Option<Path<Vec2<i32>>> {
1358        let heuristic = |pos: &Vec2<i32>| (pos - dest).map(|e| e as f32).magnitude();
1359        let transition =
1360            |from: Vec2<i32>, to: Vec2<i32>| path_cost_fn(self.tile_at(from), self.tile_at(to));
1361        let neighbors = |pos: &Vec2<i32>| {
1362            let pos = *pos;
1363            let transition = &transition;
1364            CARDINALS.iter().map(move |dir| {
1365                let to = pos + *dir;
1366                (to, transition(pos, to))
1367            })
1368        };
1369        let satisfied = |pos: &Vec2<i32>| *pos == dest;
1370
1371        // We use this hasher (FxHasher64) because
1372        // (1) we don't care about DDOS attacks (ruling out SipHash);
1373        // (2) we don't care about determinism across computers (we could use AAHash);
1374        // (3) we have 8-byte keys (for which FxHash is fastest).
1375        Astar::new(250, origin, BuildHasherDefault::<FxHasher64>::default())
1376            .poll(250, heuristic, neighbors, satisfied)
1377            .into_path()
1378            .map(|(p, _c)| p)
1379    }
1380
1381    /// We use this hasher (FxHasher64) because
1382    /// (1) we don't care about DDOS attacks (ruling out SipHash);
1383    /// (2) we care about determinism across computers (ruling out AAHash);
1384    /// (3) we have 8-byte keys (for which FxHash is fastest).
1385    fn grow_from(
1386        &self,
1387        start: Vec2<i32>,
1388        max_size: usize,
1389        _rng: &mut impl Rng,
1390        mut match_fn: impl FnMut(Option<&Plot>) -> bool,
1391    ) -> HashSet<Vec2<i32>, BuildHasherDefault<FxHasher64>> {
1392        let mut open = VecDeque::new();
1393        open.push_back(start);
1394        // We use this hasher (FxHasher64) because
1395        // (1) we don't care about DDOS attacks (ruling out SipHash);
1396        // (2) we care about determinism across computers (ruling out AAHash);
1397        // (3) we have 8-byte keys (for which FxHash is fastest).
1398        let mut closed = HashSet::with_hasher(BuildHasherDefault::<FxHasher64>::default());
1399
1400        while open.len() + closed.len() < max_size {
1401            let next_pos = if let Some(next_pos) = open.pop_front() {
1402                closed.insert(next_pos);
1403                next_pos
1404            } else {
1405                break;
1406            };
1407
1408            let dirs = [
1409                Vec2::new(1, 0),
1410                Vec2::new(-1, 0),
1411                Vec2::new(0, 1),
1412                Vec2::new(0, -1),
1413            ];
1414
1415            for dir in dirs.iter() {
1416                let neighbor = next_pos + dir;
1417                if !closed.contains(&neighbor) && match_fn(self.plot_at(neighbor)) {
1418                    open.push_back(neighbor);
1419                }
1420            }
1421        }
1422
1423        closed.into_iter().chain(open).collect()
1424    }
1425
1426    fn write_path(
1427        &mut self,
1428        tiles: &[Vec2<i32>],
1429        kind: WayKind,
1430        mut permit_fn: impl FnMut(&Plot) -> bool,
1431        overwrite: bool,
1432    ) {
1433        for tiles in tiles.windows(2) {
1434            let dir = tiles[1] - tiles[0];
1435            let idx = if dir.y > 0 {
1436                1
1437            } else if dir.x > 0 {
1438                2
1439            } else if dir.y < 0 {
1440                3
1441            } else if dir.x < 0 {
1442                0
1443            } else {
1444                continue;
1445            };
1446            if self.tile_at(tiles[0]).is_none() {
1447                self.set(tiles[0], self.hazard);
1448            }
1449            let plots = &self.plots;
1450
1451            self.tiles
1452                .get_mut(&tiles[1])
1453                .filter(|tile| permit_fn(plots.get(tile.plot)))
1454                .map(|tile| {
1455                    if overwrite || tile.ways[(idx + 2) % 4].is_none() {
1456                        tile.ways[(idx + 2) % 4] = Some(kind);
1457                    }
1458                });
1459            self.tiles
1460                .get_mut(&tiles[0])
1461                .filter(|tile| permit_fn(plots.get(tile.plot)))
1462                .map(|tile| {
1463                    if overwrite || tile.ways[idx].is_none() {
1464                        tile.ways[idx] = Some(kind);
1465                    }
1466                });
1467        }
1468    }
1469
1470    pub fn new_plot(&mut self, plot: Plot) -> Id<Plot> { self.plots.insert(plot) }
1471}