Added forts to towns, began better economy sim

This commit is contained in:
Joshua Barretto 2020-06-17 19:05:47 +01:00
parent ae8195fac9
commit f21a50e393
25 changed files with 929 additions and 301 deletions

View File

@ -50,20 +50,17 @@ impl<T> Store<T> {
} }
pub fn ids(&self) -> impl Iterator<Item = Id<T>> { pub fn ids(&self) -> impl Iterator<Item = Id<T>> {
// NOTE: Assumes usize fits into 8 bytes. (0..self.items.len()).map(|i| Id(i as u64, PhantomData))
(0..self.items.len() as u64).map(|i| Id(i, PhantomData))
} }
pub fn iter(&self) -> impl Iterator<Item = &T> { self.items.iter() } pub fn values(&self) -> impl Iterator<Item = &T> { self.items.iter() }
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> { self.items.iter_mut() } pub fn values_mut(&mut self) -> impl Iterator<Item = &mut T> { self.items.iter_mut() }
pub fn iter_ids(&self) -> impl Iterator<Item = (Id<T>, &T)> { pub fn iter(&self) -> impl Iterator<Item = (Id<T>, &T)> { self.ids().zip(self.values()) }
self.items
.iter() pub fn iter_mut(&mut self) -> impl Iterator<Item = (Id<T>, &mut T)> {
.enumerate() self.ids().zip(self.values_mut())
// NOTE: Assumes usize fits into 8 bytes.
.map(|(i, item)| (Id(i as u64, PhantomData), item))
} }
pub fn insert(&mut self, item: T) -> Id<T> { pub fn insert(&mut self, item: T) -> Id<T> {

View File

@ -1 +1 @@
economy.csv

View File

@ -0,0 +1 @@
,joshua,archbox.localdomain,17.06.2020 16:07,file:///home/joshua/.config/libreoffice/4;

View File

@ -1437,7 +1437,7 @@ fn handle_debug_column(
let spawn_rate = sim.get_interpolated(wpos, |chunk| chunk.spawn_rate)?; let spawn_rate = sim.get_interpolated(wpos, |chunk| chunk.spawn_rate)?;
let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32); let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32);
let chunk = sim.get(chunk_pos)?; let chunk = sim.get(chunk_pos)?;
let col = sampler.get(wpos)?; let col = sampler.get((wpos, server.world.index()))?;
let downhill = chunk.downhill; let downhill = chunk.downhill;
let river = &chunk.river; let river = &chunk.river;
let flux = chunk.flux; let flux = chunk.flux;

View File

@ -184,7 +184,7 @@ impl Server {
..WorldOpts::default() ..WorldOpts::default()
}); });
#[cfg(feature = "worldgen")] #[cfg(feature = "worldgen")]
let map = world.sim().get_map(); let map = world.get_map_data();
#[cfg(not(feature = "worldgen"))] #[cfg(not(feature = "worldgen"))]
let world = World::generate(settings.world_seed); let world = World::generate(settings.world_seed);
@ -219,11 +219,11 @@ impl Server {
// get a z cache for the collumn in which we want to spawn // get a z cache for the collumn in which we want to spawn
let mut block_sampler = world.sample_blocks(); let mut block_sampler = world.sample_blocks();
let z_cache = block_sampler let z_cache = block_sampler
.get_z_cache(spawn_location) .get_z_cache(spawn_location, world.index())
.expect(&format!("no z_cache found for chunk: {}", spawn_chunk)); .expect(&format!("no z_cache found for chunk: {}", spawn_chunk));
// get the minimum and maximum z values at which there could be soild blocks // get the minimum and maximum z values at which there could be soild blocks
let (min_z, _, max_z) = z_cache.get_z_limits(&mut block_sampler); let (min_z, _, max_z) = z_cache.get_z_limits(&mut block_sampler, world.index());
// round range outwards, so no potential air block is missed // round range outwards, so no potential air block is missed
let min_z = min_z.floor() as i32; let min_z = min_z.floor() as i32;
let max_z = max_z.ceil() as i32; let max_z = max_z.ceil() as i32;
@ -239,6 +239,7 @@ impl Server {
Vec3::new(spawn_location.x, spawn_location.y, *z), Vec3::new(spawn_location.x, spawn_location.y, *z),
Some(&z_cache), Some(&z_cache),
false, false,
world.index(),
) )
.map(|b| b.is_air()) .map(|b| b.is_air())
.unwrap_or(false) .unwrap_or(false)

View File

