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>> {
// NOTE: Assumes usize fits into 8 bytes.
(0..self.items.len() as u64).map(|i| Id(i, PhantomData))
(0..self.items.len()).map(|i| Id(i as u64, 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)> {
self.items
.iter()
.enumerate()
// NOTE: Assumes usize fits into 8 bytes.
.map(|(i, item)| (Id(i as u64, PhantomData), item))
pub fn iter(&self) -> impl Iterator<Item = (Id<T>, &T)> { self.ids().zip(self.values()) }
pub fn iter_mut(&mut self) -> impl Iterator<Item = (Id<T>, &mut T)> {
self.ids().zip(self.values_mut())
}
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 chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32);
let chunk = sim.get(chunk_pos)?;
let col = sampler.get(wpos)?;
let col = sampler.get((wpos, server.world.index()))?;
let downhill = chunk.downhill;
let river = &chunk.river;
let flux = chunk.flux;

View File

@ -184,7 +184,7 @@ impl Server {
..WorldOpts::default()
});
#[cfg(feature = "worldgen")]
let map = world.sim().get_map();
let map = world.get_map_data();
#[cfg(not(feature = "worldgen"))]
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
let mut block_sampler = world.sample_blocks();
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));
// 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
let min_z = min_z.floor() 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),
Some(&z_cache),
false,
world.index(),
)
.map(|b| b.is_air())
.unwrap_or(false)

View File

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

View File

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

View File

