diff --git a/common/src/lottery.rs b/common/src/lottery.rs index b110d4b5e3..44fd26307f 100644 --- a/common/src/lottery.rs +++ b/common/src/lottery.rs @@ -33,11 +33,16 @@ use crate::{ use rand::prelude::*; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use tracing::warn; +use std::{ + convert::AsRef, + marker::PhantomData, +}; #[derive(Clone, Debug, PartialEq, Deserialize)] -pub struct Lottery { - items: Vec<(f32, T)>, +pub struct Lottery = Vec<(f32, T)>> { + items: Items, total: f32, + phantom: PhantomData, } impl assets::Asset for Lottery { @@ -55,15 +60,29 @@ impl From> for Lottery { *rate = total - *rate; } - Self { items, total } + Self { items, total, phantom: PhantomData } } } -impl Lottery { +impl<'a, T> Lottery { + pub fn from_slice(items: &'a mut [(f32, T)]) -> Self { + let mut total = 0.0; + + for (rate, _) in items.iter_mut() { + total += *rate; + *rate = total - *rate; + } + + Self { items, total, phantom: PhantomData } + } +} + +impl> Lottery { pub fn choose_seeded(&self, seed: u32) -> &T { let x = ((seed % 65536) as f32 / 65536.0) * self.total; - &self.items[self + &self.items.as_ref()[self .items + .as_ref() .binary_search_by(|(y, _)| y.partial_cmp(&x).unwrap()) .unwrap_or_else(|i| i.saturating_sub(1))] .1 @@ -71,7 +90,7 @@ impl Lottery { pub fn choose(&self) -> &T { self.choose_seeded(thread_rng().gen()) } - pub fn iter(&self) -> impl Iterator { self.items.iter() } + pub fn iter(&self) -> impl Iterator { self.items.as_ref().iter() } pub fn total(&self) -> f32 { self.total } } diff --git a/world/src/site2/gen.rs b/world/src/site2/gen.rs index 7a73577bc9..83ebcbb5b5 100644 --- a/world/src/site2/gen.rs +++ b/world/src/site2/gen.rs @@ -67,7 +67,7 @@ impl Fill { let inner = Aabr { min: aabb.min.xy() - 1 + inset, max: aabb.max.xy() - inset, - }; + }.made_valid(); aabb_contains(*aabb, pos) && ((inner.projected_point(pos.xy()) - pos.xy()) .map(|e| e.abs()) @@ -241,7 +241,7 @@ impl Fill { } } -pub trait Structure { +pub trait Render { fn render Id, G: FnMut(Id, Fill)>( &self, site: &Site, diff --git a/world/src/site2/mod.rs b/world/src/site2/mod.rs index 5c9e792540..88ff15c4c2 100644 --- a/world/src/site2/mod.rs +++ b/world/src/site2/mod.rs @@ -1,9 +1,11 @@ mod gen; +mod planning; mod plot; +mod structure; mod tile; use self::{ - gen::{Fill, Primitive, Structure}, + gen::{Fill, Primitive, Render}, plot::{Plot, PlotKind}, tile::{HazardKind, KeepKind, Ori, RoofKind, Tile, TileGrid, TileKind, TILE_SIZE}, }; @@ -166,7 +168,7 @@ impl Site { ) -> Option<(Aabr, Vec2)> { self.tiles.find_near(search_pos, |center, _| { self.tiles - .grow_aabr(center, area_range.clone(), min_dims) + .grow_aabr(center, area_range.clone(), min_dims, 0) .ok() .filter(|aabr| { (aabr.min.x..aabr.max.x) @@ -285,6 +287,13 @@ impl Site { site.demarcate_obstacles(land); + for _ in 0..100 { + site.tick(land, &mut rng); + } + + return site; + + /* site.make_plaza(land, &mut rng); let build_chance = Lottery::from(vec![(128.0, 1), (5.0, 2), (8.0, 3), (0.75, 4)]); @@ -523,6 +532,7 @@ impl Site { } site + */ } pub fn wpos_tile_pos(&self, wpos2d: Vec2) -> Vec2 { @@ -711,6 +721,7 @@ impl Site { for plot in plots_to_render { let (prim_tree, fills) = match &self.plots[plot].kind { + PlotKind::Hut(hut) => hut.render_collect(self), PlotKind::House(house) => house.render_collect(self), PlotKind::Castle(castle) => castle.render_collect(self), _ => continue, diff --git a/world/src/site2/planning.rs b/world/src/site2/planning.rs new file mode 100644 index 0000000000..7dd34e7adb --- /dev/null +++ b/world/src/site2/planning.rs @@ -0,0 +1,35 @@ +use common::lottery::Lottery; +use super::{ + *, + structure::{Structure, Hut}, +}; + +// All are weights, must be positive, 1.0 is default. +pub struct Values { + defence: f32, + farming: f32, + housing: f32, +} + +impl Site { + // TODO: How long is a tick? A year? + pub fn tick(&mut self, land: &Land, rng: &mut impl Rng) { + let values = Values { + defence: 1.0, + farming: 1.0, + housing: 1.0, + }; + + match *Lottery::from_slice(&mut [ + (10.0, 0), // Huts + ]) + .choose_seeded(rng.gen()) + { + 0 => { + Hut::choose_location((), land, self, rng) + .map(|hut| hut.generate(land, self, rng)); + }, + _ => unreachable!(), + } + } +} diff --git a/world/src/site2/plot.rs b/world/src/site2/plot.rs index 3b0bcaa1f1..2c8a6e352b 100644 --- a/world/src/site2/plot.rs +++ b/world/src/site2/plot.rs @@ -2,6 +2,7 @@ mod castle; mod house; pub use self::{castle::Castle, house::House}; +pub use super::structure::Hut; use super::*; use crate::util::DHashSet; @@ -26,6 +27,7 @@ impl Plot { } pub enum PlotKind { + Hut(Hut), House(House), Plaza, Castle(Castle), diff --git a/world/src/site2/plot/castle.rs b/world/src/site2/plot/castle.rs index e9e5ef63e7..71e02fa27a 100644 --- a/world/src/site2/plot/castle.rs +++ b/world/src/site2/plot/castle.rs @@ -41,7 +41,7 @@ impl Castle { } } -impl Structure for Castle { +impl Render for Castle { #[allow(clippy::identity_op)] fn render Id, G: FnMut(Id, Fill)>( &self, diff --git a/world/src/site2/plot/house.rs b/world/src/site2/plot/house.rs index 873da73d99..e39220d0b9 100644 --- a/world/src/site2/plot/house.rs +++ b/world/src/site2/plot/house.rs @@ -52,7 +52,7 @@ impl House { } } -impl Structure for House { +impl Render for House { fn render Id, G: FnMut(Id, Fill)>( &self, site: &Site, diff --git a/world/src/site2/structure/hut.rs b/world/src/site2/structure/hut.rs new file mode 100644 index 0000000000..f082a708dc --- /dev/null +++ b/world/src/site2/structure/hut.rs @@ -0,0 +1,110 @@ +use super::*; +use vek::*; + +pub struct Hut { + root: Vec2, + tile_aabr: Aabr, + bounds: Aabr, + alt: i32, + height: i32, + door_dir: Vec2, +} + +impl Structure for Hut { + type Config = (); + + fn choose_location(cfg: Self::Config, land: &Land, site: &Site, rng: &mut R) -> Option { + let (tile_aabr, root) = site.tiles.find_near( + Vec2::zero(), + |tile, _| if rng.gen_range(0..16) == 0 { + site.tiles.grow_aabr(tile, 4..9, (2, 2), 2).ok() + } else { + None + }, + )?; + let center = (tile_aabr.min + (tile_aabr.max - 1)) / 2; + + Some(Self { + root, + tile_aabr, + bounds: Aabr { + min: site.tile_wpos(tile_aabr.min), + max: site.tile_wpos(tile_aabr.max), + }, + alt: land.get_alt_approx(site.tile_center_wpos(center)) as i32, + height: 4, + door_dir: match rng.gen_range(0..4) { + 0 => Vec2::unit_x(), + 1 => -Vec2::unit_x(), + 2 => Vec2::unit_y(), + 3 => -Vec2::unit_y(), + _ => unreachable!(), + }, + }) + } + + fn generate(self, land: &Land, site: &mut Site, rng: &mut R) { + let aabr = self.tile_aabr; + + let plot = site.create_plot(Plot { + root_tile: self.root, + tiles: aabr_tiles(aabr).collect(), + seed: rng.gen(), + kind: PlotKind::Hut(self), + }); + + site.blit_aabr(aabr, Tile { + kind: TileKind::Building, + plot: Some(plot), + }); + } +} + +impl Render for Hut { + fn render Id, G: FnMut(Id, Fill)>( + &self, + site: &Site, + mut prim: F, + mut fill: G, + ) { + let daub = Fill::Block(Block::new(BlockKind::Wood, Rgb::new(110, 50, 16))); + + let outer = prim(Primitive::Aabb(Aabb { + min: (self.bounds.min + 1).with_z(self.alt), + max: (self.bounds.max - 1).with_z(self.alt + self.height), + })); + let inner = prim(Primitive::Aabb(Aabb { + min: (self.bounds.min + 2).with_z(self.alt), + max: (self.bounds.max - 2).with_z(self.alt + self.height), + })); + + let door_pos = site.tile_center_wpos(self.root); + let door = prim(Primitive::Aabb(Aabb { + min: door_pos.with_z(self.alt), + max: (door_pos + self.door_dir * 32).with_z(self.alt + 2), + }.made_valid())); + + let space = prim(Primitive::And(inner, door)); + + let walls = prim(Primitive::AndNot(outer, space)); + + fill(walls, daub); + + let roof_lip = 2; + let roof_height = (self.bounds.min - self.bounds.max) + .map(|e| e.abs()) + .reduce_min() + .saturating_sub(1) + / 2 + + roof_lip + + 1; + let roof = prim(Primitive::Pyramid{ + aabb: Aabb { + min: (self.bounds.min + 1 - roof_lip).with_z(self.alt + self.height), + max: (self.bounds.max - 1 + roof_lip).with_z(self.alt + self.height + roof_height), + }, + inset: Vec2::broadcast(roof_height), + }); + fill(roof, daub); + } +} diff --git a/world/src/site2/structure/mod.rs b/world/src/site2/structure/mod.rs new file mode 100644 index 0000000000..d17e28b55e --- /dev/null +++ b/world/src/site2/structure/mod.rs @@ -0,0 +1,17 @@ +mod hut; + +pub use self::{ + hut::Hut, +}; + +use super::*; + +pub trait Structure: Sized { + type Config; + + /// Attempt to choose a location to place this plot in the given site. + fn choose_location(cfg: Self::Config, land: &Land, site: &Site, rng: &mut R) -> Option; + + /// Generate the plot with the given location information on the given site + fn generate(self, land: &Land, site: &mut Site, rng: &mut R); +} diff --git a/world/src/site2/tile.rs b/world/src/site2/tile.rs index fd82266926..6946a07ff1 100644 --- a/world/src/site2/tile.rs +++ b/world/src/site2/tile.rs @@ -75,8 +75,11 @@ impl TileGrid { &self, center: Vec2, area_range: Range, - min_dims: Extent2, + min_dims: impl Into>, + radius: u32, ) -> Result, Aabr> { + let min_dims = min_dims.into() + radius * 2; + let mut aabr = Aabr { min: center, max: center + 1, @@ -86,15 +89,16 @@ impl TileGrid { return Err(aabr); }; + let inner_size = |aabr: Aabr| aabr.size().map(|e| e.saturating_sub(radius as i32 * 2)); + let mut last_growth = 0; for i in 0..32 { if i - last_growth >= 4 - || aabr.size().product() - + if i % 2 == 0 { - aabr.size().h + || (inner_size(aabr) + if i % 2 == 0 { + Extent2::new(0, 1) } else { - aabr.size().w - } + Extent2::new(1, 0) + }).product() > area_range.end as i32 { break; @@ -130,13 +134,19 @@ impl TileGrid { } } - if aabr.size().product() as u32 >= area_range.start + let inner_size = inner_size(aabr); + let inner_aabr = Aabr { + min: aabr.min + radius as i32, + max: aabr.min + radius as i32 + inner_size, + }; + + if inner_size.product() as u32 >= area_range.start && aabr.size().w as u32 >= min_dims.w && aabr.size().h as u32 >= min_dims.h { - Ok(aabr) + Ok(inner_aabr) } else { - Err(aabr) + Err(inner_aabr) } }