@ -3,7 +3,7 @@ mod natural;
use crate::{ use crate::{
column::{ColumnGen, ColumnSample}, column::{ColumnGen, ColumnSample},
util::{RandomField, Sampler, SmallCache}, util::{RandomField, Sampler, SmallCache},
CONFIG, Index, CONFIG,
}; };
use common::{ use common::{
terrain::{structure::StructureBlock, Block, BlockKind, Structure}, terrain::{structure::StructureBlock, Block, BlockKind, Structure},
@ -29,8 +29,9 @@ impl<'a> BlockGen<'a> {
column_gen: &ColumnGen<'a>, column_gen: &ColumnGen<'a>,
cache: &'b mut SmallCache<Option<ColumnSample<'a>>>, cache: &'b mut SmallCache<Option<ColumnSample<'a>>>,
wpos: Vec2<i32>, wpos: Vec2<i32>,
index: &Index,
) -> Option<&'b ColumnSample<'a>> { ) -> Option<&'b ColumnSample<'a>> {
cache.get(wpos, |wpos| column_gen.get(wpos)).as_ref() cache.get(wpos, |wpos| column_gen.get((wpos, index))).as_ref()
} }
fn get_cliff_height( fn get_cliff_height(
@ -40,11 +41,16 @@ impl<'a> BlockGen<'a> {
close_cliffs: &[(Vec2<i32>, u32); 9], close_cliffs: &[(Vec2<i32>, u32); 9],
cliff_hill: f32, cliff_hill: f32,
tolerance: f32, tolerance: f32,
index: &Index,
) -> f32 { ) -> f32 {
close_cliffs.iter().fold( close_cliffs.iter().fold(
0.0f32, 0.0f32,
|max_height, (cliff_pos, seed)| match Self::sample_column(column_gen, cache, *cliff_pos) |max_height, (cliff_pos, seed)| match Self::sample_column(
{ column_gen,
cache,
Vec2::from(*cliff_pos),
index,
) {
Some(cliff_sample) if cliff_sample.is_cliffs && cliff_sample.spawn_rate > 0.5 => { Some(cliff_sample) if cliff_sample.is_cliffs && cliff_sample.spawn_rate > 0.5 => {
let cliff_pos3d = Vec3::from(*cliff_pos); let cliff_pos3d = Vec3::from(*cliff_pos);
@ -84,14 +90,14 @@ impl<'a> BlockGen<'a> {
) )
} }
pub fn get_z_cache(&mut self, wpos: Vec2<i32>) -> Option<ZCache<'a>> { pub fn get_z_cache(&mut self, wpos: Vec2<i32>, index: &'a Index) -> Option<ZCache<'a>> {
let BlockGen { let BlockGen {
column_cache, column_cache,
column_gen, column_gen,
} = self; } = self;
// Main sample // Main sample
let sample = column_gen.get(wpos)?; let sample = column_gen.get((wpos, index))?;
// Tree samples // Tree samples
let mut structures = [None, None, None, None, None, None, None, None, None]; let mut structures = [None, None, None, None, None, None, None, None, None];
@ -101,7 +107,7 @@ impl<'a> BlockGen<'a> {
.zip(structures.iter_mut()) .zip(structures.iter_mut())
.for_each(|(close_structure, structure)| { .for_each(|(close_structure, structure)| {
if let Some(st) = *close_structure { if let Some(st) = *close_structure {
let st_sample = Self::sample_column(column_gen, column_cache, st.pos); let st_sample = Self::sample_column(column_gen, column_cache, st.pos, index);
if let Some(st_sample) = st_sample { if let Some(st_sample) = st_sample {
let st_sample = st_sample.clone(); let st_sample = st_sample.clone();
let st_info = match st.meta { let st_info = match st.meta {
@ -111,6 +117,7 @@ impl<'a> BlockGen<'a> {
st.pos, st.pos,
st.seed, st.seed,
&st_sample, &st_sample,
index,
), ),
Some(meta) => Some(StructureInfo { Some(meta) => Some(StructureInfo {
pos: Vec3::from(st.pos) + Vec3::unit_z() * st_sample.alt as i32, pos: Vec3::from(st.pos) + Vec3::unit_z() * st_sample.alt as i32,
@ -137,6 +144,7 @@ impl<'a> BlockGen<'a> {
wpos: Vec3<i32>, wpos: Vec3<i32>,
z_cache: Option<&ZCache>, z_cache: Option<&ZCache>,
only_structures: bool, only_structures: bool,
index: &Index,
) -> Option<Block> { ) -> Option<Block> {
let BlockGen { let BlockGen {
column_cache, column_cache,
@ -208,6 +216,7 @@ impl<'a> BlockGen<'a> {
&close_cliffs, &close_cliffs,
cliff_hill, cliff_hill,
0.0, 0.0,
index,
); );
( (
@ -412,7 +421,7 @@ pub struct ZCache<'a> {
} }
impl<'a> ZCache<'a> { impl<'a> ZCache<'a> {
pub fn get_z_limits(&self, block_gen: &mut BlockGen) -> (f32, f32, f32) { pub fn get_z_limits(&self, block_gen: &mut BlockGen, index: &Index) -> (f32, f32, f32) {
let cave_depth = let cave_depth =
if self.sample.cave_xy.abs() > 0.9 && self.sample.water_level <= self.sample.alt { if self.sample.cave_xy.abs() > 0.9 && self.sample.water_level <= self.sample.alt {
(self.sample.alt - self.sample.cave_alt + 8.0).max(0.0) (self.sample.alt - self.sample.cave_alt + 8.0).max(0.0)
@ -430,6 +439,7 @@ impl<'a> ZCache<'a> {
&self.sample.close_cliffs, &self.sample.close_cliffs,
self.sample.cliff_hill, self.sample.cliff_hill,
32.0, 32.0,
index,
); );
let rocks = if self.sample.rock > 0.0 { 12.0 } else { 0.0 }; let rocks = if self.sample.rock > 0.0 { 12.0 } else { 0.0 };

View File

@ -3,7 +3,7 @@ use crate::{
all::ForestKind, all::ForestKind,
column::{ColumnGen, ColumnSample}, column::{ColumnGen, ColumnSample},
util::{RandomPerm, Sampler, SmallCache, UnitChooser}, util::{RandomPerm, Sampler, SmallCache, UnitChooser},
CONFIG, Index, CONFIG,
}; };
use common::terrain::Structure; use common::terrain::Structure;
use lazy_static::lazy_static; use lazy_static::lazy_static;
@ -20,6 +20,7 @@ pub fn structure_gen<'a>(
st_pos: Vec2<i32>, st_pos: Vec2<i32>,
st_seed: u32, st_seed: u32,
st_sample: &ColumnSample, st_sample: &ColumnSample,
index: &'a Index,
) -> Option<StructureInfo> { ) -> Option<StructureInfo> {
// Assuming it's a tree... figure out when it SHOULDN'T spawn // Assuming it's a tree... figure out when it SHOULDN'T spawn
let random_seed = (st_seed as f64) / (u32::MAX as f64); let random_seed = (st_seed as f64) / (u32::MAX as f64);
@ -39,6 +40,7 @@ pub fn structure_gen<'a>(
&st_sample.close_cliffs, &st_sample.close_cliffs,
st_sample.cliff_hill, st_sample.cliff_hill,
0.0, 0.0,
index,
); );
let wheight = st_sample.alt.max(cliff_height); let wheight = st_sample.alt.max(cliff_height);

View File

@ -6,7 +6,8 @@ use self::{Occupation::*, Stock::*};
use crate::{ use crate::{
sim::WorldSim, sim::WorldSim,
site::{Dungeon, Settlement, Site as WorldSite}, site::{Dungeon, Settlement, Site as WorldSite},
util::{attempt, seed_expan, CARDINALS, NEIGHBORS}, util::{attempt, seed_expan, MapVec, CARDINALS, NEIGHBORS},
Index,
}; };
use common::{ use common::{
astar::Astar, astar::Astar,
@ -70,7 +71,7 @@ impl<'a, R: Rng> GenCtx<'a, R> {
} }
impl Civs { impl Civs {
pub fn generate(seed: u32, sim: &mut WorldSim) -> Self { pub fn generate(seed: u32, sim: &mut WorldSim, index: &mut Index) -> Self {
let mut this = Self::default(); let mut this = Self::default();
let rng = ChaChaRng::from_seed(seed_expan::rng_state(seed)); let rng = ChaChaRng::from_seed(seed_expan::rng_state(seed));
let mut ctx = GenCtx { sim, rng }; let mut ctx = GenCtx { sim, rng };
@ -103,7 +104,7 @@ impl Civs {
last_exports: Stocks::from_default(0.0), last_exports: Stocks::from_default(0.0),
export_targets: Stocks::from_default(0.0), export_targets: Stocks::from_default(0.0),
trade_states: Stocks::default(), //trade_states: Stocks::default(),
coin: 1000.0, coin: 1000.0,
}) })
}); });
@ -116,13 +117,13 @@ impl Civs {
} }
// Flatten ground around sites // Flatten ground around sites
for site in this.sites.iter() { for site in this.sites.values() {
let radius = 48i32; let radius = 48i32;
let wpos = site.center * TerrainChunkSize::RECT_SIZE.map(|e: u32| e as i32); let wpos = site.center * TerrainChunkSize::RECT_SIZE.map(|e: u32| e as i32);
let flatten_radius = match &site.kind { let flatten_radius = match &site.kind {
SiteKind::Settlement => 10.0, SiteKind::Settlement => 8.0,
SiteKind::Dungeon => 2.0, SiteKind::Dungeon => 2.0,
}; };
@ -143,7 +144,7 @@ impl Civs {
let pos = site.center + offs; let pos = site.center + offs;
let factor = (1.0 let factor = (1.0
- (site.center - pos).map(|e| e as f32).magnitude() / flatten_radius) - (site.center - pos).map(|e| e as f32).magnitude() / flatten_radius)
* 1.15; * 0.8;
ctx.sim ctx.sim
.get_mut(pos) .get_mut(pos)
// Don't disrupt chunks that are near water // Don't disrupt chunks that are near water
@ -161,33 +162,32 @@ impl Civs {
// Place sites in world // Place sites in world
let mut cnt = 0; let mut cnt = 0;
for site in this.sites.iter() { for sim_site in this.sites.values() {
cnt += 1; cnt += 1;
let wpos = site.center.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| { let wpos = sim_site.center.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| {
e * sz as i32 + sz as i32 / 2 e * sz as i32 + sz as i32 / 2
}); });
let mut rng = ctx.reseed().rng; let mut rng = ctx.reseed().rng;
let world_site = match &site.kind { let site = index.sites.insert(match &sim_site.kind {
SiteKind::Settlement => { SiteKind::Settlement => {
WorldSite::from(Settlement::generate(wpos, Some(ctx.sim), &mut rng)) WorldSite::settlement(Settlement::generate(wpos, Some(ctx.sim), &mut rng))
}, },
SiteKind::Dungeon => { SiteKind::Dungeon => {
WorldSite::from(Dungeon::generate(wpos, Some(ctx.sim), &mut rng)) WorldSite::dungeon(Dungeon::generate(wpos, Some(ctx.sim), &mut rng))
}, },
}; });
let site_ref = &index.sites[site];
let radius_chunks = let radius_chunks =
(world_site.radius() / TerrainChunkSize::RECT_SIZE.x as f32).ceil() as usize; (site_ref.radius() / TerrainChunkSize::RECT_SIZE.x as f32).ceil() as usize;
for pos in Spiral2d::new() for pos in Spiral2d::new()
.map(|offs| site.center + offs) .map(|offs| sim_site.center + offs)
.take((radius_chunks * 2).pow(2)) .take((radius_chunks * 2).pow(2))
{ {
ctx.sim ctx.sim.get_mut(pos).map(|chunk| chunk.sites.push(site));
.get_mut(pos)
.map(|chunk| chunk.sites.push(world_site.clone()));
} }
debug!(?site.center, "Placed site at location"); debug!(?sim_site.center, "Placed site at location");
} }
info!(?cnt, "all sites placed"); info!(?cnt, "all sites placed");
@ -198,18 +198,18 @@ impl Civs {
pub fn place(&self, id: Id<Place>) -> &Place { self.places.get(id) } pub fn place(&self, id: Id<Place>) -> &Place { self.places.get(id) }
pub fn sites(&self) -> impl Iterator<Item = &Site> + '_ { self.sites.iter() } pub fn sites(&self) -> impl Iterator<Item = &Site> + '_ { self.sites.values() }
#[allow(dead_code)] #[allow(dead_code)]
#[allow(clippy::print_literal)] // TODO: Pending review in #587 #[allow(clippy::print_literal)] // TODO: Pending review in #587
fn display_info(&self) { fn display_info(&self) {
for (id, civ) in self.civs.iter_ids() { for (id, civ) in self.civs.iter() {
println!("# Civilisation {:?}", id); println!("# Civilisation {:?}", id);
println!("Name: {}", "<unnamed>"); println!("Name: {}", "<unnamed>");
println!("Homeland: {:#?}", self.places.get(civ.homeland)); println!("Homeland: {:#?}", self.places.get(civ.homeland));
} }
for (id, site) in self.sites.iter_ids() { for (id, site) in self.sites.iter() {
println!("# Site {:?}", id); println!("# Site {:?}", id);
println!("{:#?}", site); println!("{:#?}", site);
} }
@ -290,7 +290,7 @@ impl Civs {
last_exports: Stocks::from_default(0.0), last_exports: Stocks::from_default(0.0),
export_targets: Stocks::from_default(0.0), export_targets: Stocks::from_default(0.0),
trade_states: Stocks::default(), //trade_states: Stocks::default(),
coin: 1000.0, coin: 1000.0,
}) })
})?; })?;
@ -380,7 +380,7 @@ impl Civs {
const MAX_NEIGHBOR_DISTANCE: f32 = 500.0; const MAX_NEIGHBOR_DISTANCE: f32 = 500.0;
let mut nearby = self let mut nearby = self
.sites .sites
.iter_ids() .iter()
.map(|(id, p)| (id, (p.center.distance_squared(loc) as f32).sqrt())) .map(|(id, p)| (id, (p.center.distance_squared(loc) as f32).sqrt()))
.filter(|(_, dist)| *dist < MAX_NEIGHBOR_DISTANCE) .filter(|(_, dist)| *dist < MAX_NEIGHBOR_DISTANCE)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -440,7 +440,7 @@ impl Civs {
} }
fn tick(&mut self, _ctx: &mut GenCtx<impl Rng>, years: f32) { fn tick(&mut self, _ctx: &mut GenCtx<impl Rng>, years: f32) {
for site in self.sites.iter_mut() { for site in self.sites.values_mut() {
site.simulate(years, &self.places.get(site.place).nat_res); site.simulate(years, &self.places.get(site.place).nat_res);
} }
@ -717,7 +717,7 @@ pub struct Site {
last_exports: Stocks<f32>, last_exports: Stocks<f32>,
export_targets: Stocks<f32>, export_targets: Stocks<f32>,
trade_states: Stocks<TradeState>, //trade_states: Stocks<TradeState>,
coin: f32, coin: f32,
} }
@ -996,79 +996,3 @@ impl Default for TradeState {
} }
pub type Stocks<T> = MapVec<Stock, T>; pub type Stocks<T> = MapVec<Stock, T>;
#[derive(Clone, Debug)]
pub struct MapVec<K, T> {
/// We use this hasher (FxHasher32) because
/// (1) we don't care about DDOS attacks (ruling out SipHash);
/// (2) we care about determinism across computers (ruling out AAHash);
/// (3) we have 1-byte keys (for which FxHash is supposedly fastest).
entries: HashMap<K, T, BuildHasherDefault<FxHasher32>>,
default: T,
}
/// Need manual implementation of Default since K doesn't need that bound.
impl<K, T: Default> Default for MapVec<K, T> {
fn default() -> Self {
Self {
entries: Default::default(),
default: Default::default(),
}
}
}
impl<K: Copy + Eq + Hash, T: Clone> MapVec<K, T> {
pub fn from_list<'a>(i: impl IntoIterator<Item = &'a (K, T)>, default: T) -> Self
where
K: 'a,
T: 'a,
{
Self {
entries: i.into_iter().cloned().collect(),
default,
}
}
pub fn from_default(default: T) -> Self {
Self {
entries: HashMap::default(),
default,
}
}
pub fn get_mut(&mut self, entry: K) -> &mut T {
let default = &self.default;
self.entries.entry(entry).or_insert_with(|| default.clone())
}
pub fn get(&self, entry: K) -> &T { self.entries.get(&entry).unwrap_or(&self.default) }
pub fn map<U: Default>(self, mut f: impl FnMut(K, T) -> U) -> MapVec<K, U> {
MapVec {
entries: self
.entries
.into_iter()
.map(|(s, v)| (s, f(s, v)))
.collect(),
default: U::default(),
}
}
pub fn iter(&self) -> impl Iterator<Item = (K, &T)> + '_ {
self.entries.iter().map(|(s, v)| (*s, v))
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (K, &mut T)> + '_ {
self.entries.iter_mut().map(|(s, v)| (*s, v))
}
}
impl<K: Copy + Eq + Hash, T: Clone> std::ops::Index<K> for MapVec<K, T> {
type Output = T;
fn index(&self, entry: K) -> &Self::Output { self.get(entry) }
}
impl<K: Copy + Eq + Hash, T: Clone> std::ops::IndexMut<K> for MapVec<K, T> {
fn index_mut(&mut self, entry: K) -> &mut Self::Output { self.get_mut(entry) }
}

View File

@ -3,7 +3,7 @@ use crate::{
block::StructureMeta, block::StructureMeta,
sim::{local_cells, uniform_idx_as_vec2, vec2_as_uniform_idx, RiverKind, SimChunk, WorldSim}, sim::{local_cells, uniform_idx_as_vec2, vec2_as_uniform_idx, RiverKind, SimChunk, WorldSim},
util::Sampler, util::Sampler,
CONFIG, Index, CONFIG,
}; };
use common::{terrain::TerrainChunkSize, vol::RectVolSize}; use common::{terrain::TerrainChunkSize, vol::RectVolSize};
use noise::NoiseFn; use noise::NoiseFn;
@ -165,8 +165,11 @@ pub fn quadratic_nearest_point(
min_root min_root
} }
impl<'a> Sampler<'a> for ColumnGen<'a> { impl<'a, 'b> Sampler<'b> for ColumnGen<'a>
type Index = Vec2<i32>; where
'a: 'b,
{
type Index = (Vec2<i32>, &'b Index);
type Sample = Option<ColumnSample<'a>>; type Sample = Option<ColumnSample<'a>>;
#[allow(clippy::float_cmp)] // TODO: Pending review in #587 #[allow(clippy::float_cmp)] // TODO: Pending review in #587
@ -174,7 +177,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
#[allow(clippy::nonminimal_bool)] // TODO: Pending review in #587 #[allow(clippy::nonminimal_bool)] // TODO: Pending review in #587
#[allow(clippy::single_match)] // TODO: Pending review in #587 #[allow(clippy::single_match)] // TODO: Pending review in #587
#[allow(clippy::bind_instead_of_map)] // TODO: Pending review in #587 #[allow(clippy::bind_instead_of_map)] // TODO: Pending review in #587
fn get(&self, wpos: Vec2<i32>) -> Option<ColumnSample<'a>> { fn get(&self, (wpos, index): Self::Index) -> Option<ColumnSample<'a>> {
let wposf = wpos.map(|e| e as f64); let wposf = wpos.map(|e| e as f64);
let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32); let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32);
@ -1098,7 +1101,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
tree_density: if sim_chunk tree_density: if sim_chunk
.sites .sites
.iter() .iter()
.all(|site| site.spawn_rules(wpos).trees) .all(|site| index.sites[*site].spawn_rules(wpos).trees)
{ {
Lerp::lerp(0.0, tree_density, alt.sub(2.0).sub(basement).mul(0.5)) Lerp::lerp(0.0, tree_density, alt.sub(2.0).sub(basement).mul(0.5))
} else { } else {

8
world/src/index.rs Normal file
View File

@ -0,0 +1,8 @@
use crate::site::Site;
use common::store::{Id, Store};
#[derive(Default)]
pub struct Index {
pub time: f32,
pub sites: Store<Site>,
}

View File

@ -8,8 +8,10 @@ mod block;
pub mod civ; pub mod civ;
mod column; mod column;
pub mod config; pub mod config;
pub mod index;
pub mod layer; pub mod layer;
pub mod sim; pub mod sim;
pub mod sim2;
pub mod site; pub mod site;
pub mod util; pub mod util;
@ -19,6 +21,7 @@ pub use crate::config::CONFIG;
use crate::{ use crate::{
block::BlockGen, block::BlockGen,
column::{ColumnGen, ColumnSample}, column::{ColumnGen, ColumnSample},
index::Index,
util::{Grid, Sampler}, util::{Grid, Sampler},
}; };
use common::{ use common::{
@ -39,26 +42,35 @@ pub enum Error {
pub struct World { pub struct World {
sim: sim::WorldSim, sim: sim::WorldSim,
civs: civ::Civs, civs: civ::Civs,
index: Index,
} }
impl World { impl World {
pub fn generate(seed: u32, opts: sim::WorldOpts) -> Self { pub fn generate(seed: u32, opts: sim::WorldOpts) -> Self {
let mut sim = sim::WorldSim::generate(seed, opts); let mut sim = sim::WorldSim::generate(seed, opts);
let civs = civ::Civs::generate(seed, &mut sim); let mut index = Index::default();
Self { sim, civs } let civs = civ::Civs::generate(seed, &mut sim, &mut index);
sim2::simulate(&mut index, &mut sim);
Self { sim, civs, index }
} }
pub fn sim(&self) -> &sim::WorldSim { &self.sim } pub fn sim(&self) -> &sim::WorldSim { &self.sim }
pub fn civs(&self) -> &civ::Civs { &self.civs } pub fn civs(&self) -> &civ::Civs { &self.civs }
pub fn index(&self) -> &Index { &self.index }
pub fn tick(&self, _dt: Duration) { pub fn tick(&self, _dt: Duration) {
// TODO // TODO
} }
pub fn get_map_data(&self) -> Vec<u32> { self.sim.get_map(&self.index) }
pub fn sample_columns( pub fn sample_columns(
&self, &self,
) -> impl Sampler<Index = Vec2<i32>, Sample = Option<ColumnSample>> + '_ { ) -> impl Sampler<Index = (Vec2<i32>, &Index), Sample = Option<ColumnSample>> + '_ {
ColumnGen::new(&self.sim) ColumnGen::new(&self.sim)
} }
@ -77,7 +89,7 @@ impl World {
let grid_border = 4; let grid_border = 4;
let zcache_grid = Grid::populate_from( let zcache_grid = Grid::populate_from(
TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + grid_border * 2, TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + grid_border * 2,
|offs| sampler.get_z_cache(chunk_wpos2d - grid_border + offs), |offs| sampler.get_z_cache(chunk_wpos2d - grid_border + offs, &self.index),
); );
let air = Block::empty(); let air = Block::empty();
@ -132,7 +144,8 @@ impl World {
_ => continue, _ => continue,
}; };
let (min_z, only_structures_min_z, max_z) = z_cache.get_z_limits(&mut sampler); let (min_z, only_structures_min_z, max_z) =
z_cache.get_z_limits(&mut sampler, &self.index);
(base_z..min_z as i32).for_each(|z| { (base_z..min_z as i32).for_each(|z| {
let _ = chunk.set(Vec3::new(x, y, z), stone); let _ = chunk.set(Vec3::new(x, y, z), stone);
@ -144,7 +157,7 @@ impl World {
let only_structures = lpos.z >= only_structures_min_z as i32; let only_structures = lpos.z >= only_structures_min_z as i32;
if let Some(block) = if let Some(block) =
sampler.get_with_z_cache(wpos, Some(&z_cache), only_structures) sampler.get_with_z_cache(wpos, Some(&z_cache), only_structures, &self.index)
{ {
let _ = chunk.set(lpos, block); let _ = chunk.set(lpos, block);
} }
@ -163,10 +176,9 @@ impl World {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
// Apply site generation // Apply site generation
sim_chunk sim_chunk.sites.iter().for_each(|site| {
.sites self.index.sites[*site].apply_to(chunk_wpos2d, sample_get, &mut chunk)
.iter() });
.for_each(|site| site.apply_to(chunk_wpos2d, sample_get, &mut chunk));
// Apply paths // Apply paths
layer::apply_paths_to(chunk_wpos2d, sample_get, &mut chunk); layer::apply_paths_to(chunk_wpos2d, sample_get, &mut chunk);
@ -217,7 +229,12 @@ impl World {
// Apply site supplementary information // Apply site supplementary information
sim_chunk.sites.iter().for_each(|site| { sim_chunk.sites.iter().for_each(|site| {
site.apply_supplement(&mut rng, chunk_wpos2d, sample_get, &mut supplement) self.index.sites[*site].apply_supplement(
&mut rng,
chunk_wpos2d,
sample_get,
&mut supplement,
)
}); });
Ok((chunk, supplement)) Ok((chunk, supplement))

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
sim::{RiverKind, WorldSim, WORLD_SIZE}, sim::{RiverKind, WorldSim, WORLD_SIZE},
CONFIG, Index, CONFIG,
}; };
use common::{terrain::TerrainChunkSize, vol::RectVolSize}; use common::{terrain::TerrainChunkSize, vol::RectVolSize};
use std::{f32, f64}; use std::{f32, f64};
@ -114,6 +114,7 @@ impl MapConfig {
pub fn generate( pub fn generate(
&self, &self,
sampler: &WorldSim, sampler: &WorldSim,
index: &Index,
mut write_pixel: impl FnMut(Vec2<usize>, (u8, u8, u8, u8)), mut write_pixel: impl FnMut(Vec2<usize>, (u8, u8, u8, u8)),
) -> MapDebug { ) -> MapDebug {
let MapConfig { let MapConfig {
@ -170,7 +171,8 @@ impl MapConfig {
sample.river.river_kind, sample.river.river_kind,
sample.path.is_path(), sample.path.is_path(),
sample.sites.iter().any(|site| { sample.sites.iter().any(|site| {
site.get_origin() index.sites[*site]
.get_origin()
.distance_squared(pos * TerrainChunkSize::RECT_SIZE.x as i32) .distance_squared(pos * TerrainChunkSize::RECT_SIZE.x as i32)
< 64i32.pow(2) < 64i32.pow(2)
}), }),

View File

@ -28,7 +28,7 @@ use crate::{
civ::Place, civ::Place,
site::Site, site::Site,
util::{seed_expan, FastNoise, RandomField, StructureGen2d, LOCALITY, NEIGHBORS}, util::{seed_expan, FastNoise, RandomField, StructureGen2d, LOCALITY, NEIGHBORS},
CONFIG, Index, CONFIG,
}; };
use common::{ use common::{
assets, assets,
@ -1305,14 +1305,14 @@ impl WorldSim {
/// Draw a map of the world based on chunk information. Returns a buffer of /// Draw a map of the world based on chunk information. Returns a buffer of
/// u32s. /// u32s.
pub fn get_map(&self) -> Vec<u32> { pub fn get_map(&self, index: &Index) -> Vec<u32> {
let mut v = vec![0u32; WORLD_SIZE.x * WORLD_SIZE.y]; let mut v = vec![0u32; WORLD_SIZE.x * WORLD_SIZE.y];
// TODO: Parallelize again. // TODO: Parallelize again.
MapConfig { MapConfig {
gain: self.max_height, gain: self.max_height,
..MapConfig::default() ..MapConfig::default()
} }
.generate(&self, |pos, (r, g, b, a)| { .generate(&self, index, |pos, (r, g, b, a)| {
v[pos.y * WORLD_SIZE.x + pos.x] = u32::from_le_bytes([r, g, b, a]); v[pos.y * WORLD_SIZE.x + pos.x] = u32::from_le_bytes([r, g, b, a]);
}); });
v v
@ -1788,7 +1788,7 @@ pub struct SimChunk {
pub river: RiverData, pub river: RiverData,
pub warp_factor: f32, pub warp_factor: f32,
pub sites: Vec<Site>, pub sites: Vec<Id<Site>>,
pub place: Option<Id<Place>>, pub place: Option<Id<Place>>,
pub path: PathData, pub path: PathData,
pub contains_waypoint: bool, pub contains_waypoint: bool,

250
world/src/sim2/mod.rs Normal file
View File

@ -0,0 +1,250 @@
use crate::{
sim::WorldSim,
site::{
economy::{Good, Labor},
Site,
},
util::MapVec,
Index,
};
use common::store::Id;
use tracing::{debug, info, warn};
const MONTH: f32 = 30.0;
const YEAR: f32 = 12.0 * MONTH;
const TICK_PERIOD: f32 = 3.0 * MONTH; // 3 months
const HISTORY_DAYS: f32 = 500.0 * YEAR; // 500 years
pub fn simulate(index: &mut Index, world: &mut WorldSim) {
use std::io::Write;
let mut f = std::fs::File::create("economy.csv").unwrap();
write!(f, "Population,").unwrap();
for g in Good::list() {
write!(f, "{:?} Value,", g).unwrap();
}
for g in Good::list() {
write!(f, "{:?} Price,", g).unwrap();
}
for g in Good::list() {
write!(f, "{:?} Stock,", g).unwrap();
}
for g in Good::list() {
write!(f, "{:?} Surplus,", g).unwrap();
}
for l in Labor::list() {
write!(f, "{:?} Labor,", l).unwrap();
}
for l in Labor::list() {
write!(f, "{:?} Productivity,", l).unwrap();
}
writeln!(f, "").unwrap();
for i in 0..(HISTORY_DAYS / TICK_PERIOD) as i32 {
if (index.time / YEAR) as i32 % 50 == 0 && (index.time % YEAR) as i32 == 0 {
debug!("Year {}", (index.time / YEAR) as i32);
}
tick(index, world, TICK_PERIOD);
if i % 5 == 0 {
let site = index.sites.values().next().unwrap();
write!(f, "{},", site.economy.pop).unwrap();
for g in Good::list() {
write!(f, "{:?},", site.economy.values[*g].unwrap_or(-1.0)).unwrap();
}
for g in Good::list() {
write!(f, "{:?},", site.economy.prices[*g]).unwrap();
}
for g in Good::list() {
write!(f, "{:?},", site.economy.stocks[*g]).unwrap();
}
for g in Good::list() {
write!(f, "{:?},", site.economy.marginal_surplus[*g]).unwrap();
}
for l in Labor::list() {
write!(f, "{:?},", site.economy.labors[*l] * site.economy.pop).unwrap();
}
for l in Labor::list() {
write!(f, "{:?},", site.economy.productivity[*l]).unwrap();
}
writeln!(f, "").unwrap();
}
}
}
pub fn tick(index: &mut Index, world: &mut WorldSim, dt: f32) {
for site in index.sites.ids() {
tick_site_economy(index, site, dt);
}
index.time += dt;
}
/// Simulate a site's economy. This simulation is roughly equivalent to the
/// Lange-Lerner model's solution to the socialist calculation problem. The
/// simulation begins by assigning arbitrary values to each commodity and then
/// incrementally updates them according to the final scarcity of the commodity
/// at the end of the tick. This results in the formulation of values that are
/// roughly analgous to prices for each commodity. The workforce is then
/// reassigned according to the respective commodity values. The simulation also
/// includes damping terms that prevent cyclical inconsistencies in value
/// rationalisation magnifying enough to crash the economy. We also ensure that
/// a small number of workers are allocated to every industry (even inactive
/// ones) each tick. This is not an accident: a small amount of productive
/// capacity in one industry allows the economy to quickly pivot to a different
/// prodution configuration should an additional commodity that acts as
/// production input become available. This means that the economy will
/// dynamically react to environmental changes. If a product becomes available
/// through a mechanism such as trade, an entire arm of the economy may
/// materialise to take advantage of this.
pub fn tick_site_economy(index: &mut Index, site: Id<Site>, dt: f32) {
let site = &mut index.sites[site];
let orders = site.economy.get_orders();
let productivity = site.economy.get_productivity();
let mut demand = MapVec::from_default(0.0);
for (labor, orders) in &orders {
let scale = if let Some(labor) = labor {
site.economy.labors[*labor]
} else {
1.0
} * site.economy.pop;
for (good, amount) in orders {
demand[*good] += *amount * scale;
}
}
let mut supply = MapVec::from_default(0.0);
for (labor, (output_good, _)) in productivity.iter() {
supply[*output_good] +=
site.economy.yields[labor] * site.economy.labors[labor] * site.economy.pop;
}
let stocks = &site.economy.stocks;
site.economy.surplus = demand
.clone()
.map(|g, demand| supply[g] + stocks[g] - demand);
site.economy.marginal_surplus = demand.clone().map(|g, demand| supply[g] - demand);
// Update values according to the surplus of each stock
// Note that values are used for workforce allocation and are not the same thing
// as price
let values = &mut site.economy.values;
let marginal_surplus = &site.economy.marginal_surplus;
let stocks = &site.economy.stocks;
site.economy.surplus.iter().for_each(|(good, surplus)| {
// Value rationalisation
let val = 2.0f32.powf(1.0 - *surplus / demand[good]);
let smooth = 0.8;
values[good] = if val > 0.001 && val < 1000.0 {
Some(smooth * values[good].unwrap_or(val) + (1.0 - smooth) * val)
} else {
None
};
});
site.economy.prices = site.economy.stocks.clone().map(|g, stock| {
// Price rationalisation
demand[g] / (supply[g] + stocks[g])
});
// Update export targets based on relative values
let value_avg = values
.iter()
.map(|(_, v)| (*v).unwrap_or(0.0))
.sum::<f32>()
.max(0.01)
/ values.iter().filter(|(_, v)| v.is_some()).count() as f32;
//let export_targets = &mut site.economy.export_targets;
//let last_exports = &self.last_exports;
// site.economy.values.iter().for_each(|(stock, value)| {
// let rvalue = (*value).map(|v| v - value_avg).unwrap_or(0.0);
// //let factor = if export_targets[stock] > 0.0 { 1.0 / rvalue } else {
// rvalue }; //export_targets[stock] = last_exports[stock] - rvalue *
// 0.1; // + (trade_states[stock].sell_belief.price -
// trade_states[stock].buy_belief.price) * 0.025; });
//let pop = site.economy.pop;
// Redistribute workforce according to relative good values
let labor_ratios = productivity.clone().map(|labor, (output_good, _)| {
site.economy.values[output_good].unwrap_or(0.0) * site.economy.productivity[labor]
//* demand[output_good] / supply[output_good].max(0.001)
});
let labor_ratio_sum = labor_ratios.iter().map(|(_, r)| *r).sum::<f32>().max(0.01);
productivity.iter().for_each(|(labor, _)| {
let smooth = 0.8;
site.economy.labors[labor] = smooth * site.economy.labors[labor]
+ (1.0 - smooth)
* (labor_ratios[labor].max(labor_ratio_sum / 1000.0) / labor_ratio_sum);
});
// Production
let stocks_before = site.economy.stocks.clone();
for (labor, orders) in orders.iter() {
let scale = if let Some(labor) = labor {
site.economy.labors[*labor]
} else {
1.0
} * site.economy.pop;
// For each order, we try to find the minimum satisfaction rate - this limits
// how much we can produce! For example, if we need 0.25 fish and
// 0.75 oats to make 1 unit of food, but only 0.5 units of oats are
// available then we only need to consume 2/3rds
// of other ingredients and leave the rest in stock
// In effect, this is the productivity
let labor_productivity = orders
.iter()
.map(|(good, amount)| {
// What quantity is this order requesting?
let _quantity = *amount * scale;
// What proportion of this order is the economy able to satisfy?
let satisfaction = (stocks_before[*good] / demand[*good]).min(1.0);
satisfaction
})
.min_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap_or_else(|| panic!("Industry {:?} requires at least one input order", labor));
for (good, amount) in orders {
// What quantity is this order requesting?
let quantity = *amount * scale;
// What amount gets actually used in production?
let used = quantity * labor_productivity;
// Deplete stocks accordingly
site.economy.stocks[*good] = (site.economy.stocks[*good] - used).max(0.0);
}
// Industries produce things
if let Some(labor) = labor {
let (stock, rate) = productivity[*labor];
let workers = site.economy.labors[*labor] * site.economy.pop;
let final_rate = rate;
let yield_per_worker = labor_productivity * final_rate;
site.economy.yields[*labor] = yield_per_worker;
site.economy.productivity[*labor] = labor_productivity;
site.economy.stocks[stock] += yield_per_worker * workers.powf(1.1);
}
}
// Decay stocks
site.economy
.stocks
.iter_mut()
.for_each(|(c, v)| *v *= 1.0 - c.decay_rate());
// Decay stocks
site.economy.replenish(index.time);
// Births/deaths
const NATURAL_BIRTH_RATE: f32 = 0.05;
const DEATH_RATE: f32 = 0.005;
let birth_rate = if site.economy.surplus[Good::Food] > 0.0 {
NATURAL_BIRTH_RATE
} else {
0.0
};
site.economy.pop += dt / YEAR * site.economy.pop * (birth_rate - DEATH_RATE);
}

View File

@ -0,0 +1,39 @@
use common::{terrain::Block, vol::Vox};
#[derive(Copy, Clone)]
pub struct BlockMask {
block: Block,
priority: i32,
}
impl BlockMask {
pub fn new(block: Block, priority: i32) -> Self { Self { block, priority } }
pub fn nothing() -> Self {
Self {
block: Block::empty(),
priority: 0,
}
}
pub fn with_priority(mut self, priority: i32) -> Self {
self.priority = priority;
self
}
pub fn resolve_with(self, other: Self) -> Self {
if self.priority >= other.priority {
self
} else {
other
}
}
pub fn finish(self) -> Option<Block> {
if self.priority > 0 {
Some(self.block)
} else {
None
}
}
}

View File

@ -305,7 +305,7 @@ impl Floor {
this.create_rooms(ctx, level, 7); this.create_rooms(ctx, level, 7);
// Create routes between all rooms // Create routes between all rooms
let room_areas = this.rooms.iter().map(|r| r.area).collect::<Vec<_>>(); let room_areas = this.rooms.values().map(|r| r.area).collect::<Vec<_>>();
for a in room_areas.iter() { for a in room_areas.iter() {
for b in room_areas.iter() { for b in room_areas.iter() {
this.create_route(ctx, a.center(), b.center()); this.create_route(ctx, a.center(), b.center());
@ -342,7 +342,7 @@ impl Floor {
// Ensure no overlap // Ensure no overlap
if self if self
.rooms .rooms
.iter() .values()
.any(|r| r.area.collides_with_rect(area_border)) .any(|r| r.area.collides_with_rect(area_border))
{ {
return None; return None;

145
world/src/site/economy.rs Normal file
View File

@ -0,0 +1,145 @@
use crate::util::{DHashMap, MapVec};
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Good {
Wheat = 0,
Flour = 1,
Meat = 2,
Fish = 3,
Game = 4,
Food = 5,
Logs = 6,
Wood = 7,
Rock = 8,
Stone = 9,
}
use Good::*;
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Labor {
Farmer = 0,
Lumberjack = 1,
Miner = 2,
Fisher = 3,
Hunter = 4,
Cook = 5,
}
use Labor::*;
pub struct Economy {
pub pop: f32,
pub stocks: MapVec<Good, f32>,
pub surplus: MapVec<Good, f32>,
pub marginal_surplus: MapVec<Good, f32>,
pub values: MapVec<Good, Option<f32>>,
pub prices: MapVec<Good, f32>,
pub labors: MapVec<Labor, f32>,
pub yields: MapVec<Labor, f32>,
pub productivity: MapVec<Labor, f32>,
}
impl Default for Economy {
fn default() -> Self {
Self {
pop: 32.0,
stocks: Default::default(),
surplus: Default::default(),
marginal_surplus: Default::default(),
values: Default::default(),
prices: Default::default(),
labors: Default::default(),
yields: Default::default(),
productivity: Default::default(),
}
}
}
impl Economy {
pub fn get_orders(&self) -> DHashMap<Option<Labor>, Vec<(Good, f32)>> {
vec![
(None, vec![(Food, 0.5)]),
(Some(Cook), vec![
(Flour, 12.0),
(Meat, 4.0),
(Wood, 1.5),
(Stone, 1.0),
]),
(Some(Lumberjack), vec![(Logs, 0.5)]),
(Some(Miner), vec![(Rock, 0.5)]),
(Some(Fisher), vec![(Fish, 4.0)]),
(Some(Hunter), vec![(Game, 1.0)]),
(Some(Farmer), vec![(Wheat, 2.0)]),
]
.into_iter()
.collect()
}
pub fn get_productivity(&self) -> MapVec<Labor, (Good, f32)> {
// Per labourer, per year
MapVec::from_list(
&[
(Farmer, (Flour, 2.0)),
(Lumberjack, (Wood, 0.5)),
(Miner, (Stone, 0.5)),
(Fisher, (Meat, 3.0)),
(Hunter, (Meat, 1.0)),
(Cook, (Food, 16.0)),
],
(Rock, 0.0),
)
.map(|l, (good, v)| (good, v * (1.0 + self.labors[l])))
}
pub fn replenish(&mut self, time: f32) {
use rand::Rng;
for (i, (g, v)) in [(Wheat, 195.0), (Logs, 120.0), (Rock, 120.0), (Game, 20.0)]
.iter()
.enumerate()
{
self.stocks[*g] = (*v
* (1.25 + (((time * 0.0001 + i as f32).sin() + 1.0) % 1.0) * 0.5)
- self.stocks[*g])
* 0.075; //rand::thread_rng().gen_range(0.05, 0.1);
}
}
}
impl Default for Good {
fn default() -> Self {
Good::Rock // Arbitrary
}
}
impl Good {
pub fn list() -> &'static [Self] {
static GOODS: [Good; 10] = [
Wheat, Flour, Meat, Fish, Game, Food, Logs, Wood, Rock, Stone,
];
&GOODS
}
pub fn decay_rate(&self) -> f32 {
match self {
Food => 0.2,
Wheat => 0.1,
Meat => 0.25,
Fish => 0.2,
_ => 0.0,
}
}
}
impl Labor {
pub fn list() -> &'static [Self] {
static LABORS: [Labor; 6] = [Farmer, Lumberjack, Miner, Fisher, Hunter, Cook];
&LABORS
}
}

View File

@ -1,8 +1,10 @@
mod block_mask;
mod dungeon; mod dungeon;
pub mod economy;
mod settlement; mod settlement;
// Reexports // Reexports
pub use self::{dungeon::Dungeon, settlement::Settlement}; pub use self::{block_mask::BlockMask, dungeon::Dungeon, economy::Economy, settlement::Settlement};
use crate::column::ColumnSample; use crate::column::ColumnSample;
use common::{ use common::{
@ -14,44 +16,6 @@ use rand::Rng;
use std::{fmt, sync::Arc}; use std::{fmt, sync::Arc};
use vek::*; use vek::*;
#[derive(Copy, Clone)]
pub struct BlockMask {
block: Block,
priority: i32,
}
impl BlockMask {
pub fn new(block: Block, priority: i32) -> Self { Self { block, priority } }
pub fn nothing() -> Self {
Self {
block: Block::empty(),
priority: 0,
}
}
pub fn with_priority(mut self, priority: i32) -> Self {
self.priority = priority;
self
}
pub fn resolve_with(self, other: Self) -> Self {
if self.priority >= other.priority {
self
} else {
other
}
}
pub fn finish(self) -> Option<Block> {
if self.priority > 0 {
Some(self.block)
} else {
None
}
}
}
pub struct SpawnRules { pub struct SpawnRules {
pub trees: bool, pub trees: bool,
} }
@ -60,31 +24,49 @@ impl Default for SpawnRules {
fn default() -> Self { Self { trees: true } } fn default() -> Self { Self { trees: true } }
} }
#[derive(Clone)] pub struct Site {
pub enum Site { pub kind: SiteKind,
Settlement(Arc<Settlement>), pub economy: Economy,
Dungeon(Arc<Dungeon>), }
pub enum SiteKind {
Settlement(Settlement),
Dungeon(Dungeon),
} }
impl Site { impl Site {
pub fn settlement(s: Settlement) -> Self {
Self {
kind: SiteKind::Settlement(s),
economy: Economy::default(),
}
}
pub fn dungeon(d: Dungeon) -> Self {
Self {
kind: SiteKind::Dungeon(d),
economy: Economy::default(),
}
}
pub fn radius(&self) -> f32 { pub fn radius(&self) -> f32 {
match self { match &self.kind {
Site::Settlement(settlement) => settlement.radius(), SiteKind::Settlement(settlement) => settlement.radius(),
Site::Dungeon(dungeon) => dungeon.radius(), SiteKind::Dungeon(dungeon) => dungeon.radius(),
} }
} }
pub fn get_origin(&self) -> Vec2<i32> { pub fn get_origin(&self) -> Vec2<i32> {
match self { match &self.kind {
Site::Settlement(s) => s.get_origin(), SiteKind::Settlement(s) => s.get_origin(),
Site::Dungeon(d) => d.get_origin(), SiteKind::Dungeon(d) => d.get_origin(),
} }
} }
pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules { pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
match self { match &self.kind {
Site::Settlement(s) => s.spawn_rules(wpos), SiteKind::Settlement(s) => s.spawn_rules(wpos),
Site::Dungeon(d) => d.spawn_rules(wpos), SiteKind::Dungeon(d) => d.spawn_rules(wpos),
} }
} }
@ -94,9 +76,9 @@ impl Site {
get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>, get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol), vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
) { ) {
match self { match &self.kind {
Site::Settlement(settlement) => settlement.apply_to(wpos2d, get_column, vol), SiteKind::Settlement(settlement) => settlement.apply_to(wpos2d, get_column, vol),
Site::Dungeon(dungeon) => dungeon.apply_to(wpos2d, get_column, vol), SiteKind::Dungeon(dungeon) => dungeon.apply_to(wpos2d, get_column, vol),
} }
} }
@ -107,28 +89,13 @@ impl Site {
get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>, get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
supplement: &mut ChunkSupplement, supplement: &mut ChunkSupplement,
) { ) {
match self { match &self.kind {
Site::Settlement(settlement) => { SiteKind::Settlement(settlement) => {
settlement.apply_supplement(rng, wpos2d, get_column, supplement) settlement.apply_supplement(rng, wpos2d, get_column, supplement)
}, },
Site::Dungeon(dungeon) => dungeon.apply_supplement(rng, wpos2d, get_column, supplement), SiteKind::Dungeon(dungeon) => {
} dungeon.apply_supplement(rng, wpos2d, get_column, supplement)
} },
}
impl From<Settlement> for Site {
fn from(settlement: Settlement) -> Self { Site::Settlement(Arc::new(settlement)) }
}
impl From<Dungeon> for Site {
fn from(dungeon: Dungeon) -> Self { Site::Dungeon(Arc::new(dungeon)) }
}
impl fmt::Debug for Site {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Site::Settlement(_) => write!(f, "Settlement"),
Site::Dungeon(_) => write!(f, "Dungeon"),
} }
} }
} }

View File

@ -12,7 +12,7 @@ use common::{
use rand::prelude::*; use rand::prelude::*;
use vek::*; use vek::*;
const COLOR_THEMES: [Rgb<u8>; 11] = [ const COLOR_THEMES: [Rgb<u8>; 17] = [
Rgb::new(0x1D, 0x4D, 0x45), Rgb::new(0x1D, 0x4D, 0x45),
Rgb::new(0xB3, 0x7D, 0x60), Rgb::new(0xB3, 0x7D, 0x60),
Rgb::new(0xAC, 0x5D, 0x26), Rgb::new(0xAC, 0x5D, 0x26),
@ -24,6 +24,12 @@ const COLOR_THEMES: [Rgb<u8>; 11] = [
Rgb::new(0x2F, 0x32, 0x47), Rgb::new(0x2F, 0x32, 0x47),
Rgb::new(0x8F, 0x35, 0x43), Rgb::new(0x8F, 0x35, 0x43),
Rgb::new(0x6D, 0x1E, 0x3A), Rgb::new(0x6D, 0x1E, 0x3A),
Rgb::new(0x6D, 0xA7, 0x80),
Rgb::new(0x4F, 0xA0, 0x95),
Rgb::new(0xE2, 0xB9, 0x99),
Rgb::new(0x7A, 0x30, 0x22),
Rgb::new(0x4A, 0x06, 0x08),
Rgb::new(0x8E, 0xB4, 0x57),
]; ];
pub struct House { pub struct House {
@ -149,10 +155,7 @@ impl Archetype for House {
}; };
let this = Self { let this = Self {
roof_color: COLOR_THEMES roof_color: *COLOR_THEMES.choose(rng).unwrap(),
.choose(rng)
.unwrap()
.map(|e| e.saturating_add(rng.gen_range(0, 20)) - 10),
noise: RandomField::new(rng.gen()), noise: RandomField::new(rng.gen()),
roof_ribbing: rng.gen(), roof_ribbing: rng.gen(),
roof_ribbing_diagonal: rng.gen(), roof_ribbing_diagonal: rng.gen(),

View File

@ -9,27 +9,38 @@ use vek::*;
pub struct Keep; pub struct Keep;
pub struct Attr {
height: i32,
is_tower: bool,
}
impl Archetype for Keep { impl Archetype for Keep {
type Attr = (); type Attr = Attr;
fn generate<R: Rng>(rng: &mut R) -> (Self, Skeleton<Self::Attr>) { fn generate<R: Rng>(rng: &mut R) -> (Self, Skeleton<Self::Attr>) {
let len = rng.gen_range(-8, 20).max(0); let len = rng.gen_range(-8, 24).max(0);
let skel = Skeleton { let skel = Skeleton {
offset: -rng.gen_range(0, len + 7).clamped(0, len), offset: -rng.gen_range(0, len + 7).clamped(0, len),
ori: if rng.gen() { Ori::East } else { Ori::North }, ori: if rng.gen() { Ori::East } else { Ori::North },
root: Branch { root: Branch {
len, len,
attr: Self::Attr::default(), attr: Attr {
locus: 6 + rng.gen_range(0, 5), height: rng.gen_range(12, 16),
is_tower: false,
},
locus: 10 + rng.gen_range(0, 5),
border: 3, border: 3,
children: (0..1) children: (0..1)
.map(|_| { .map(|_| {
( (
rng.gen_range(-5, len + 5).clamped(0, len.max(1) - 1), rng.gen_range(-5, len + 5).clamped(0, len.max(1) - 1),
Branch { Branch {
len: rng.gen_range(5, 12) * if rng.gen() { 1 } else { -1 }, len: 0,
attr: Self::Attr::default(), attr: Attr {
locus: 5 + rng.gen_range(0, 3), height: rng.gen_range(20, 28),
is_tower: true,
},
locus: 4 + rng.gen_range(0, 5),
border: 3, border: 3,
children: Vec::new(), children: Vec::new(),
}, },
@ -48,7 +59,7 @@ impl Archetype for Keep {
pos: Vec3<i32>, pos: Vec3<i32>,
dist: i32, dist: i32,
bound_offset: Vec2<i32>, bound_offset: Vec2<i32>,
_center_offset: Vec2<i32>, center_offset: Vec2<i32>,
z: i32, z: i32,
ori: Ori, ori: Ori,
branch: &Branch<Self::Attr>, branch: &Branch<Self::Attr>,
@ -60,18 +71,24 @@ impl Archetype for Keep {
let important_layer = normal_layer + 1; let important_layer = normal_layer + 1;
let internal_layer = important_layer + 1; let internal_layer = important_layer + 1;
let make_block = let make_block = |r, g, b| {
|r, g, b| BlockMask::new(Block::new(BlockKind::Normal, Rgb::new(r, g, b)), normal_layer); BlockMask::new(
Block::new(BlockKind::Normal, Rgb::new(r, g, b)),
normal_layer,
)
};
let foundation = make_block(100, 100, 100); let foundation = make_block(100, 100, 100);
let wall = make_block(100, 100, 110); let wall = make_block(100, 100, 110);
let floor = make_block(120, 80, 50).with_priority(important_layer); let floor = make_block(120, 80, 50).with_priority(important_layer);
let pole = make_block(90, 70, 50).with_priority(important_layer);
let flag = make_block(50, 170, 100).with_priority(important_layer);
let internal = BlockMask::new(Block::empty(), internal_layer); let internal = BlockMask::new(Block::empty(), internal_layer);
let empty = BlockMask::nothing(); let empty = BlockMask::nothing();
let width = branch.locus; let width = branch.locus;
let rampart_width = 2 + branch.locus; let rampart_width = 2 + branch.locus;
let ceil_height = 12; let ceil_height = branch.attr.height;
let door_height = 6; let door_height = 6;
let edge_pos = if (bound_offset.x == rampart_width) ^ (ori == Ori::East) { let edge_pos = if (bound_offset.x == rampart_width) ^ (ori == Ori::East) {
pos.y pos.y
@ -79,26 +96,49 @@ impl Archetype for Keep {
pos.x pos.x
}; };
let rampart_height = ceil_height + if edge_pos % 2 == 0 { 3 } else { 4 }; let rampart_height = ceil_height + if edge_pos % 2 == 0 { 3 } else { 4 };
let min_dist = bound_offset.reduce_max(); let inner = Clamp::clamp(
center_offset,
Vec2::new(-5, -branch.len / 2 - 5),
Vec2::new(5, branch.len / 2 + 5),
);
let min_dist = bound_offset.map(|e| e.pow(2) as f32).sum().powf(0.5) as i32; //(bound_offset.distance_squared(inner) as f32).sqrt() as i32 + 5;//bound_offset.reduce_max();
if profile.y <= 0 - (min_dist - width - 1).max(0) && min_dist < width + 3 { if profile.y <= 0 - (min_dist - width - 1).max(0) && min_dist < width + 3 {
// Foundations // Foundations
foundation foundation
} else if profile.y == ceil_height && min_dist < rampart_width { } else if profile.y == ceil_height && min_dist < rampart_width {
if min_dist < width { if min_dist < width { floor } else { wall }
floor } else if !branch.attr.is_tower
} else { && bound_offset.x.abs() == 4
wall && min_dist == width + 1
} && profile.y < ceil_height
} else if bound_offset.x.abs() == 4 && min_dist == width + 1 && profile.y < ceil_height { {
wall wall
} else if bound_offset.x.abs() < 3 && profile.y < door_height - bound_offset.x.abs() && profile.y > 0 { } else if bound_offset.x.abs() < 3
&& profile.y < door_height - bound_offset.x.abs()
&& profile.y > 0
{
internal internal
} else if min_dist == width && profile.y <= ceil_height { } else if min_dist == width && profile.y <= ceil_height {
wall wall
} else if profile.y >= ceil_height { } else if profile.y >= ceil_height {
if profile.y > ceil_height && min_dist < rampart_width { if profile.y > ceil_height && min_dist < rampart_width {
internal if branch.attr.is_tower
&& center_offset == Vec2::zero()
&& profile.y < ceil_height + 16
{
pole
} else if branch.attr.is_tower
&& center_offset.x == 0
&& center_offset.y > 0
&& center_offset.y < 8
&& profile.y > ceil_height + 8
&& profile.y < ceil_height + 14
{
flag
} else {
empty
}
} else if min_dist == rampart_width { } else if min_dist == rampart_width {
if profile.y < rampart_height { if profile.y < rampart_height {
wall wall

View File

@ -12,7 +12,6 @@ use vek::*;
pub type HouseBuilding = Building<archetype::house::House>; pub type HouseBuilding = Building<archetype::house::House>;
pub type KeepBuilding = Building<archetype::keep::Keep>; pub type KeepBuilding = Building<archetype::keep::Keep>;
pub struct Building<A: Archetype> { pub struct Building<A: Archetype> {
skel: Skeleton<A::Attr>, skel: Skeleton<A::Attr>,
archetype: A, archetype: A,
@ -44,7 +43,7 @@ impl<A: Archetype> Building<A> {
let aabr = self.bounds_2d(); let aabr = self.bounds_2d();
Aabb { Aabb {
min: Vec3::from(aabr.min) + Vec3::unit_z() * (self.origin.z - 8), min: Vec3::from(aabr.min) + Vec3::unit_z() * (self.origin.z - 8),
max: Vec3::from(aabr.max) + Vec3::unit_z() * (self.origin.z + 32), max: Vec3::from(aabr.max) + Vec3::unit_z() * (self.origin.z + 48),
} }
} }

View File

@ -1,6 +1,10 @@
mod building; mod building;
mod town;
use self::building::{HouseBuilding, KeepBuilding}; use self::{
building::{HouseBuilding, KeepBuilding},
town::{District, Town},
};
use super::SpawnRules; use super::SpawnRules;
use crate::{ use crate::{
column::ColumnSample, column::ColumnSample,
@ -84,7 +88,6 @@ fn to_tile(e: i32) -> i32 { ((e as f32).div_euclid(AREA_SIZE as f32)).floor() as
pub enum StructureKind { pub enum StructureKind {
House(HouseBuilding), House(HouseBuilding),
Keep(KeepBuilding), Keep(KeepBuilding),
} }
pub struct Structure { pub struct Structure {
@ -96,7 +99,6 @@ impl Structure {
match &self.kind { match &self.kind {
StructureKind::House(house) => house.bounds_2d(), StructureKind::House(house) => house.bounds_2d(),
StructureKind::Keep(keep) => keep.bounds_2d(), StructureKind::Keep(keep) => keep.bounds_2d(),
} }
} }
@ -104,7 +106,6 @@ impl Structure {
match &self.kind { match &self.kind {
StructureKind::House(house) => house.bounds(), StructureKind::House(house) => house.bounds(),
StructureKind::Keep(keep) => keep.bounds(), StructureKind::Keep(keep) => keep.bounds(),
} }
} }
@ -112,7 +113,6 @@ impl Structure {
match &self.kind { match &self.kind {
StructureKind::House(house) => house.sample(rpos), StructureKind::House(house) => house.sample(rpos),
StructureKind::Keep(keep) => keep.sample(rpos), StructureKind::Keep(keep) => keep.sample(rpos),
} }
} }
} }
@ -127,10 +127,6 @@ pub struct Settlement {
noise: RandomField, noise: RandomField,
} }
pub struct Town {
base_tile: Vec2<i32>,
}
pub struct Farm { pub struct Farm {
#[allow(dead_code)] #[allow(dead_code)]
base_tile: Vec2<i32>, base_tile: Vec2<i32>,
@ -280,12 +276,28 @@ impl Settlement {
Some(Plot::Dirt) => true, Some(Plot::Dirt) => true,
_ => false, _ => false,
}) { }) {
self.land // self.land
.plot_at_mut(base_tile) // .plot_at_mut(base_tile)
.map(|plot| *plot = Plot::Town); // .map(|plot| *plot = Plot::Town { district: None });
if i == 0 { if i == 0 {
self.town = Some(Town { base_tile }); let town = Town::generate(self.origin, base_tile, ctx);
for (id, district) in town.districts().iter() {
let district_plot =
self.land.plots.insert(Plot::Town { district: Some(id) });
for x in district.aabr.min.x..district.aabr.max.x {
for y in district.aabr.min.y..district.aabr.max.y {
if !matches!(self.land.plot_at(Vec2::new(x, y)), Some(Plot::Hazard))
{
self.land.set(Vec2::new(x, y), district_plot);
}
}
}
}
self.town = Some(town);
origin = base_tile; origin = base_tile;
} }
} }
@ -346,27 +358,25 @@ impl Settlement {
return; return;
}; };
for (i, tile) in Spiral2d::new() for tile in Spiral2d::new()
.map(|offs| town_center + offs) .map(|offs| town_center + offs)
.take(16usize.pow(2)) .take(16usize.pow(2))
.enumerate()
{ {
// This is a stupid way to decide how to place buildings // This is a stupid way to decide how to place buildings
for _ in 0..ctx.rng.gen_range(2, 5) { for i in 0..ctx.rng.gen_range(2, 5) {
for _ in 0..25 { for _ in 0..25 {
let house_pos = tile.map(|e| e * AREA_SIZE as i32 + AREA_SIZE as i32 / 2) let house_pos = tile.map(|e| e * AREA_SIZE as i32 + AREA_SIZE as i32 / 2)
+ Vec2::<i32>::zero().map(|_| { + Vec2::<i32>::zero().map(|_| {
ctx.rng ctx.rng
.gen_range(-(AREA_SIZE as i32) / 2, AREA_SIZE as i32 / 2) .gen_range(-(AREA_SIZE as i32) / 4, AREA_SIZE as i32 / 4)
}); });
let tile_pos = house_pos.map(|e| e.div_euclid(AREA_SIZE as i32)); let tile_pos = house_pos.map(|e| e.div_euclid(AREA_SIZE as i32));
if !matches!(self.land.plot_at(tile_pos), Some(Plot::Town)) if self
|| self .land
.land .tile_at(tile_pos)
.tile_at(tile_pos) .map(|t| t.contains(WayKind::Path))
.map(|t| t.contains(WayKind::Path)) .unwrap_or(true)
.unwrap_or(true)
|| ctx || ctx
.sim .sim
.and_then(|sim| sim.get_nearest_path(self.origin + house_pos)) .and_then(|sim| sim.get_nearest_path(self.origin + house_pos))
@ -376,30 +386,30 @@ impl Settlement {
continue; continue;
} }
let alt = if let Some(Plot::Town { district }) = self.land.plot_at(tile_pos) {
district
.and_then(|d| self.town.as_ref().map(|t| t.districts().get(d)))
.map(|d| d.alt)
.unwrap_or_else(|| {
ctx.sim
.and_then(|sim| sim.get_alt_approx(self.origin + house_pos))
.unwrap_or(0.0)
.ceil() as i32
})
} else {
continue;
};
let structure = Structure { let structure = Structure {
kind: if i == 0 { kind: if tile == town_center && i == 0 {
StructureKind::Keep(KeepBuilding::generate( StructureKind::Keep(KeepBuilding::generate(
ctx.rng, ctx.rng,
Vec3::new( Vec3::new(house_pos.x, house_pos.y, alt),
house_pos.x,
house_pos.y,
ctx.sim
.and_then(|sim| sim.get_alt_approx(self.origin + house_pos))
.unwrap_or(0.0)
.ceil() as i32,
),
)) ))
} else { } else {
StructureKind::House(HouseBuilding::generate( StructureKind::House(HouseBuilding::generate(
ctx.rng, ctx.rng,
Vec3::new( Vec3::new(house_pos.x, house_pos.y, alt),
house_pos.x,
house_pos.y,
ctx.sim
.and_then(|sim| sim.get_alt_approx(self.origin + house_pos))
.unwrap_or(0.0)
.ceil() as i32,
),
)) ))
}, },
}; };
@ -536,12 +546,13 @@ impl Settlement {
} else { } else {
continue; continue;
}; };
let surface_z = col_sample.riverless_alt.floor() as i32; let land_surface_z = col_sample.riverless_alt.floor() as i32;
let mut surface_z = land_surface_z;
// Sample settlement // Sample settlement
let sample = self.land.get_at_block(rpos); let sample = self.land.get_at_block(rpos);
let noisy_color = |col: Rgb<u8>, factor: u32| { let noisy_color = move |col: Rgb<u8>, factor: u32| {
let nz = self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, surface_z)); let nz = self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, surface_z));
col.map(|e| { col.map(|e| {
(e as u32 + nz % (factor * 2)) (e as u32 + nz % (factor * 2))
@ -550,6 +561,30 @@ impl Settlement {
}) })
}; };
// District alt
if let Some(Plot::Town { district }) = sample.plot {
if let Some(d) =
district.and_then(|d| self.town.as_ref().map(|t| t.districts().get(d)))
{
let other = self
.land
.plot_at(sample.second_closest)
.and_then(|p| match p {
Plot::Town { district } => *district,
_ => None,
})
.and_then(|d| {
self.town.as_ref().map(|t| t.districts().get(d).alt as f32)
})
.unwrap_or(surface_z as f32);
surface_z = Lerp::lerp(
(other + d.alt as f32) / 2.0,
d.alt as f32,
(1.25 * sample.edge_dist / (d.alt as f32 - other).abs()).min(1.0),
) as i32;
}
}
// Paths // Paths
if let Some((WayKind::Path, dist, nearest)) = sample.way { if let Some((WayKind::Path, dist, nearest)) = sample.way {
let inset = -1; let inset = -1;
@ -599,7 +634,7 @@ impl Settlement {
Some(Plot::Dirt) => Some(Rgb::new(90, 70, 50)), Some(Plot::Dirt) => Some(Rgb::new(90, 70, 50)),
Some(Plot::Grass) => Some(Rgb::new(100, 200, 0)), Some(Plot::Grass) => Some(Rgb::new(100, 200, 0)),
Some(Plot::Water) => Some(Rgb::new(100, 150, 250)), Some(Plot::Water) => Some(Rgb::new(100, 150, 250)),
Some(Plot::Town) => { Some(Plot::Town { district }) => {
if let Some((_, path_nearest)) = col_sample.path { if let Some((_, path_nearest)) = col_sample.path {
let path_dir = (path_nearest - wpos2d.map(|e| e as f32)) let path_dir = (path_nearest - wpos2d.map(|e| e as f32))
.rotated_z(f32::consts::PI / 2.0) .rotated_z(f32::consts::PI / 2.0)
@ -693,7 +728,8 @@ impl Settlement {
if let Some(color) = color { if let Some(color) = color {
if col_sample.water_dist.map(|dist| dist > 2.0).unwrap_or(true) { if col_sample.water_dist.map(|dist| dist > 2.0).unwrap_or(true) {
for z in -8..3 { let diff = (surface_z - land_surface_z).abs();
for z in -8 - diff..3 + diff {
let pos = Vec3::new(offs.x, offs.y, surface_z + z); let pos = Vec3::new(offs.x, offs.y, surface_z + z);
if let (0, Some(block)) = (z, surface_block) { if let (0, Some(block)) = (z, surface_block) {
@ -767,7 +803,8 @@ impl Settlement {
for x in bounds.min.x..bounds.max.x + 1 { for x in bounds.min.x..bounds.max.x + 1 {
for y in bounds.min.y..bounds.max.y + 1 { for y in bounds.min.y..bounds.max.y + 1 {
let col = if let Some(col) = get_column(self.origin + Vec2::new(x, y) - wpos2d) { let col = if let Some(col) = get_column(self.origin + Vec2::new(x, y) - wpos2d)
{
col col
} else { } else {
continue; continue;
@ -813,7 +850,7 @@ impl Settlement {
let entity_wpos = Vec3::new(wpos2d.x as f32, wpos2d.y as f32, col_sample.alt + 3.0); let entity_wpos = Vec3::new(wpos2d.x as f32, wpos2d.y as f32, col_sample.alt + 3.0);
if matches!(sample.plot, Some(Plot::Town)) if matches!(sample.plot, Some(Plot::Town { .. }))
&& RandomField::new(self.seed).chance(Vec3::from(wpos2d), 1.0 / (50.0 * 50.0)) && RandomField::new(self.seed).chance(Vec3::from(wpos2d), 1.0 / (50.0 * 50.0))
{ {
let is_human: bool; let is_human: bool;
@ -903,7 +940,7 @@ impl Settlement {
Some(Plot::Dirt) => return Some(Rgb::new(90, 70, 50)), Some(Plot::Dirt) => return Some(Rgb::new(90, 70, 50)),
Some(Plot::Grass) => return Some(Rgb::new(100, 200, 0)), Some(Plot::Grass) => return Some(Rgb::new(100, 200, 0)),
Some(Plot::Water) => return Some(Rgb::new(100, 150, 250)), Some(Plot::Water) => return Some(Rgb::new(100, 150, 250)),
Some(Plot::Town) => { Some(Plot::Town { .. }) => {
return Some(Rgb::new(150, 110, 60).map2(Rgb::iota(), |e: u8, i: i32| { return Some(Rgb::new(150, 110, 60).map2(Rgb::iota(), |e: u8, i: i32| {
e.saturating_add((self.noise.get(Vec3::new(pos.x, pos.y, i * 5)) % 16) as u8) e.saturating_add((self.noise.get(Vec3::new(pos.x, pos.y, i * 5)) % 16) as u8)
.saturating_sub(8) .saturating_sub(8)
@ -955,7 +992,9 @@ pub enum Plot {
Dirt, Dirt,
Grass, Grass,
Water, Water,
Town, Town {
district: Option<Id<District>>,
},
Field { Field {
farm: Id<Farm>, farm: Id<Farm>,
seed: u32, seed: u32,
@ -1015,6 +1054,8 @@ pub struct Sample<'a> {
plot: Option<&'a Plot>, plot: Option<&'a Plot>,
way: Option<(&'a WayKind, f32, Vec2<f32>)>, way: Option<(&'a WayKind, f32, Vec2<f32>)>,
tower: Option<(&'a Tower, Vec2<i32>)>, tower: Option<(&'a Tower, Vec2<i32>)>,
edge_dist: f32,
second_closest: Vec2<i32>,
} }
pub struct Land { pub struct Land {
@ -1049,6 +1090,15 @@ impl Land {
.min_by_key(|(center, _)| center.distance_squared(pos)) .min_by_key(|(center, _)| center.distance_squared(pos))
.unwrap() .unwrap()
.0; .0;
let second_closest = neighbors
.iter()
.filter(|(center, _)| *center != closest)
.min_by_key(|(center, _)| center.distance_squared(pos))
.unwrap()
.0;
sample.second_closest = second_closest.map(to_tile);
sample.edge_dist = (second_closest - pos).map(|e| e as f32).magnitude()
- (closest - pos).map(|e| e as f32).magnitude();
let center_tile = self.tile_at(neighbors[4].0.map(to_tile)); let center_tile = self.tile_at(neighbors[4].0.map(to_tile));

View File

@ -0,0 +1,82 @@
use super::{GenCtx, AREA_SIZE};
use common::store::{Id, Store};
use rand::prelude::*;
use vek::*;
pub struct Town {
pub base_tile: Vec2<i32>,
radius: i32,
districts: Store<District>,
}
impl Town {
pub fn districts(&self) -> &Store<District> { &self.districts }
pub fn generate(origin: Vec2<i32>, base_tile: Vec2<i32>, ctx: &mut GenCtx<impl Rng>) -> Self {
let mut this = Self {
base_tile,
radius: 4,
districts: Store::default(),
};
this.generate_districts(origin, ctx);
this
}
fn generate_districts(&mut self, origin: Vec2<i32>, ctx: &mut GenCtx<impl Rng>) {
let base_aabr = Aabr {
min: self.base_tile - self.radius,
max: self.base_tile + self.radius,
};
gen_plot(base_aabr, ctx).for_each(base_aabr, &mut |aabr| {
if aabr.center().distance_squared(self.base_tile) < self.radius.pow(2) {
self.districts.insert(District {
seed: ctx.rng.gen(),
aabr,
alt: ctx
.sim
.and_then(|sim| {
sim.get_alt_approx(
origin + aabr.center() * AREA_SIZE as i32 + AREA_SIZE as i32 / 2,
)
})
.unwrap_or(0.0) as i32,
});
}
});
}
}
pub struct District {
pub seed: u32,
pub aabr: Aabr<i32>,
pub alt: i32,
}
enum Plot {
District,
Parent(Vec<(Aabr<i32>, Plot)>),
}
impl Plot {
fn for_each(&self, aabr: Aabr<i32>, f: &mut impl FnMut(Aabr<i32>)) {
match self {
Plot::District => f(aabr),
Plot::Parent(children) => children.iter().for_each(|(aabr, p)| p.for_each(*aabr, f)),
}
}
}
fn gen_plot(aabr: Aabr<i32>, ctx: &mut GenCtx<impl Rng>) -> Plot {
if aabr.size().product() <= 9 {
Plot::District
} else if aabr.size().w < aabr.size().h {
let [a, b] = aabr.split_at_y(aabr.min.y + ctx.rng.gen_range(1, aabr.size().h));
Plot::Parent(vec![(a, gen_plot(a, ctx)), (b, gen_plot(b, ctx))])
} else {
let [a, b] = aabr.split_at_x(aabr.min.x + ctx.rng.gen_range(1, aabr.size().w));
Plot::Parent(vec![(a, gen_plot(a, ctx)), (b, gen_plot(b, ctx))])
}
}

79
world/src/util/map_vec.rs Normal file
View File

@ -0,0 +1,79 @@
use crate::util::DHashMap;
use std::hash::Hash;
#[derive(Clone, Debug)]
pub struct MapVec<K, T> {
/// We use this hasher (FxHasher32) because
/// (1) we don't care about DDOS attacks (ruling out SipHash);
/// (2) we care about determinism across computers (ruling out AAHash);
/// (3) we have 1-byte keys (for which FxHash is supposedly fastest).
entries: DHashMap<K, T>,
default: T,
}
/// Need manual implementation of Default since K doesn't need that bound.
impl<K, T: Default> Default for MapVec<K, T> {
fn default() -> Self {
Self {
entries: Default::default(),
default: Default::default(),
}
}
}
impl<K: Copy + Eq + Hash, T: Clone> MapVec<K, T> {
pub fn from_list<'a>(i: impl IntoIterator<Item = &'a (K, T)>, default: T) -> Self
where
K: 'a,
T: 'a,
{
Self {
entries: i.into_iter().cloned().collect(),
default,
}
}
pub fn from_default(default: T) -> Self {
Self {
entries: DHashMap::default(),
default,
}
}
pub fn get_mut(&mut self, entry: K) -> &mut T {
let default = &self.default;
self.entries.entry(entry).or_insert_with(|| default.clone())
}
pub fn get(&self, entry: K) -> &T { self.entries.get(&entry).unwrap_or(&self.default) }
#[allow(clippy::clone_on_copy)] // TODO: Pending review in #587
pub fn map<U: Default>(self, mut f: impl FnMut(K, T) -> U) -> MapVec<K, U> {
MapVec {
entries: self
.entries
.into_iter()
.map(|(s, v)| (s.clone(), f(s, v)))
.collect(),
default: U::default(),
}
}
pub fn iter(&self) -> impl Iterator<Item = (K, &T)> + '_ {
self.entries.iter().map(|(s, v)| (*s, v))
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (K, &mut T)> + '_ {
self.entries.iter_mut().map(|(s, v)| (*s, v))
}
}
impl<K: Copy + Eq + Hash, T: Clone> std::ops::Index<K> for MapVec<K, T> {
type Output = T;
fn index(&self, entry: K) -> &Self::Output { self.get(entry) }
}
impl<K: Copy + Eq + Hash, T: Clone> std::ops::IndexMut<K> for MapVec<K, T> {
fn index_mut(&mut self, entry: K) -> &mut Self::Output { self.get_mut(entry) }
}

View File

@ -1,5 +1,6 @@
pub mod fast_noise; pub mod fast_noise;
pub mod grid; pub mod grid;
pub mod map_vec;
pub mod random; pub mod random;
pub mod sampler; pub mod sampler;
pub mod seed_expan; pub mod seed_expan;
@ -11,6 +12,7 @@ pub mod unit_chooser;
pub use self::{ pub use self::{
fast_noise::FastNoise, fast_noise::FastNoise,
grid::Grid, grid::Grid,
map_vec::MapVec,
random::{RandomField, RandomPerm}, random::{RandomField, RandomPerm},
sampler::{Sampler, SamplerMut}, sampler::{Sampler, SamplerMut},
small_cache::SmallCache, small_cache::SmallCache,
@ -18,8 +20,15 @@ pub use self::{
unit_chooser::UnitChooser, unit_chooser::UnitChooser,
}; };
use fxhash::{FxHasher32, FxHasher64};
use hashbrown::{HashMap, HashSet};
use std::hash::BuildHasherDefault;
use vek::*; use vek::*;
// Deterministic HashMap and HashSet
pub type DHashMap<K, V> = HashMap<K, V, BuildHasherDefault<FxHasher32>>;
pub type DHashSet<T> = HashSet<T, BuildHasherDefault<FxHasher32>>;
pub fn attempt<T>(max_iters: usize, mut f: impl FnMut() -> Option<T>) -> Option<T> { pub fn attempt<T>(max_iters: usize, mut f: impl FnMut() -> Option<T>) -> Option<T> {
(0..max_iters).find_map(|_| f()) (0..max_iters).find_map(|_| f())
} }