@ -6,7 +6,8 @@ use self::{Occupation::*, Stock::*};
use crate::{
sim::WorldSim,
site::{Dungeon, Settlement, Site as WorldSite},
util::{attempt, seed_expan, CARDINALS, NEIGHBORS},
util::{attempt, seed_expan, MapVec, CARDINALS, NEIGHBORS},
Index,
};
use common::{
astar::Astar,
@ -70,7 +71,7 @@ impl<'a, R: Rng> GenCtx<'a, R> {
}
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 rng = ChaChaRng::from_seed(seed_expan::rng_state(seed));
let mut ctx = GenCtx { sim, rng };
@ -103,7 +104,7 @@ impl Civs {
last_exports: Stocks::from_default(0.0),
export_targets: Stocks::from_default(0.0),
trade_states: Stocks::default(),
//trade_states: Stocks::default(),
coin: 1000.0,
})
});
@ -116,13 +117,13 @@ impl Civs {
}
// Flatten ground around sites
for site in this.sites.iter() {
for site in this.sites.values() {
let radius = 48i32;
let wpos = site.center * TerrainChunkSize::RECT_SIZE.map(|e: u32| e as i32);
let flatten_radius = match &site.kind {
SiteKind::Settlement => 10.0,
SiteKind::Settlement => 8.0,
SiteKind::Dungeon => 2.0,
};
@ -143,7 +144,7 @@ impl Civs {
let pos = site.center + offs;
let factor = (1.0
- (site.center - pos).map(|e| e as f32).magnitude() / flatten_radius)
* 1.15;
* 0.8;
ctx.sim
.get_mut(pos)
// Don't disrupt chunks that are near water
@ -161,33 +162,32 @@ impl Civs {
// Place sites in world
let mut cnt = 0;
for site in this.sites.iter() {
for sim_site in this.sites.values() {
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
});
let mut rng = ctx.reseed().rng;
let world_site = match &site.kind {
let site = index.sites.insert(match &sim_site.kind {
SiteKind::Settlement => {
WorldSite::from(Settlement::generate(wpos, Some(ctx.sim), &mut rng))
WorldSite::settlement(Settlement::generate(wpos, Some(ctx.sim), &mut rng))
},
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 =
(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()
.map(|offs| site.center + offs)
.map(|offs| sim_site.center + offs)
.take((radius_chunks * 2).pow(2))
{
ctx.sim
.get_mut(pos)
.map(|chunk| chunk.sites.push(world_site.clone()));
ctx.sim.get_mut(pos).map(|chunk| chunk.sites.push(site));
}
debug!(?site.center, "Placed site at location");
debug!(?sim_site.center, "Placed site at location");
}
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 sites(&self) -> impl Iterator<Item = &Site> + '_ { self.sites.iter() }
pub fn sites(&self) -> impl Iterator<Item = &Site> + '_ { self.sites.values() }
#[allow(dead_code)]
#[allow(clippy::print_literal)] // TODO: Pending review in #587
fn display_info(&self) {
for (id, civ) in self.civs.iter_ids() {
for (id, civ) in self.civs.iter() {
println!("# Civilisation {:?}", id);
println!("Name: {}", "<unnamed>");
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);
}
@ -290,7 +290,7 @@ impl Civs {
last_exports: Stocks::from_default(0.0),
export_targets: Stocks::from_default(0.0),
trade_states: Stocks::default(),
//trade_states: Stocks::default(),
coin: 1000.0,
})
})?;
@ -380,7 +380,7 @@ impl Civs {
const MAX_NEIGHBOR_DISTANCE: f32 = 500.0;
let mut nearby = self
.sites
.iter_ids()
.iter()
.map(|(id, p)| (id, (p.center.distance_squared(loc) as f32).sqrt()))
.filter(|(_, dist)| *dist < MAX_NEIGHBOR_DISTANCE)
.collect::<Vec<_>>();
@ -440,7 +440,7 @@ impl Civs {
}
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);
}
@ -717,7 +717,7 @@ pub struct Site {
last_exports: Stocks<f32>,
export_targets: Stocks<f32>,
trade_states: Stocks<TradeState>,
//trade_states: Stocks<TradeState>,
coin: f32,
}
@ -996,79 +996,3 @@ impl Default for TradeState {
}
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,
sim::{local_cells, uniform_idx_as_vec2, vec2_as_uniform_idx, RiverKind, SimChunk, WorldSim},
util::Sampler,
CONFIG,
Index, CONFIG,
};
use common::{terrain::TerrainChunkSize, vol::RectVolSize};
use noise::NoiseFn;
@ -165,8 +165,11 @@ pub fn quadratic_nearest_point(
min_root
}
impl<'a> Sampler<'a> for ColumnGen<'a> {
type Index = Vec2<i32>;
impl<'a, 'b> Sampler<'b> for ColumnGen<'a>
where
'a: 'b,
{
type Index = (Vec2<i32>, &'b Index);
type Sample = Option<ColumnSample<'a>>;
#[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::single_match)] // 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 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
.sites
.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))
} 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;
mod column;
pub mod config;
pub mod index;
pub mod layer;
pub mod sim;
pub mod sim2;
pub mod site;
pub mod util;
@ -19,6 +21,7 @@ pub use crate::config::CONFIG;
use crate::{
block::BlockGen,
column::{ColumnGen, ColumnSample},
index::Index,
util::{Grid, Sampler},
};
use common::{
@ -39,26 +42,35 @@ pub enum Error {
pub struct World {
sim: sim::WorldSim,
civs: civ::Civs,
index: Index,
}
impl World {
pub fn generate(seed: u32, opts: sim::WorldOpts) -> Self {
let mut sim = sim::WorldSim::generate(seed, opts);
let civs = civ::Civs::generate(seed, &mut sim);
Self { sim, civs }
let mut index = Index::default();
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 civs(&self) -> &civ::Civs { &self.civs }
pub fn index(&self) -> &Index { &self.index }
pub fn tick(&self, _dt: Duration) {
// TODO
}
pub fn get_map_data(&self) -> Vec<u32> { self.sim.get_map(&self.index) }
pub fn sample_columns(
&self,
) -> impl Sampler<Index = Vec2<i32>, Sample = Option<ColumnSample>> + '_ {
) -> impl Sampler<Index = (Vec2<i32>, &Index), Sample = Option<ColumnSample>> + '_ {
ColumnGen::new(&self.sim)
}
@ -77,7 +89,7 @@ impl World {
let grid_border = 4;
let zcache_grid = Grid::populate_from(
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();
@ -132,7 +144,8 @@ impl World {
_ => 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| {
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;
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);
}
@ -163,10 +176,9 @@ impl World {
let mut rng = rand::thread_rng();
// Apply site generation
sim_chunk
.sites
.iter()
.for_each(|site| site.apply_to(chunk_wpos2d, sample_get, &mut chunk));
sim_chunk.sites.iter().for_each(|site| {
self.index.sites[*site].apply_to(chunk_wpos2d, sample_get, &mut chunk)
});
// Apply paths
layer::apply_paths_to(chunk_wpos2d, sample_get, &mut chunk);
@ -217,7 +229,12 @@ impl World {
// Apply site supplementary information
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))

View File

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

View File

@ -28,7 +28,7 @@ use crate::{
civ::Place,
site::Site,
util::{seed_expan, FastNoise, RandomField, StructureGen2d, LOCALITY, NEIGHBORS},
CONFIG,
Index, CONFIG,
};
use common::{
assets,
@ -1305,14 +1305,14 @@ impl WorldSim {
/// Draw a map of the world based on chunk information. Returns a buffer of
/// 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];
// TODO: Parallelize again.
MapConfig {
gain: self.max_height,
..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
@ -1788,7 +1788,7 @@ pub struct SimChunk {
pub river: RiverData,
pub warp_factor: f32,
pub sites: Vec<Site>,
pub sites: Vec<Id<Site>>,
pub place: Option<Id<Place>>,
pub path: PathData,
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);
// 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 b in room_areas.iter() {
this.create_route(ctx, a.center(), b.center());
@ -342,7 +342,7 @@ impl Floor {
// Ensure no overlap
if self
.rooms
.iter()
.values()
.any(|r| r.area.collides_with_rect(area_border))
{
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;
pub mod economy;
mod settlement;
// 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 common::{
@ -14,44 +16,6 @@ use rand::Rng;
use std::{fmt, sync::Arc};
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 trees: bool,
}
@ -60,31 +24,49 @@ impl Default for SpawnRules {
fn default() -> Self { Self { trees: true } }
}
#[derive(Clone)]
pub enum Site {
Settlement(Arc<Settlement>),
Dungeon(Arc<Dungeon>),
pub struct Site {
pub kind: SiteKind,
pub economy: Economy,
}
pub enum SiteKind {
Settlement(Settlement),
Dungeon(Dungeon),
}
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 {
match self {
Site::Settlement(settlement) => settlement.radius(),
Site::Dungeon(dungeon) => dungeon.radius(),
match &self.kind {
SiteKind::Settlement(settlement) => settlement.radius(),
SiteKind::Dungeon(dungeon) => dungeon.radius(),
}
}
pub fn get_origin(&self) -> Vec2<i32> {
match self {
Site::Settlement(s) => s.get_origin(),
Site::Dungeon(d) => d.get_origin(),
match &self.kind {
SiteKind::Settlement(s) => s.get_origin(),
SiteKind::Dungeon(d) => d.get_origin(),
}
}
pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
match self {
Site::Settlement(s) => s.spawn_rules(wpos),
Site::Dungeon(d) => d.spawn_rules(wpos),
match &self.kind {
SiteKind::Settlement(s) => s.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>>,
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
) {
match self {
Site::Settlement(settlement) => settlement.apply_to(wpos2d, get_column, vol),
Site::Dungeon(dungeon) => dungeon.apply_to(wpos2d, get_column, vol),
match &self.kind {
SiteKind::Settlement(settlement) => settlement.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>>,
supplement: &mut ChunkSupplement,
) {
match self {
Site::Settlement(settlement) => {
match &self.kind {
SiteKind::Settlement(settlement) => {
settlement.apply_supplement(rng, wpos2d, get_column, supplement)
},
Site::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"),
SiteKind::Dungeon(dungeon) => {
dungeon.apply_supplement(rng, wpos2d, get_column, supplement)
},
}
}
}

View File

@ -12,7 +12,7 @@ use common::{
use rand::prelude::*;
use vek::*;
const COLOR_THEMES: [Rgb<u8>; 11] = [
const COLOR_THEMES: [Rgb<u8>; 17] = [
Rgb::new(0x1D, 0x4D, 0x45),
Rgb::new(0xB3, 0x7D, 0x60),
Rgb::new(0xAC, 0x5D, 0x26),
@ -24,6 +24,12 @@ const COLOR_THEMES: [Rgb<u8>; 11] = [
Rgb::new(0x2F, 0x32, 0x47),
Rgb::new(0x8F, 0x35, 0x43),
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 {
@ -149,10 +155,7 @@ impl Archetype for House {
};
let this = Self {
roof_color: COLOR_THEMES
.choose(rng)
.unwrap()
.map(|e| e.saturating_add(rng.gen_range(0, 20)) - 10),
roof_color: *COLOR_THEMES.choose(rng).unwrap(),
noise: RandomField::new(rng.gen()),
roof_ribbing: rng.gen(),
roof_ribbing_diagonal: rng.gen(),

View File

@ -9,27 +9,38 @@ use vek::*;
pub struct Keep;
pub struct Attr {
height: i32,
is_tower: bool,
}
impl Archetype for Keep {
type Attr = ();
type Attr = 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 {
offset: -rng.gen_range(0, len + 7).clamped(0, len),
ori: if rng.gen() { Ori::East } else { Ori::North },
root: Branch {
len,
attr: Self::Attr::default(),
locus: 6 + rng.gen_range(0, 5),
attr: Attr {
height: rng.gen_range(12, 16),
is_tower: false,
},
locus: 10 + rng.gen_range(0, 5),
border: 3,
children: (0..1)
.map(|_| {
(
rng.gen_range(-5, len + 5).clamped(0, len.max(1) - 1),
Branch {
len: rng.gen_range(5, 12) * if rng.gen() { 1 } else { -1 },
attr: Self::Attr::default(),
locus: 5 + rng.gen_range(0, 3),
len: 0,
attr: Attr {
height: rng.gen_range(20, 28),
is_tower: true,
},
locus: 4 + rng.gen_range(0, 5),
border: 3,
children: Vec::new(),
},
@ -48,7 +59,7 @@ impl Archetype for Keep {
pos: Vec3<i32>,
dist: i32,
bound_offset: Vec2<i32>,
_center_offset: Vec2<i32>,
center_offset: Vec2<i32>,
z: i32,
ori: Ori,
branch: &Branch<Self::Attr>,
@ -60,18 +71,24 @@ impl Archetype for Keep {
let important_layer = normal_layer + 1;
let internal_layer = important_layer + 1;
let make_block =
|r, g, b| BlockMask::new(Block::new(BlockKind::Normal, Rgb::new(r, g, b)), normal_layer);
let make_block = |r, g, b| {
BlockMask::new(
Block::new(BlockKind::Normal, Rgb::new(r, g, b)),
normal_layer,
)
};
let foundation = make_block(100, 100, 100);
let wall = make_block(100, 100, 110);
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 empty = BlockMask::nothing();
let width = branch.locus;
let rampart_width = 2 + branch.locus;
let ceil_height = 12;
let ceil_height = branch.attr.height;
let door_height = 6;
let edge_pos = if (bound_offset.x == rampart_width) ^ (ori == Ori::East) {
pos.y
@ -79,26 +96,49 @@ impl Archetype for Keep {
pos.x
};
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 {
// Foundations
foundation
} else if profile.y == ceil_height && min_dist < rampart_width {
if min_dist < width {
floor
} else {
wall
}
} else if bound_offset.x.abs() == 4 && min_dist == width + 1 && profile.y < ceil_height {
if min_dist < width { floor } else { wall }
} else if !branch.attr.is_tower
&& bound_offset.x.abs() == 4
&& min_dist == width + 1
&& profile.y < ceil_height
{
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
} else if min_dist == width && profile.y <= ceil_height {
wall
} else if profile.y >= ceil_height {
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 {
if profile.y < rampart_height {
wall

View File

@ -12,7 +12,6 @@ use vek::*;
pub type HouseBuilding = Building<archetype::house::House>;
pub type KeepBuilding = Building<archetype::keep::Keep>;
pub struct Building<A: Archetype> {
skel: Skeleton<A::Attr>,
archetype: A,
@ -44,7 +43,7 @@ impl<A: Archetype> Building<A> {
let aabr = self.bounds_2d();
Aabb {
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 town;
use self::building::{HouseBuilding, KeepBuilding};
use self::{
building::{HouseBuilding, KeepBuilding},
town::{District, Town},
};
use super::SpawnRules;
use crate::{
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 {
House(HouseBuilding),
Keep(KeepBuilding),
}
pub struct Structure {
@ -96,7 +99,6 @@ impl Structure {
match &self.kind {
StructureKind::House(house) => house.bounds_2d(),
StructureKind::Keep(keep) => keep.bounds_2d(),
}
}
@ -104,7 +106,6 @@ impl Structure {
match &self.kind {
StructureKind::House(house) => house.bounds(),
StructureKind::Keep(keep) => keep.bounds(),
}
}
@ -112,7 +113,6 @@ impl Structure {
match &self.kind {
StructureKind::House(house) => house.sample(rpos),
StructureKind::Keep(keep) => keep.sample(rpos),
}
}
}
@ -127,10 +127,6 @@ pub struct Settlement {
noise: RandomField,
}
pub struct Town {
base_tile: Vec2<i32>,
}
pub struct Farm {
#[allow(dead_code)]
base_tile: Vec2<i32>,
@ -280,12 +276,28 @@ impl Settlement {
Some(Plot::Dirt) => true,
_ => false,
}) {
self.land
.plot_at_mut(base_tile)
.map(|plot| *plot = Plot::Town);
// self.land
// .plot_at_mut(base_tile)
// .map(|plot| *plot = Plot::Town { district: None });
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;
}
}
@ -346,27 +358,25 @@ impl Settlement {
return;
};
for (i, tile) in Spiral2d::new()
for tile in Spiral2d::new()
.map(|offs| town_center + offs)
.take(16usize.pow(2))
.enumerate()
{
// 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 {
let house_pos = tile.map(|e| e * AREA_SIZE as i32 + AREA_SIZE as i32 / 2)
+ Vec2::<i32>::zero().map(|_| {
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));
if !matches!(self.land.plot_at(tile_pos), Some(Plot::Town))
|| self
.land
.tile_at(tile_pos)
.map(|t| t.contains(WayKind::Path))
.unwrap_or(true)
if self
.land
.tile_at(tile_pos)
.map(|t| t.contains(WayKind::Path))
.unwrap_or(true)
|| ctx
.sim
.and_then(|sim| sim.get_nearest_path(self.origin + house_pos))
@ -376,30 +386,30 @@ impl Settlement {
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 {
kind: if i == 0 {
kind: if tile == town_center && i == 0 {
StructureKind::Keep(KeepBuilding::generate(
ctx.rng,
Vec3::new(
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,
),
Vec3::new(house_pos.x, house_pos.y, alt),
))
} else {
StructureKind::House(HouseBuilding::generate(
ctx.rng,
Vec3::new(
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,
),
Vec3::new(house_pos.x, house_pos.y, alt),
))
},
};
@ -536,12 +546,13 @@ impl Settlement {
} else {
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
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));
col.map(|e| {
(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
if let Some((WayKind::Path, dist, nearest)) = sample.way {
let inset = -1;
@ -599,7 +634,7 @@ impl Settlement {
Some(Plot::Dirt) => Some(Rgb::new(90, 70, 50)),
Some(Plot::Grass) => Some(Rgb::new(100, 200, 0)),
Some(Plot::Water) => Some(Rgb::new(100, 150, 250)),
Some(Plot::Town) => {
Some(Plot::Town { district }) => {
if let Some((_, path_nearest)) = col_sample.path {
let path_dir = (path_nearest - wpos2d.map(|e| e as f32))
.rotated_z(f32::consts::PI / 2.0)
@ -693,7 +728,8 @@ impl Settlement {
if let Some(color) = color {
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);
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 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
} else {
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);
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))
{
let is_human: bool;
@ -903,7 +940,7 @@ impl Settlement {
Some(Plot::Dirt) => return Some(Rgb::new(90, 70, 50)),
Some(Plot::Grass) => return Some(Rgb::new(100, 200, 0)),
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| {
e.saturating_add((self.noise.get(Vec3::new(pos.x, pos.y, i * 5)) % 16) as u8)
.saturating_sub(8)
@ -955,7 +992,9 @@ pub enum Plot {
Dirt,
Grass,
Water,
Town,
Town {
district: Option<Id<District>>,
},
Field {
farm: Id<Farm>,
seed: u32,
@ -1015,6 +1054,8 @@ pub struct Sample<'a> {
plot: Option<&'a Plot>,
way: Option<(&'a WayKind, f32, Vec2<f32>)>,
tower: Option<(&'a Tower, Vec2<i32>)>,
edge_dist: f32,
second_closest: Vec2<i32>,
}
pub struct Land {
@ -1049,6 +1090,15 @@ impl Land {
.min_by_key(|(center, _)| center.distance_squared(pos))
.unwrap()
.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));

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 grid;
pub mod map_vec;
pub mod random;
pub mod sampler;
pub mod seed_expan;
@ -11,6 +12,7 @@ pub mod unit_chooser;
pub use self::{
fast_noise::FastNoise,
grid::Grid,
map_vec::MapVec,
random::{RandomField, RandomPerm},
sampler::{Sampler, SamplerMut},
small_cache::SmallCache,
@ -18,8 +20,15 @@ pub use self::{
unit_chooser::UnitChooser,
};
use fxhash::{FxHasher32, FxHasher64};
use hashbrown::{HashMap, HashSet};
use std::hash::BuildHasherDefault;
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> {
(0..max_iters).find_map(|_| f())
}