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_farms(&mut ctx);
191 this.place_town(&mut ctx);
192 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 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 {
221 self.land.set(tile, hazard);
222 }
223 })
224 }
225
226 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 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 }
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 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) .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 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 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 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 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 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 {
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 { .. }) => {
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, 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 block
734 } else {
735 break;
736 };
737
738 if let (0, Some(sprite)) = (z, surface_sprite) {
739 let _ = vol.set(
740 pos,
741 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 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_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 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 for structure in &self.structures {
812 let bounds = structure.bounds_2d();
813
814 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 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 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 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 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 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 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 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 if permitted(Good::Potions) {
1070 stockmap
1071 .entry(Good::Potions)
1072 .and_modify(|e| *e = e.powf(0.25));
1073 }
1074 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 stockmap
1083 .iter_mut()
1084 .filter(|(good, _amount)| **good != Good::Coin)
1085 .for_each(|(_good, amount)| *amount *= 0.1);
1086 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 .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 .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#[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 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 Astar::new(250, origin, BuildHasherDefault::<FxHasher64>::default())
1376 .poll(250, heuristic, neighbors, satisfied)
1377 .into_path()
1378 .map(|(p, _c)| p)
1379 }
1380
1381 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 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}