mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Added forts to towns, began better economy sim
This commit is contained in:
parent
ae8195fac9
commit
f21a50e393
@ -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> {
|
||||
|
2
server-cli/.gitignore
vendored
2
server-cli/.gitignore
vendored
@ -1 +1 @@
|
||||
|
||||
economy.csv
|
||||
|
1
server-cli/.~lock.economy.csv#
Normal file
1
server-cli/.~lock.economy.csv#
Normal file
@ -0,0 +1 @@
|
||||
,joshua,archbox.localdomain,17.06.2020 16:07,file:///home/joshua/.config/libreoffice/4;
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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 };
|
||||
|
@ -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);
|
||||
|
@ -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) }
|
||||
}
|
||||
|
@ -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
8
world/src/index.rs
Normal 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>,
|
||||
}
|
@ -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))
|
||||
|
@ -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)
|
||||
}),
|
||||
|
@ -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
250
world/src/sim2/mod.rs
Normal 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);
|
||||
}
|
39
world/src/site/block_mask.rs
Normal file
39
world/src/site/block_mask.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
@ -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
145
world/src/site/economy.rs
Normal 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
|
||||
}
|
||||
}
|
@ -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)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,23 +99,20 @@ impl Structure {
|
||||
match &self.kind {
|
||||
StructureKind::House(house) => house.bounds_2d(),
|
||||
StructureKind::Keep(keep) => keep.bounds_2d(),
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn bounds(&self) -> Aabb<i32> {
|
||||
match &self.kind {
|
||||
StructureKind::House(house) => house.bounds(),
|
||||
StructureKind::Keep(keep) => keep.bounds(),
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn sample(&self, rpos: Vec3<i32>) -> Option<Block> {
|
||||
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));
|
||||
|
||||
|
82
world/src/site/settlement/town.rs
Normal file
82
world/src/site/settlement/town.rs
Normal 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
79
world/src/util/map_vec.rs
Normal 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) }
|
||||
}
|
@ -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())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user