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>> {
|
pub fn ids(&self) -> impl Iterator<Item = Id<T>> {
|
||||||
// NOTE: Assumes usize fits into 8 bytes.
|
(0..self.items.len()).map(|i| Id(i as u64, PhantomData))
|
||||||
(0..self.items.len() as u64).map(|i| Id(i, PhantomData))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = &T> { self.items.iter() }
|
pub fn values(&self) -> impl Iterator<Item = &T> { self.items.iter() }
|
||||||
|
|
||||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> { self.items.iter_mut() }
|
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut T> { self.items.iter_mut() }
|
||||||
|
|
||||||
pub fn iter_ids(&self) -> impl Iterator<Item = (Id<T>, &T)> {
|
pub fn iter(&self) -> impl Iterator<Item = (Id<T>, &T)> { self.ids().zip(self.values()) }
|
||||||
self.items
|
|
||||||
.iter()
|
pub fn iter_mut(&mut self) -> impl Iterator<Item = (Id<T>, &mut T)> {
|
||||||
.enumerate()
|
self.ids().zip(self.values_mut())
|
||||||
// NOTE: Assumes usize fits into 8 bytes.
|
|
||||||
.map(|(i, item)| (Id(i as u64, PhantomData), item))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(&mut self, item: T) -> Id<T> {
|
pub fn insert(&mut self, item: T) -> Id<T> {
|
||||||
|
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 spawn_rate = sim.get_interpolated(wpos, |chunk| chunk.spawn_rate)?;
|
||||||
let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32);
|
let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32);
|
||||||
let chunk = sim.get(chunk_pos)?;
|
let chunk = sim.get(chunk_pos)?;
|
||||||
let col = sampler.get(wpos)?;
|
let col = sampler.get((wpos, server.world.index()))?;
|
||||||
let downhill = chunk.downhill;
|
let downhill = chunk.downhill;
|
||||||
let river = &chunk.river;
|
let river = &chunk.river;
|
||||||
let flux = chunk.flux;
|
let flux = chunk.flux;
|
||||||
|
@ -184,7 +184,7 @@ impl Server {
|
|||||||
..WorldOpts::default()
|
..WorldOpts::default()
|
||||||
});
|
});
|
||||||
#[cfg(feature = "worldgen")]
|
#[cfg(feature = "worldgen")]
|
||||||
let map = world.sim().get_map();
|
let map = world.get_map_data();
|
||||||
|
|
||||||
#[cfg(not(feature = "worldgen"))]
|
#[cfg(not(feature = "worldgen"))]
|
||||||
let world = World::generate(settings.world_seed);
|
let world = World::generate(settings.world_seed);
|
||||||
@ -219,11 +219,11 @@ impl Server {
|
|||||||
// get a z cache for the collumn in which we want to spawn
|
// get a z cache for the collumn in which we want to spawn
|
||||||
let mut block_sampler = world.sample_blocks();
|
let mut block_sampler = world.sample_blocks();
|
||||||
let z_cache = block_sampler
|
let z_cache = block_sampler
|
||||||
.get_z_cache(spawn_location)
|
.get_z_cache(spawn_location, world.index())
|
||||||
.expect(&format!("no z_cache found for chunk: {}", spawn_chunk));
|
.expect(&format!("no z_cache found for chunk: {}", spawn_chunk));
|
||||||
|
|
||||||
// get the minimum and maximum z values at which there could be soild blocks
|
// get the minimum and maximum z values at which there could be soild blocks
|
||||||
let (min_z, _, max_z) = z_cache.get_z_limits(&mut block_sampler);
|
let (min_z, _, max_z) = z_cache.get_z_limits(&mut block_sampler, world.index());
|
||||||
// round range outwards, so no potential air block is missed
|
// round range outwards, so no potential air block is missed
|
||||||
let min_z = min_z.floor() as i32;
|
let min_z = min_z.floor() as i32;
|
||||||
let max_z = max_z.ceil() as i32;
|
let max_z = max_z.ceil() as i32;
|
||||||
@ -239,6 +239,7 @@ impl Server {
|
|||||||
Vec3::new(spawn_location.x, spawn_location.y, *z),
|
Vec3::new(spawn_location.x, spawn_location.y, *z),
|
||||||
Some(&z_cache),
|
Some(&z_cache),
|
||||||
false,
|
false,
|
||||||
|
world.index(),
|
||||||
)
|
)
|
||||||
.map(|b| b.is_air())
|
.map(|b| b.is_air())
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
|
@ -3,7 +3,7 @@ mod natural;
|
|||||||
use crate::{
|
use crate::{
|
||||||
column::{ColumnGen, ColumnSample},
|
column::{ColumnGen, ColumnSample},
|
||||||
util::{RandomField, Sampler, SmallCache},
|
util::{RandomField, Sampler, SmallCache},
|
||||||
CONFIG,
|
Index, CONFIG,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
terrain::{structure::StructureBlock, Block, BlockKind, Structure},
|
terrain::{structure::StructureBlock, Block, BlockKind, Structure},
|
||||||
@ -29,8 +29,9 @@ impl<'a> BlockGen<'a> {
|
|||||||
column_gen: &ColumnGen<'a>,
|
column_gen: &ColumnGen<'a>,
|
||||||
cache: &'b mut SmallCache<Option<ColumnSample<'a>>>,
|
cache: &'b mut SmallCache<Option<ColumnSample<'a>>>,
|
||||||
wpos: Vec2<i32>,
|
wpos: Vec2<i32>,
|
||||||
|
index: &Index,
|
||||||
) -> Option<&'b ColumnSample<'a>> {
|
) -> Option<&'b ColumnSample<'a>> {
|
||||||
cache.get(wpos, |wpos| column_gen.get(wpos)).as_ref()
|
cache.get(wpos, |wpos| column_gen.get((wpos, index))).as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_cliff_height(
|
fn get_cliff_height(
|
||||||
@ -40,11 +41,16 @@ impl<'a> BlockGen<'a> {
|
|||||||
close_cliffs: &[(Vec2<i32>, u32); 9],
|
close_cliffs: &[(Vec2<i32>, u32); 9],
|
||||||
cliff_hill: f32,
|
cliff_hill: f32,
|
||||||
tolerance: f32,
|
tolerance: f32,
|
||||||
|
index: &Index,
|
||||||
) -> f32 {
|
) -> f32 {
|
||||||
close_cliffs.iter().fold(
|
close_cliffs.iter().fold(
|
||||||
0.0f32,
|
0.0f32,
|
||||||
|max_height, (cliff_pos, seed)| match Self::sample_column(column_gen, cache, *cliff_pos)
|
|max_height, (cliff_pos, seed)| match Self::sample_column(
|
||||||
{
|
column_gen,
|
||||||
|
cache,
|
||||||
|
Vec2::from(*cliff_pos),
|
||||||
|
index,
|
||||||
|
) {
|
||||||
Some(cliff_sample) if cliff_sample.is_cliffs && cliff_sample.spawn_rate > 0.5 => {
|
Some(cliff_sample) if cliff_sample.is_cliffs && cliff_sample.spawn_rate > 0.5 => {
|
||||||
let cliff_pos3d = Vec3::from(*cliff_pos);
|
let cliff_pos3d = Vec3::from(*cliff_pos);
|
||||||
|
|
||||||
@ -84,14 +90,14 @@ impl<'a> BlockGen<'a> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_z_cache(&mut self, wpos: Vec2<i32>) -> Option<ZCache<'a>> {
|
pub fn get_z_cache(&mut self, wpos: Vec2<i32>, index: &'a Index) -> Option<ZCache<'a>> {
|
||||||
let BlockGen {
|
let BlockGen {
|
||||||
column_cache,
|
column_cache,
|
||||||
column_gen,
|
column_gen,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
// Main sample
|
// Main sample
|
||||||
let sample = column_gen.get(wpos)?;
|
let sample = column_gen.get((wpos, index))?;
|
||||||
|
|
||||||
// Tree samples
|
// Tree samples
|
||||||
let mut structures = [None, None, None, None, None, None, None, None, None];
|
let mut structures = [None, None, None, None, None, None, None, None, None];
|
||||||
@ -101,7 +107,7 @@ impl<'a> BlockGen<'a> {
|
|||||||
.zip(structures.iter_mut())
|
.zip(structures.iter_mut())
|
||||||
.for_each(|(close_structure, structure)| {
|
.for_each(|(close_structure, structure)| {
|
||||||
if let Some(st) = *close_structure {
|
if let Some(st) = *close_structure {
|
||||||
let st_sample = Self::sample_column(column_gen, column_cache, st.pos);
|
let st_sample = Self::sample_column(column_gen, column_cache, st.pos, index);
|
||||||
if let Some(st_sample) = st_sample {
|
if let Some(st_sample) = st_sample {
|
||||||
let st_sample = st_sample.clone();
|
let st_sample = st_sample.clone();
|
||||||
let st_info = match st.meta {
|
let st_info = match st.meta {
|
||||||
@ -111,6 +117,7 @@ impl<'a> BlockGen<'a> {
|
|||||||
st.pos,
|
st.pos,
|
||||||
st.seed,
|
st.seed,
|
||||||
&st_sample,
|
&st_sample,
|
||||||
|
index,
|
||||||
),
|
),
|
||||||
Some(meta) => Some(StructureInfo {
|
Some(meta) => Some(StructureInfo {
|
||||||
pos: Vec3::from(st.pos) + Vec3::unit_z() * st_sample.alt as i32,
|
pos: Vec3::from(st.pos) + Vec3::unit_z() * st_sample.alt as i32,
|
||||||
@ -137,6 +144,7 @@ impl<'a> BlockGen<'a> {
|
|||||||
wpos: Vec3<i32>,
|
wpos: Vec3<i32>,
|
||||||
z_cache: Option<&ZCache>,
|
z_cache: Option<&ZCache>,
|
||||||
only_structures: bool,
|
only_structures: bool,
|
||||||
|
index: &Index,
|
||||||
) -> Option<Block> {
|
) -> Option<Block> {
|
||||||
let BlockGen {
|
let BlockGen {
|
||||||
column_cache,
|
column_cache,
|
||||||
@ -208,6 +216,7 @@ impl<'a> BlockGen<'a> {
|
|||||||
&close_cliffs,
|
&close_cliffs,
|
||||||
cliff_hill,
|
cliff_hill,
|
||||||
0.0,
|
0.0,
|
||||||
|
index,
|
||||||
);
|
);
|
||||||
|
|
||||||
(
|
(
|
||||||
@ -412,7 +421,7 @@ pub struct ZCache<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ZCache<'a> {
|
impl<'a> ZCache<'a> {
|
||||||
pub fn get_z_limits(&self, block_gen: &mut BlockGen) -> (f32, f32, f32) {
|
pub fn get_z_limits(&self, block_gen: &mut BlockGen, index: &Index) -> (f32, f32, f32) {
|
||||||
let cave_depth =
|
let cave_depth =
|
||||||
if self.sample.cave_xy.abs() > 0.9 && self.sample.water_level <= self.sample.alt {
|
if self.sample.cave_xy.abs() > 0.9 && self.sample.water_level <= self.sample.alt {
|
||||||
(self.sample.alt - self.sample.cave_alt + 8.0).max(0.0)
|
(self.sample.alt - self.sample.cave_alt + 8.0).max(0.0)
|
||||||
@ -430,6 +439,7 @@ impl<'a> ZCache<'a> {
|
|||||||
&self.sample.close_cliffs,
|
&self.sample.close_cliffs,
|
||||||
self.sample.cliff_hill,
|
self.sample.cliff_hill,
|
||||||
32.0,
|
32.0,
|
||||||
|
index,
|
||||||
);
|
);
|
||||||
|
|
||||||
let rocks = if self.sample.rock > 0.0 { 12.0 } else { 0.0 };
|
let rocks = if self.sample.rock > 0.0 { 12.0 } else { 0.0 };
|
||||||
|
@ -3,7 +3,7 @@ use crate::{
|
|||||||
all::ForestKind,
|
all::ForestKind,
|
||||||
column::{ColumnGen, ColumnSample},
|
column::{ColumnGen, ColumnSample},
|
||||||
util::{RandomPerm, Sampler, SmallCache, UnitChooser},
|
util::{RandomPerm, Sampler, SmallCache, UnitChooser},
|
||||||
CONFIG,
|
Index, CONFIG,
|
||||||
};
|
};
|
||||||
use common::terrain::Structure;
|
use common::terrain::Structure;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
@ -20,6 +20,7 @@ pub fn structure_gen<'a>(
|
|||||||
st_pos: Vec2<i32>,
|
st_pos: Vec2<i32>,
|
||||||
st_seed: u32,
|
st_seed: u32,
|
||||||
st_sample: &ColumnSample,
|
st_sample: &ColumnSample,
|
||||||
|
index: &'a Index,
|
||||||
) -> Option<StructureInfo> {
|
) -> Option<StructureInfo> {
|
||||||
// Assuming it's a tree... figure out when it SHOULDN'T spawn
|
// Assuming it's a tree... figure out when it SHOULDN'T spawn
|
||||||
let random_seed = (st_seed as f64) / (u32::MAX as f64);
|
let random_seed = (st_seed as f64) / (u32::MAX as f64);
|
||||||
@ -39,6 +40,7 @@ pub fn structure_gen<'a>(
|
|||||||
&st_sample.close_cliffs,
|
&st_sample.close_cliffs,
|
||||||
st_sample.cliff_hill,
|
st_sample.cliff_hill,
|
||||||
0.0,
|
0.0,
|
||||||
|
index,
|
||||||
);
|
);
|
||||||
|
|
||||||
let wheight = st_sample.alt.max(cliff_height);
|
let wheight = st_sample.alt.max(cliff_height);
|
||||||
|
@ -6,7 +6,8 @@ use self::{Occupation::*, Stock::*};
|
|||||||
use crate::{
|
use crate::{
|
||||||
sim::WorldSim,
|
sim::WorldSim,
|
||||||
site::{Dungeon, Settlement, Site as WorldSite},
|
site::{Dungeon, Settlement, Site as WorldSite},
|
||||||
util::{attempt, seed_expan, CARDINALS, NEIGHBORS},
|
util::{attempt, seed_expan, MapVec, CARDINALS, NEIGHBORS},
|
||||||
|
Index,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
astar::Astar,
|
astar::Astar,
|
||||||
@ -70,7 +71,7 @@ impl<'a, R: Rng> GenCtx<'a, R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Civs {
|
impl Civs {
|
||||||
pub fn generate(seed: u32, sim: &mut WorldSim) -> Self {
|
pub fn generate(seed: u32, sim: &mut WorldSim, index: &mut Index) -> Self {
|
||||||
let mut this = Self::default();
|
let mut this = Self::default();
|
||||||
let rng = ChaChaRng::from_seed(seed_expan::rng_state(seed));
|
let rng = ChaChaRng::from_seed(seed_expan::rng_state(seed));
|
||||||
let mut ctx = GenCtx { sim, rng };
|
let mut ctx = GenCtx { sim, rng };
|
||||||
@ -103,7 +104,7 @@ impl Civs {
|
|||||||
|
|
||||||
last_exports: Stocks::from_default(0.0),
|
last_exports: Stocks::from_default(0.0),
|
||||||
export_targets: Stocks::from_default(0.0),
|
export_targets: Stocks::from_default(0.0),
|
||||||
trade_states: Stocks::default(),
|
//trade_states: Stocks::default(),
|
||||||
coin: 1000.0,
|
coin: 1000.0,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@ -116,13 +117,13 @@ impl Civs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Flatten ground around sites
|
// Flatten ground around sites
|
||||||
for site in this.sites.iter() {
|
for site in this.sites.values() {
|
||||||
let radius = 48i32;
|
let radius = 48i32;
|
||||||
|
|
||||||
let wpos = site.center * TerrainChunkSize::RECT_SIZE.map(|e: u32| e as i32);
|
let wpos = site.center * TerrainChunkSize::RECT_SIZE.map(|e: u32| e as i32);
|
||||||
|
|
||||||
let flatten_radius = match &site.kind {
|
let flatten_radius = match &site.kind {
|
||||||
SiteKind::Settlement => 10.0,
|
SiteKind::Settlement => 8.0,
|
||||||
SiteKind::Dungeon => 2.0,
|
SiteKind::Dungeon => 2.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -143,7 +144,7 @@ impl Civs {
|
|||||||
let pos = site.center + offs;
|
let pos = site.center + offs;
|
||||||
let factor = (1.0
|
let factor = (1.0
|
||||||
- (site.center - pos).map(|e| e as f32).magnitude() / flatten_radius)
|
- (site.center - pos).map(|e| e as f32).magnitude() / flatten_radius)
|
||||||
* 1.15;
|
* 0.8;
|
||||||
ctx.sim
|
ctx.sim
|
||||||
.get_mut(pos)
|
.get_mut(pos)
|
||||||
// Don't disrupt chunks that are near water
|
// Don't disrupt chunks that are near water
|
||||||
@ -161,33 +162,32 @@ impl Civs {
|
|||||||
|
|
||||||
// Place sites in world
|
// Place sites in world
|
||||||
let mut cnt = 0;
|
let mut cnt = 0;
|
||||||
for site in this.sites.iter() {
|
for sim_site in this.sites.values() {
|
||||||
cnt += 1;
|
cnt += 1;
|
||||||
let wpos = site.center.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| {
|
let wpos = sim_site.center.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| {
|
||||||
e * sz as i32 + sz as i32 / 2
|
e * sz as i32 + sz as i32 / 2
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut rng = ctx.reseed().rng;
|
let mut rng = ctx.reseed().rng;
|
||||||
let world_site = match &site.kind {
|
let site = index.sites.insert(match &sim_site.kind {
|
||||||
SiteKind::Settlement => {
|
SiteKind::Settlement => {
|
||||||
WorldSite::from(Settlement::generate(wpos, Some(ctx.sim), &mut rng))
|
WorldSite::settlement(Settlement::generate(wpos, Some(ctx.sim), &mut rng))
|
||||||
},
|
},
|
||||||
SiteKind::Dungeon => {
|
SiteKind::Dungeon => {
|
||||||
WorldSite::from(Dungeon::generate(wpos, Some(ctx.sim), &mut rng))
|
WorldSite::dungeon(Dungeon::generate(wpos, Some(ctx.sim), &mut rng))
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
let site_ref = &index.sites[site];
|
||||||
|
|
||||||
let radius_chunks =
|
let radius_chunks =
|
||||||
(world_site.radius() / TerrainChunkSize::RECT_SIZE.x as f32).ceil() as usize;
|
(site_ref.radius() / TerrainChunkSize::RECT_SIZE.x as f32).ceil() as usize;
|
||||||
for pos in Spiral2d::new()
|
for pos in Spiral2d::new()
|
||||||
.map(|offs| site.center + offs)
|
.map(|offs| sim_site.center + offs)
|
||||||
.take((radius_chunks * 2).pow(2))
|
.take((radius_chunks * 2).pow(2))
|
||||||
{
|
{
|
||||||
ctx.sim
|
ctx.sim.get_mut(pos).map(|chunk| chunk.sites.push(site));
|
||||||
.get_mut(pos)
|
|
||||||
.map(|chunk| chunk.sites.push(world_site.clone()));
|
|
||||||
}
|
}
|
||||||
debug!(?site.center, "Placed site at location");
|
debug!(?sim_site.center, "Placed site at location");
|
||||||
}
|
}
|
||||||
info!(?cnt, "all sites placed");
|
info!(?cnt, "all sites placed");
|
||||||
|
|
||||||
@ -198,18 +198,18 @@ impl Civs {
|
|||||||
|
|
||||||
pub fn place(&self, id: Id<Place>) -> &Place { self.places.get(id) }
|
pub fn place(&self, id: Id<Place>) -> &Place { self.places.get(id) }
|
||||||
|
|
||||||
pub fn sites(&self) -> impl Iterator<Item = &Site> + '_ { self.sites.iter() }
|
pub fn sites(&self) -> impl Iterator<Item = &Site> + '_ { self.sites.values() }
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[allow(clippy::print_literal)] // TODO: Pending review in #587
|
#[allow(clippy::print_literal)] // TODO: Pending review in #587
|
||||||
fn display_info(&self) {
|
fn display_info(&self) {
|
||||||
for (id, civ) in self.civs.iter_ids() {
|
for (id, civ) in self.civs.iter() {
|
||||||
println!("# Civilisation {:?}", id);
|
println!("# Civilisation {:?}", id);
|
||||||
println!("Name: {}", "<unnamed>");
|
println!("Name: {}", "<unnamed>");
|
||||||
println!("Homeland: {:#?}", self.places.get(civ.homeland));
|
println!("Homeland: {:#?}", self.places.get(civ.homeland));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (id, site) in self.sites.iter_ids() {
|
for (id, site) in self.sites.iter() {
|
||||||
println!("# Site {:?}", id);
|
println!("# Site {:?}", id);
|
||||||
println!("{:#?}", site);
|
println!("{:#?}", site);
|
||||||
}
|
}
|
||||||
@ -290,7 +290,7 @@ impl Civs {
|
|||||||
|
|
||||||
last_exports: Stocks::from_default(0.0),
|
last_exports: Stocks::from_default(0.0),
|
||||||
export_targets: Stocks::from_default(0.0),
|
export_targets: Stocks::from_default(0.0),
|
||||||
trade_states: Stocks::default(),
|
//trade_states: Stocks::default(),
|
||||||
coin: 1000.0,
|
coin: 1000.0,
|
||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
@ -380,7 +380,7 @@ impl Civs {
|
|||||||
const MAX_NEIGHBOR_DISTANCE: f32 = 500.0;
|
const MAX_NEIGHBOR_DISTANCE: f32 = 500.0;
|
||||||
let mut nearby = self
|
let mut nearby = self
|
||||||
.sites
|
.sites
|
||||||
.iter_ids()
|
.iter()
|
||||||
.map(|(id, p)| (id, (p.center.distance_squared(loc) as f32).sqrt()))
|
.map(|(id, p)| (id, (p.center.distance_squared(loc) as f32).sqrt()))
|
||||||
.filter(|(_, dist)| *dist < MAX_NEIGHBOR_DISTANCE)
|
.filter(|(_, dist)| *dist < MAX_NEIGHBOR_DISTANCE)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
@ -440,7 +440,7 @@ impl Civs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn tick(&mut self, _ctx: &mut GenCtx<impl Rng>, years: f32) {
|
fn tick(&mut self, _ctx: &mut GenCtx<impl Rng>, years: f32) {
|
||||||
for site in self.sites.iter_mut() {
|
for site in self.sites.values_mut() {
|
||||||
site.simulate(years, &self.places.get(site.place).nat_res);
|
site.simulate(years, &self.places.get(site.place).nat_res);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -717,7 +717,7 @@ pub struct Site {
|
|||||||
|
|
||||||
last_exports: Stocks<f32>,
|
last_exports: Stocks<f32>,
|
||||||
export_targets: Stocks<f32>,
|
export_targets: Stocks<f32>,
|
||||||
trade_states: Stocks<TradeState>,
|
//trade_states: Stocks<TradeState>,
|
||||||
coin: f32,
|
coin: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -996,79 +996,3 @@ impl Default for TradeState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub type Stocks<T> = MapVec<Stock, T>;
|
pub type Stocks<T> = MapVec<Stock, T>;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct MapVec<K, T> {
|
|
||||||
/// We use this hasher (FxHasher32) because
|
|
||||||
/// (1) we don't care about DDOS attacks (ruling out SipHash);
|
|
||||||
/// (2) we care about determinism across computers (ruling out AAHash);
|
|
||||||
/// (3) we have 1-byte keys (for which FxHash is supposedly fastest).
|
|
||||||
entries: HashMap<K, T, BuildHasherDefault<FxHasher32>>,
|
|
||||||
default: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Need manual implementation of Default since K doesn't need that bound.
|
|
||||||
impl<K, T: Default> Default for MapVec<K, T> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
entries: Default::default(),
|
|
||||||
default: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Copy + Eq + Hash, T: Clone> MapVec<K, T> {
|
|
||||||
pub fn from_list<'a>(i: impl IntoIterator<Item = &'a (K, T)>, default: T) -> Self
|
|
||||||
where
|
|
||||||
K: 'a,
|
|
||||||
T: 'a,
|
|
||||||
{
|
|
||||||
Self {
|
|
||||||
entries: i.into_iter().cloned().collect(),
|
|
||||||
default,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_default(default: T) -> Self {
|
|
||||||
Self {
|
|
||||||
entries: HashMap::default(),
|
|
||||||
default,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_mut(&mut self, entry: K) -> &mut T {
|
|
||||||
let default = &self.default;
|
|
||||||
self.entries.entry(entry).or_insert_with(|| default.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(&self, entry: K) -> &T { self.entries.get(&entry).unwrap_or(&self.default) }
|
|
||||||
|
|
||||||
pub fn map<U: Default>(self, mut f: impl FnMut(K, T) -> U) -> MapVec<K, U> {
|
|
||||||
MapVec {
|
|
||||||
entries: self
|
|
||||||
.entries
|
|
||||||
.into_iter()
|
|
||||||
.map(|(s, v)| (s, f(s, v)))
|
|
||||||
.collect(),
|
|
||||||
default: U::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = (K, &T)> + '_ {
|
|
||||||
self.entries.iter().map(|(s, v)| (*s, v))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = (K, &mut T)> + '_ {
|
|
||||||
self.entries.iter_mut().map(|(s, v)| (*s, v))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Copy + Eq + Hash, T: Clone> std::ops::Index<K> for MapVec<K, T> {
|
|
||||||
type Output = T;
|
|
||||||
|
|
||||||
fn index(&self, entry: K) -> &Self::Output { self.get(entry) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Copy + Eq + Hash, T: Clone> std::ops::IndexMut<K> for MapVec<K, T> {
|
|
||||||
fn index_mut(&mut self, entry: K) -> &mut Self::Output { self.get_mut(entry) }
|
|
||||||
}
|
|
||||||
|
@ -3,7 +3,7 @@ use crate::{
|
|||||||
block::StructureMeta,
|
block::StructureMeta,
|
||||||
sim::{local_cells, uniform_idx_as_vec2, vec2_as_uniform_idx, RiverKind, SimChunk, WorldSim},
|
sim::{local_cells, uniform_idx_as_vec2, vec2_as_uniform_idx, RiverKind, SimChunk, WorldSim},
|
||||||
util::Sampler,
|
util::Sampler,
|
||||||
CONFIG,
|
Index, CONFIG,
|
||||||
};
|
};
|
||||||
use common::{terrain::TerrainChunkSize, vol::RectVolSize};
|
use common::{terrain::TerrainChunkSize, vol::RectVolSize};
|
||||||
use noise::NoiseFn;
|
use noise::NoiseFn;
|
||||||
@ -165,8 +165,11 @@ pub fn quadratic_nearest_point(
|
|||||||
min_root
|
min_root
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Sampler<'a> for ColumnGen<'a> {
|
impl<'a, 'b> Sampler<'b> for ColumnGen<'a>
|
||||||
type Index = Vec2<i32>;
|
where
|
||||||
|
'a: 'b,
|
||||||
|
{
|
||||||
|
type Index = (Vec2<i32>, &'b Index);
|
||||||
type Sample = Option<ColumnSample<'a>>;
|
type Sample = Option<ColumnSample<'a>>;
|
||||||
|
|
||||||
#[allow(clippy::float_cmp)] // TODO: Pending review in #587
|
#[allow(clippy::float_cmp)] // TODO: Pending review in #587
|
||||||
@ -174,7 +177,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
|||||||
#[allow(clippy::nonminimal_bool)] // TODO: Pending review in #587
|
#[allow(clippy::nonminimal_bool)] // TODO: Pending review in #587
|
||||||
#[allow(clippy::single_match)] // TODO: Pending review in #587
|
#[allow(clippy::single_match)] // TODO: Pending review in #587
|
||||||
#[allow(clippy::bind_instead_of_map)] // TODO: Pending review in #587
|
#[allow(clippy::bind_instead_of_map)] // TODO: Pending review in #587
|
||||||
fn get(&self, wpos: Vec2<i32>) -> Option<ColumnSample<'a>> {
|
fn get(&self, (wpos, index): Self::Index) -> Option<ColumnSample<'a>> {
|
||||||
let wposf = wpos.map(|e| e as f64);
|
let wposf = wpos.map(|e| e as f64);
|
||||||
let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32);
|
let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32);
|
||||||
|
|
||||||
@ -1098,7 +1101,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
|||||||
tree_density: if sim_chunk
|
tree_density: if sim_chunk
|
||||||
.sites
|
.sites
|
||||||
.iter()
|
.iter()
|
||||||
.all(|site| site.spawn_rules(wpos).trees)
|
.all(|site| index.sites[*site].spawn_rules(wpos).trees)
|
||||||
{
|
{
|
||||||
Lerp::lerp(0.0, tree_density, alt.sub(2.0).sub(basement).mul(0.5))
|
Lerp::lerp(0.0, tree_density, alt.sub(2.0).sub(basement).mul(0.5))
|
||||||
} else {
|
} else {
|
||||||
|
8
world/src/index.rs
Normal file
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;
|
pub mod civ;
|
||||||
mod column;
|
mod column;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
pub mod index;
|
||||||
pub mod layer;
|
pub mod layer;
|
||||||
pub mod sim;
|
pub mod sim;
|
||||||
|
pub mod sim2;
|
||||||
pub mod site;
|
pub mod site;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
@ -19,6 +21,7 @@ pub use crate::config::CONFIG;
|
|||||||
use crate::{
|
use crate::{
|
||||||
block::BlockGen,
|
block::BlockGen,
|
||||||
column::{ColumnGen, ColumnSample},
|
column::{ColumnGen, ColumnSample},
|
||||||
|
index::Index,
|
||||||
util::{Grid, Sampler},
|
util::{Grid, Sampler},
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
@ -39,26 +42,35 @@ pub enum Error {
|
|||||||
pub struct World {
|
pub struct World {
|
||||||
sim: sim::WorldSim,
|
sim: sim::WorldSim,
|
||||||
civs: civ::Civs,
|
civs: civ::Civs,
|
||||||
|
index: Index,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl World {
|
impl World {
|
||||||
pub fn generate(seed: u32, opts: sim::WorldOpts) -> Self {
|
pub fn generate(seed: u32, opts: sim::WorldOpts) -> Self {
|
||||||
let mut sim = sim::WorldSim::generate(seed, opts);
|
let mut sim = sim::WorldSim::generate(seed, opts);
|
||||||
let civs = civ::Civs::generate(seed, &mut sim);
|
let mut index = Index::default();
|
||||||
Self { sim, civs }
|
let civs = civ::Civs::generate(seed, &mut sim, &mut index);
|
||||||
|
|
||||||
|
sim2::simulate(&mut index, &mut sim);
|
||||||
|
|
||||||
|
Self { sim, civs, index }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sim(&self) -> &sim::WorldSim { &self.sim }
|
pub fn sim(&self) -> &sim::WorldSim { &self.sim }
|
||||||
|
|
||||||
pub fn civs(&self) -> &civ::Civs { &self.civs }
|
pub fn civs(&self) -> &civ::Civs { &self.civs }
|
||||||
|
|
||||||
|
pub fn index(&self) -> &Index { &self.index }
|
||||||
|
|
||||||
pub fn tick(&self, _dt: Duration) {
|
pub fn tick(&self, _dt: Duration) {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_map_data(&self) -> Vec<u32> { self.sim.get_map(&self.index) }
|
||||||
|
|
||||||
pub fn sample_columns(
|
pub fn sample_columns(
|
||||||
&self,
|
&self,
|
||||||
) -> impl Sampler<Index = Vec2<i32>, Sample = Option<ColumnSample>> + '_ {
|
) -> impl Sampler<Index = (Vec2<i32>, &Index), Sample = Option<ColumnSample>> + '_ {
|
||||||
ColumnGen::new(&self.sim)
|
ColumnGen::new(&self.sim)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +89,7 @@ impl World {
|
|||||||
let grid_border = 4;
|
let grid_border = 4;
|
||||||
let zcache_grid = Grid::populate_from(
|
let zcache_grid = Grid::populate_from(
|
||||||
TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + grid_border * 2,
|
TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + grid_border * 2,
|
||||||
|offs| sampler.get_z_cache(chunk_wpos2d - grid_border + offs),
|
|offs| sampler.get_z_cache(chunk_wpos2d - grid_border + offs, &self.index),
|
||||||
);
|
);
|
||||||
|
|
||||||
let air = Block::empty();
|
let air = Block::empty();
|
||||||
@ -132,7 +144,8 @@ impl World {
|
|||||||
_ => continue,
|
_ => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
let (min_z, only_structures_min_z, max_z) = z_cache.get_z_limits(&mut sampler);
|
let (min_z, only_structures_min_z, max_z) =
|
||||||
|
z_cache.get_z_limits(&mut sampler, &self.index);
|
||||||
|
|
||||||
(base_z..min_z as i32).for_each(|z| {
|
(base_z..min_z as i32).for_each(|z| {
|
||||||
let _ = chunk.set(Vec3::new(x, y, z), stone);
|
let _ = chunk.set(Vec3::new(x, y, z), stone);
|
||||||
@ -144,7 +157,7 @@ impl World {
|
|||||||
let only_structures = lpos.z >= only_structures_min_z as i32;
|
let only_structures = lpos.z >= only_structures_min_z as i32;
|
||||||
|
|
||||||
if let Some(block) =
|
if let Some(block) =
|
||||||
sampler.get_with_z_cache(wpos, Some(&z_cache), only_structures)
|
sampler.get_with_z_cache(wpos, Some(&z_cache), only_structures, &self.index)
|
||||||
{
|
{
|
||||||
let _ = chunk.set(lpos, block);
|
let _ = chunk.set(lpos, block);
|
||||||
}
|
}
|
||||||
@ -163,10 +176,9 @@ impl World {
|
|||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
// Apply site generation
|
// Apply site generation
|
||||||
sim_chunk
|
sim_chunk.sites.iter().for_each(|site| {
|
||||||
.sites
|
self.index.sites[*site].apply_to(chunk_wpos2d, sample_get, &mut chunk)
|
||||||
.iter()
|
});
|
||||||
.for_each(|site| site.apply_to(chunk_wpos2d, sample_get, &mut chunk));
|
|
||||||
|
|
||||||
// Apply paths
|
// Apply paths
|
||||||
layer::apply_paths_to(chunk_wpos2d, sample_get, &mut chunk);
|
layer::apply_paths_to(chunk_wpos2d, sample_get, &mut chunk);
|
||||||
@ -217,7 +229,12 @@ impl World {
|
|||||||
|
|
||||||
// Apply site supplementary information
|
// Apply site supplementary information
|
||||||
sim_chunk.sites.iter().for_each(|site| {
|
sim_chunk.sites.iter().for_each(|site| {
|
||||||
site.apply_supplement(&mut rng, chunk_wpos2d, sample_get, &mut supplement)
|
self.index.sites[*site].apply_supplement(
|
||||||
|
&mut rng,
|
||||||
|
chunk_wpos2d,
|
||||||
|
sample_get,
|
||||||
|
&mut supplement,
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok((chunk, supplement))
|
Ok((chunk, supplement))
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
sim::{RiverKind, WorldSim, WORLD_SIZE},
|
sim::{RiverKind, WorldSim, WORLD_SIZE},
|
||||||
CONFIG,
|
Index, CONFIG,
|
||||||
};
|
};
|
||||||
use common::{terrain::TerrainChunkSize, vol::RectVolSize};
|
use common::{terrain::TerrainChunkSize, vol::RectVolSize};
|
||||||
use std::{f32, f64};
|
use std::{f32, f64};
|
||||||
@ -114,6 +114,7 @@ impl MapConfig {
|
|||||||
pub fn generate(
|
pub fn generate(
|
||||||
&self,
|
&self,
|
||||||
sampler: &WorldSim,
|
sampler: &WorldSim,
|
||||||
|
index: &Index,
|
||||||
mut write_pixel: impl FnMut(Vec2<usize>, (u8, u8, u8, u8)),
|
mut write_pixel: impl FnMut(Vec2<usize>, (u8, u8, u8, u8)),
|
||||||
) -> MapDebug {
|
) -> MapDebug {
|
||||||
let MapConfig {
|
let MapConfig {
|
||||||
@ -170,7 +171,8 @@ impl MapConfig {
|
|||||||
sample.river.river_kind,
|
sample.river.river_kind,
|
||||||
sample.path.is_path(),
|
sample.path.is_path(),
|
||||||
sample.sites.iter().any(|site| {
|
sample.sites.iter().any(|site| {
|
||||||
site.get_origin()
|
index.sites[*site]
|
||||||
|
.get_origin()
|
||||||
.distance_squared(pos * TerrainChunkSize::RECT_SIZE.x as i32)
|
.distance_squared(pos * TerrainChunkSize::RECT_SIZE.x as i32)
|
||||||
< 64i32.pow(2)
|
< 64i32.pow(2)
|
||||||
}),
|
}),
|
||||||
|
@ -28,7 +28,7 @@ use crate::{
|
|||||||
civ::Place,
|
civ::Place,
|
||||||
site::Site,
|
site::Site,
|
||||||
util::{seed_expan, FastNoise, RandomField, StructureGen2d, LOCALITY, NEIGHBORS},
|
util::{seed_expan, FastNoise, RandomField, StructureGen2d, LOCALITY, NEIGHBORS},
|
||||||
CONFIG,
|
Index, CONFIG,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
assets,
|
assets,
|
||||||
@ -1305,14 +1305,14 @@ impl WorldSim {
|
|||||||
|
|
||||||
/// Draw a map of the world based on chunk information. Returns a buffer of
|
/// Draw a map of the world based on chunk information. Returns a buffer of
|
||||||
/// u32s.
|
/// u32s.
|
||||||
pub fn get_map(&self) -> Vec<u32> {
|
pub fn get_map(&self, index: &Index) -> Vec<u32> {
|
||||||
let mut v = vec![0u32; WORLD_SIZE.x * WORLD_SIZE.y];
|
let mut v = vec![0u32; WORLD_SIZE.x * WORLD_SIZE.y];
|
||||||
// TODO: Parallelize again.
|
// TODO: Parallelize again.
|
||||||
MapConfig {
|
MapConfig {
|
||||||
gain: self.max_height,
|
gain: self.max_height,
|
||||||
..MapConfig::default()
|
..MapConfig::default()
|
||||||
}
|
}
|
||||||
.generate(&self, |pos, (r, g, b, a)| {
|
.generate(&self, index, |pos, (r, g, b, a)| {
|
||||||
v[pos.y * WORLD_SIZE.x + pos.x] = u32::from_le_bytes([r, g, b, a]);
|
v[pos.y * WORLD_SIZE.x + pos.x] = u32::from_le_bytes([r, g, b, a]);
|
||||||
});
|
});
|
||||||
v
|
v
|
||||||
@ -1788,7 +1788,7 @@ pub struct SimChunk {
|
|||||||
pub river: RiverData,
|
pub river: RiverData,
|
||||||
pub warp_factor: f32,
|
pub warp_factor: f32,
|
||||||
|
|
||||||
pub sites: Vec<Site>,
|
pub sites: Vec<Id<Site>>,
|
||||||
pub place: Option<Id<Place>>,
|
pub place: Option<Id<Place>>,
|
||||||
pub path: PathData,
|
pub path: PathData,
|
||||||
pub contains_waypoint: bool,
|
pub contains_waypoint: bool,
|
||||||
|
250
world/src/sim2/mod.rs
Normal file
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);
|
this.create_rooms(ctx, level, 7);
|
||||||
// Create routes between all rooms
|
// Create routes between all rooms
|
||||||
let room_areas = this.rooms.iter().map(|r| r.area).collect::<Vec<_>>();
|
let room_areas = this.rooms.values().map(|r| r.area).collect::<Vec<_>>();
|
||||||
for a in room_areas.iter() {
|
for a in room_areas.iter() {
|
||||||
for b in room_areas.iter() {
|
for b in room_areas.iter() {
|
||||||
this.create_route(ctx, a.center(), b.center());
|
this.create_route(ctx, a.center(), b.center());
|
||||||
@ -342,7 +342,7 @@ impl Floor {
|
|||||||
// Ensure no overlap
|
// Ensure no overlap
|
||||||
if self
|
if self
|
||||||
.rooms
|
.rooms
|
||||||
.iter()
|
.values()
|
||||||
.any(|r| r.area.collides_with_rect(area_border))
|
.any(|r| r.area.collides_with_rect(area_border))
|
||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
|
145
world/src/site/economy.rs
Normal file
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;
|
mod dungeon;
|
||||||
|
pub mod economy;
|
||||||
mod settlement;
|
mod settlement;
|
||||||
|
|
||||||
// Reexports
|
// Reexports
|
||||||
pub use self::{dungeon::Dungeon, settlement::Settlement};
|
pub use self::{block_mask::BlockMask, dungeon::Dungeon, economy::Economy, settlement::Settlement};
|
||||||
|
|
||||||
use crate::column::ColumnSample;
|
use crate::column::ColumnSample;
|
||||||
use common::{
|
use common::{
|
||||||
@ -14,44 +16,6 @@ use rand::Rng;
|
|||||||
use std::{fmt, sync::Arc};
|
use std::{fmt, sync::Arc};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub struct BlockMask {
|
|
||||||
block: Block,
|
|
||||||
priority: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BlockMask {
|
|
||||||
pub fn new(block: Block, priority: i32) -> Self { Self { block, priority } }
|
|
||||||
|
|
||||||
pub fn nothing() -> Self {
|
|
||||||
Self {
|
|
||||||
block: Block::empty(),
|
|
||||||
priority: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_priority(mut self, priority: i32) -> Self {
|
|
||||||
self.priority = priority;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resolve_with(self, other: Self) -> Self {
|
|
||||||
if self.priority >= other.priority {
|
|
||||||
self
|
|
||||||
} else {
|
|
||||||
other
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn finish(self) -> Option<Block> {
|
|
||||||
if self.priority > 0 {
|
|
||||||
Some(self.block)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SpawnRules {
|
pub struct SpawnRules {
|
||||||
pub trees: bool,
|
pub trees: bool,
|
||||||
}
|
}
|
||||||
@ -60,31 +24,49 @@ impl Default for SpawnRules {
|
|||||||
fn default() -> Self { Self { trees: true } }
|
fn default() -> Self { Self { trees: true } }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
pub struct Site {
|
||||||
pub enum Site {
|
pub kind: SiteKind,
|
||||||
Settlement(Arc<Settlement>),
|
pub economy: Economy,
|
||||||
Dungeon(Arc<Dungeon>),
|
}
|
||||||
|
|
||||||
|
pub enum SiteKind {
|
||||||
|
Settlement(Settlement),
|
||||||
|
Dungeon(Dungeon),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Site {
|
impl Site {
|
||||||
|
pub fn settlement(s: Settlement) -> Self {
|
||||||
|
Self {
|
||||||
|
kind: SiteKind::Settlement(s),
|
||||||
|
economy: Economy::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dungeon(d: Dungeon) -> Self {
|
||||||
|
Self {
|
||||||
|
kind: SiteKind::Dungeon(d),
|
||||||
|
economy: Economy::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn radius(&self) -> f32 {
|
pub fn radius(&self) -> f32 {
|
||||||
match self {
|
match &self.kind {
|
||||||
Site::Settlement(settlement) => settlement.radius(),
|
SiteKind::Settlement(settlement) => settlement.radius(),
|
||||||
Site::Dungeon(dungeon) => dungeon.radius(),
|
SiteKind::Dungeon(dungeon) => dungeon.radius(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_origin(&self) -> Vec2<i32> {
|
pub fn get_origin(&self) -> Vec2<i32> {
|
||||||
match self {
|
match &self.kind {
|
||||||
Site::Settlement(s) => s.get_origin(),
|
SiteKind::Settlement(s) => s.get_origin(),
|
||||||
Site::Dungeon(d) => d.get_origin(),
|
SiteKind::Dungeon(d) => d.get_origin(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
|
pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
|
||||||
match self {
|
match &self.kind {
|
||||||
Site::Settlement(s) => s.spawn_rules(wpos),
|
SiteKind::Settlement(s) => s.spawn_rules(wpos),
|
||||||
Site::Dungeon(d) => d.spawn_rules(wpos),
|
SiteKind::Dungeon(d) => d.spawn_rules(wpos),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,9 +76,9 @@ impl Site {
|
|||||||
get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
|
get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
|
||||||
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
|
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
|
||||||
) {
|
) {
|
||||||
match self {
|
match &self.kind {
|
||||||
Site::Settlement(settlement) => settlement.apply_to(wpos2d, get_column, vol),
|
SiteKind::Settlement(settlement) => settlement.apply_to(wpos2d, get_column, vol),
|
||||||
Site::Dungeon(dungeon) => dungeon.apply_to(wpos2d, get_column, vol),
|
SiteKind::Dungeon(dungeon) => dungeon.apply_to(wpos2d, get_column, vol),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,28 +89,13 @@ impl Site {
|
|||||||
get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
|
get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
|
||||||
supplement: &mut ChunkSupplement,
|
supplement: &mut ChunkSupplement,
|
||||||
) {
|
) {
|
||||||
match self {
|
match &self.kind {
|
||||||
Site::Settlement(settlement) => {
|
SiteKind::Settlement(settlement) => {
|
||||||
settlement.apply_supplement(rng, wpos2d, get_column, supplement)
|
settlement.apply_supplement(rng, wpos2d, get_column, supplement)
|
||||||
},
|
},
|
||||||
Site::Dungeon(dungeon) => dungeon.apply_supplement(rng, wpos2d, get_column, supplement),
|
SiteKind::Dungeon(dungeon) => {
|
||||||
}
|
dungeon.apply_supplement(rng, wpos2d, get_column, supplement)
|
||||||
}
|
},
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Settlement> for Site {
|
|
||||||
fn from(settlement: Settlement) -> Self { Site::Settlement(Arc::new(settlement)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Dungeon> for Site {
|
|
||||||
fn from(dungeon: Dungeon) -> Self { Site::Dungeon(Arc::new(dungeon)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for Site {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Site::Settlement(_) => write!(f, "Settlement"),
|
|
||||||
Site::Dungeon(_) => write!(f, "Dungeon"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ use common::{
|
|||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
const COLOR_THEMES: [Rgb<u8>; 11] = [
|
const COLOR_THEMES: [Rgb<u8>; 17] = [
|
||||||
Rgb::new(0x1D, 0x4D, 0x45),
|
Rgb::new(0x1D, 0x4D, 0x45),
|
||||||
Rgb::new(0xB3, 0x7D, 0x60),
|
Rgb::new(0xB3, 0x7D, 0x60),
|
||||||
Rgb::new(0xAC, 0x5D, 0x26),
|
Rgb::new(0xAC, 0x5D, 0x26),
|
||||||
@ -24,6 +24,12 @@ const COLOR_THEMES: [Rgb<u8>; 11] = [
|
|||||||
Rgb::new(0x2F, 0x32, 0x47),
|
Rgb::new(0x2F, 0x32, 0x47),
|
||||||
Rgb::new(0x8F, 0x35, 0x43),
|
Rgb::new(0x8F, 0x35, 0x43),
|
||||||
Rgb::new(0x6D, 0x1E, 0x3A),
|
Rgb::new(0x6D, 0x1E, 0x3A),
|
||||||
|
Rgb::new(0x6D, 0xA7, 0x80),
|
||||||
|
Rgb::new(0x4F, 0xA0, 0x95),
|
||||||
|
Rgb::new(0xE2, 0xB9, 0x99),
|
||||||
|
Rgb::new(0x7A, 0x30, 0x22),
|
||||||
|
Rgb::new(0x4A, 0x06, 0x08),
|
||||||
|
Rgb::new(0x8E, 0xB4, 0x57),
|
||||||
];
|
];
|
||||||
|
|
||||||
pub struct House {
|
pub struct House {
|
||||||
@ -149,10 +155,7 @@ impl Archetype for House {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let this = Self {
|
let this = Self {
|
||||||
roof_color: COLOR_THEMES
|
roof_color: *COLOR_THEMES.choose(rng).unwrap(),
|
||||||
.choose(rng)
|
|
||||||
.unwrap()
|
|
||||||
.map(|e| e.saturating_add(rng.gen_range(0, 20)) - 10),
|
|
||||||
noise: RandomField::new(rng.gen()),
|
noise: RandomField::new(rng.gen()),
|
||||||
roof_ribbing: rng.gen(),
|
roof_ribbing: rng.gen(),
|
||||||
roof_ribbing_diagonal: rng.gen(),
|
roof_ribbing_diagonal: rng.gen(),
|
||||||
|
@ -9,27 +9,38 @@ use vek::*;
|
|||||||
|
|
||||||
pub struct Keep;
|
pub struct Keep;
|
||||||
|
|
||||||
|
pub struct Attr {
|
||||||
|
height: i32,
|
||||||
|
is_tower: bool,
|
||||||
|
}
|
||||||
|
|
||||||
impl Archetype for Keep {
|
impl Archetype for Keep {
|
||||||
type Attr = ();
|
type Attr = Attr;
|
||||||
|
|
||||||
fn generate<R: Rng>(rng: &mut R) -> (Self, Skeleton<Self::Attr>) {
|
fn generate<R: Rng>(rng: &mut R) -> (Self, Skeleton<Self::Attr>) {
|
||||||
let len = rng.gen_range(-8, 20).max(0);
|
let len = rng.gen_range(-8, 24).max(0);
|
||||||
let skel = Skeleton {
|
let skel = Skeleton {
|
||||||
offset: -rng.gen_range(0, len + 7).clamped(0, len),
|
offset: -rng.gen_range(0, len + 7).clamped(0, len),
|
||||||
ori: if rng.gen() { Ori::East } else { Ori::North },
|
ori: if rng.gen() { Ori::East } else { Ori::North },
|
||||||
root: Branch {
|
root: Branch {
|
||||||
len,
|
len,
|
||||||
attr: Self::Attr::default(),
|
attr: Attr {
|
||||||
locus: 6 + rng.gen_range(0, 5),
|
height: rng.gen_range(12, 16),
|
||||||
|
is_tower: false,
|
||||||
|
},
|
||||||
|
locus: 10 + rng.gen_range(0, 5),
|
||||||
border: 3,
|
border: 3,
|
||||||
children: (0..1)
|
children: (0..1)
|
||||||
.map(|_| {
|
.map(|_| {
|
||||||
(
|
(
|
||||||
rng.gen_range(-5, len + 5).clamped(0, len.max(1) - 1),
|
rng.gen_range(-5, len + 5).clamped(0, len.max(1) - 1),
|
||||||
Branch {
|
Branch {
|
||||||
len: rng.gen_range(5, 12) * if rng.gen() { 1 } else { -1 },
|
len: 0,
|
||||||
attr: Self::Attr::default(),
|
attr: Attr {
|
||||||
locus: 5 + rng.gen_range(0, 3),
|
height: rng.gen_range(20, 28),
|
||||||
|
is_tower: true,
|
||||||
|
},
|
||||||
|
locus: 4 + rng.gen_range(0, 5),
|
||||||
border: 3,
|
border: 3,
|
||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
},
|
},
|
||||||
@ -48,7 +59,7 @@ impl Archetype for Keep {
|
|||||||
pos: Vec3<i32>,
|
pos: Vec3<i32>,
|
||||||
dist: i32,
|
dist: i32,
|
||||||
bound_offset: Vec2<i32>,
|
bound_offset: Vec2<i32>,
|
||||||
_center_offset: Vec2<i32>,
|
center_offset: Vec2<i32>,
|
||||||
z: i32,
|
z: i32,
|
||||||
ori: Ori,
|
ori: Ori,
|
||||||
branch: &Branch<Self::Attr>,
|
branch: &Branch<Self::Attr>,
|
||||||
@ -60,18 +71,24 @@ impl Archetype for Keep {
|
|||||||
let important_layer = normal_layer + 1;
|
let important_layer = normal_layer + 1;
|
||||||
let internal_layer = important_layer + 1;
|
let internal_layer = important_layer + 1;
|
||||||
|
|
||||||
let make_block =
|
let make_block = |r, g, b| {
|
||||||
|r, g, b| BlockMask::new(Block::new(BlockKind::Normal, Rgb::new(r, g, b)), normal_layer);
|
BlockMask::new(
|
||||||
|
Block::new(BlockKind::Normal, Rgb::new(r, g, b)),
|
||||||
|
normal_layer,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
let foundation = make_block(100, 100, 100);
|
let foundation = make_block(100, 100, 100);
|
||||||
let wall = make_block(100, 100, 110);
|
let wall = make_block(100, 100, 110);
|
||||||
let floor = make_block(120, 80, 50).with_priority(important_layer);
|
let floor = make_block(120, 80, 50).with_priority(important_layer);
|
||||||
|
let pole = make_block(90, 70, 50).with_priority(important_layer);
|
||||||
|
let flag = make_block(50, 170, 100).with_priority(important_layer);
|
||||||
let internal = BlockMask::new(Block::empty(), internal_layer);
|
let internal = BlockMask::new(Block::empty(), internal_layer);
|
||||||
let empty = BlockMask::nothing();
|
let empty = BlockMask::nothing();
|
||||||
|
|
||||||
let width = branch.locus;
|
let width = branch.locus;
|
||||||
let rampart_width = 2 + branch.locus;
|
let rampart_width = 2 + branch.locus;
|
||||||
let ceil_height = 12;
|
let ceil_height = branch.attr.height;
|
||||||
let door_height = 6;
|
let door_height = 6;
|
||||||
let edge_pos = if (bound_offset.x == rampart_width) ^ (ori == Ori::East) {
|
let edge_pos = if (bound_offset.x == rampart_width) ^ (ori == Ori::East) {
|
||||||
pos.y
|
pos.y
|
||||||
@ -79,26 +96,49 @@ impl Archetype for Keep {
|
|||||||
pos.x
|
pos.x
|
||||||
};
|
};
|
||||||
let rampart_height = ceil_height + if edge_pos % 2 == 0 { 3 } else { 4 };
|
let rampart_height = ceil_height + if edge_pos % 2 == 0 { 3 } else { 4 };
|
||||||
let min_dist = bound_offset.reduce_max();
|
let inner = Clamp::clamp(
|
||||||
|
center_offset,
|
||||||
|
Vec2::new(-5, -branch.len / 2 - 5),
|
||||||
|
Vec2::new(5, branch.len / 2 + 5),
|
||||||
|
);
|
||||||
|
let min_dist = bound_offset.map(|e| e.pow(2) as f32).sum().powf(0.5) as i32; //(bound_offset.distance_squared(inner) as f32).sqrt() as i32 + 5;//bound_offset.reduce_max();
|
||||||
|
|
||||||
if profile.y <= 0 - (min_dist - width - 1).max(0) && min_dist < width + 3 {
|
if profile.y <= 0 - (min_dist - width - 1).max(0) && min_dist < width + 3 {
|
||||||
// Foundations
|
// Foundations
|
||||||
foundation
|
foundation
|
||||||
} else if profile.y == ceil_height && min_dist < rampart_width {
|
} else if profile.y == ceil_height && min_dist < rampart_width {
|
||||||
if min_dist < width {
|
if min_dist < width { floor } else { wall }
|
||||||
floor
|
} else if !branch.attr.is_tower
|
||||||
} else {
|
&& bound_offset.x.abs() == 4
|
||||||
wall
|
&& min_dist == width + 1
|
||||||
}
|
&& profile.y < ceil_height
|
||||||
} else if bound_offset.x.abs() == 4 && min_dist == width + 1 && profile.y < ceil_height {
|
{
|
||||||
wall
|
wall
|
||||||
} else if bound_offset.x.abs() < 3 && profile.y < door_height - bound_offset.x.abs() && profile.y > 0 {
|
} else if bound_offset.x.abs() < 3
|
||||||
|
&& profile.y < door_height - bound_offset.x.abs()
|
||||||
|
&& profile.y > 0
|
||||||
|
{
|
||||||
internal
|
internal
|
||||||
} else if min_dist == width && profile.y <= ceil_height {
|
} else if min_dist == width && profile.y <= ceil_height {
|
||||||
wall
|
wall
|
||||||
} else if profile.y >= ceil_height {
|
} else if profile.y >= ceil_height {
|
||||||
if profile.y > ceil_height && min_dist < rampart_width {
|
if profile.y > ceil_height && min_dist < rampart_width {
|
||||||
internal
|
if branch.attr.is_tower
|
||||||
|
&& center_offset == Vec2::zero()
|
||||||
|
&& profile.y < ceil_height + 16
|
||||||
|
{
|
||||||
|
pole
|
||||||
|
} else if branch.attr.is_tower
|
||||||
|
&& center_offset.x == 0
|
||||||
|
&& center_offset.y > 0
|
||||||
|
&& center_offset.y < 8
|
||||||
|
&& profile.y > ceil_height + 8
|
||||||
|
&& profile.y < ceil_height + 14
|
||||||
|
{
|
||||||
|
flag
|
||||||
|
} else {
|
||||||
|
empty
|
||||||
|
}
|
||||||
} else if min_dist == rampart_width {
|
} else if min_dist == rampart_width {
|
||||||
if profile.y < rampart_height {
|
if profile.y < rampart_height {
|
||||||
wall
|
wall
|
||||||
|
@ -12,7 +12,6 @@ use vek::*;
|
|||||||
pub type HouseBuilding = Building<archetype::house::House>;
|
pub type HouseBuilding = Building<archetype::house::House>;
|
||||||
pub type KeepBuilding = Building<archetype::keep::Keep>;
|
pub type KeepBuilding = Building<archetype::keep::Keep>;
|
||||||
|
|
||||||
|
|
||||||
pub struct Building<A: Archetype> {
|
pub struct Building<A: Archetype> {
|
||||||
skel: Skeleton<A::Attr>,
|
skel: Skeleton<A::Attr>,
|
||||||
archetype: A,
|
archetype: A,
|
||||||
@ -44,7 +43,7 @@ impl<A: Archetype> Building<A> {
|
|||||||
let aabr = self.bounds_2d();
|
let aabr = self.bounds_2d();
|
||||||
Aabb {
|
Aabb {
|
||||||
min: Vec3::from(aabr.min) + Vec3::unit_z() * (self.origin.z - 8),
|
min: Vec3::from(aabr.min) + Vec3::unit_z() * (self.origin.z - 8),
|
||||||
max: Vec3::from(aabr.max) + Vec3::unit_z() * (self.origin.z + 32),
|
max: Vec3::from(aabr.max) + Vec3::unit_z() * (self.origin.z + 48),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
mod building;
|
mod building;
|
||||||
|
mod town;
|
||||||
|
|
||||||
use self::building::{HouseBuilding, KeepBuilding};
|
use self::{
|
||||||
|
building::{HouseBuilding, KeepBuilding},
|
||||||
|
town::{District, Town},
|
||||||
|
};
|
||||||
use super::SpawnRules;
|
use super::SpawnRules;
|
||||||
use crate::{
|
use crate::{
|
||||||
column::ColumnSample,
|
column::ColumnSample,
|
||||||
@ -84,7 +88,6 @@ fn to_tile(e: i32) -> i32 { ((e as f32).div_euclid(AREA_SIZE as f32)).floor() as
|
|||||||
pub enum StructureKind {
|
pub enum StructureKind {
|
||||||
House(HouseBuilding),
|
House(HouseBuilding),
|
||||||
Keep(KeepBuilding),
|
Keep(KeepBuilding),
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Structure {
|
pub struct Structure {
|
||||||
@ -96,7 +99,6 @@ impl Structure {
|
|||||||
match &self.kind {
|
match &self.kind {
|
||||||
StructureKind::House(house) => house.bounds_2d(),
|
StructureKind::House(house) => house.bounds_2d(),
|
||||||
StructureKind::Keep(keep) => keep.bounds_2d(),
|
StructureKind::Keep(keep) => keep.bounds_2d(),
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +106,6 @@ impl Structure {
|
|||||||
match &self.kind {
|
match &self.kind {
|
||||||
StructureKind::House(house) => house.bounds(),
|
StructureKind::House(house) => house.bounds(),
|
||||||
StructureKind::Keep(keep) => keep.bounds(),
|
StructureKind::Keep(keep) => keep.bounds(),
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +113,6 @@ impl Structure {
|
|||||||
match &self.kind {
|
match &self.kind {
|
||||||
StructureKind::House(house) => house.sample(rpos),
|
StructureKind::House(house) => house.sample(rpos),
|
||||||
StructureKind::Keep(keep) => keep.sample(rpos),
|
StructureKind::Keep(keep) => keep.sample(rpos),
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -127,10 +127,6 @@ pub struct Settlement {
|
|||||||
noise: RandomField,
|
noise: RandomField,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Town {
|
|
||||||
base_tile: Vec2<i32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Farm {
|
pub struct Farm {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
base_tile: Vec2<i32>,
|
base_tile: Vec2<i32>,
|
||||||
@ -280,12 +276,28 @@ impl Settlement {
|
|||||||
Some(Plot::Dirt) => true,
|
Some(Plot::Dirt) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}) {
|
}) {
|
||||||
self.land
|
// self.land
|
||||||
.plot_at_mut(base_tile)
|
// .plot_at_mut(base_tile)
|
||||||
.map(|plot| *plot = Plot::Town);
|
// .map(|plot| *plot = Plot::Town { district: None });
|
||||||
|
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
self.town = Some(Town { base_tile });
|
let town = Town::generate(self.origin, base_tile, ctx);
|
||||||
|
|
||||||
|
for (id, district) in town.districts().iter() {
|
||||||
|
let district_plot =
|
||||||
|
self.land.plots.insert(Plot::Town { district: Some(id) });
|
||||||
|
|
||||||
|
for x in district.aabr.min.x..district.aabr.max.x {
|
||||||
|
for y in district.aabr.min.y..district.aabr.max.y {
|
||||||
|
if !matches!(self.land.plot_at(Vec2::new(x, y)), Some(Plot::Hazard))
|
||||||
|
{
|
||||||
|
self.land.set(Vec2::new(x, y), district_plot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.town = Some(town);
|
||||||
origin = base_tile;
|
origin = base_tile;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -346,27 +358,25 @@ impl Settlement {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
for (i, tile) in Spiral2d::new()
|
for tile in Spiral2d::new()
|
||||||
.map(|offs| town_center + offs)
|
.map(|offs| town_center + offs)
|
||||||
.take(16usize.pow(2))
|
.take(16usize.pow(2))
|
||||||
.enumerate()
|
|
||||||
{
|
{
|
||||||
// This is a stupid way to decide how to place buildings
|
// This is a stupid way to decide how to place buildings
|
||||||
for _ in 0..ctx.rng.gen_range(2, 5) {
|
for i in 0..ctx.rng.gen_range(2, 5) {
|
||||||
for _ in 0..25 {
|
for _ in 0..25 {
|
||||||
let house_pos = tile.map(|e| e * AREA_SIZE as i32 + AREA_SIZE as i32 / 2)
|
let house_pos = tile.map(|e| e * AREA_SIZE as i32 + AREA_SIZE as i32 / 2)
|
||||||
+ Vec2::<i32>::zero().map(|_| {
|
+ Vec2::<i32>::zero().map(|_| {
|
||||||
ctx.rng
|
ctx.rng
|
||||||
.gen_range(-(AREA_SIZE as i32) / 2, AREA_SIZE as i32 / 2)
|
.gen_range(-(AREA_SIZE as i32) / 4, AREA_SIZE as i32 / 4)
|
||||||
});
|
});
|
||||||
|
|
||||||
let tile_pos = house_pos.map(|e| e.div_euclid(AREA_SIZE as i32));
|
let tile_pos = house_pos.map(|e| e.div_euclid(AREA_SIZE as i32));
|
||||||
if !matches!(self.land.plot_at(tile_pos), Some(Plot::Town))
|
if self
|
||||||
|| self
|
.land
|
||||||
.land
|
.tile_at(tile_pos)
|
||||||
.tile_at(tile_pos)
|
.map(|t| t.contains(WayKind::Path))
|
||||||
.map(|t| t.contains(WayKind::Path))
|
.unwrap_or(true)
|
||||||
.unwrap_or(true)
|
|
||||||
|| ctx
|
|| ctx
|
||||||
.sim
|
.sim
|
||||||
.and_then(|sim| sim.get_nearest_path(self.origin + house_pos))
|
.and_then(|sim| sim.get_nearest_path(self.origin + house_pos))
|
||||||
@ -376,30 +386,30 @@ impl Settlement {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let alt = if let Some(Plot::Town { district }) = self.land.plot_at(tile_pos) {
|
||||||
|
district
|
||||||
|
.and_then(|d| self.town.as_ref().map(|t| t.districts().get(d)))
|
||||||
|
.map(|d| d.alt)
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
ctx.sim
|
||||||
|
.and_then(|sim| sim.get_alt_approx(self.origin + house_pos))
|
||||||
|
.unwrap_or(0.0)
|
||||||
|
.ceil() as i32
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
let structure = Structure {
|
let structure = Structure {
|
||||||
kind: if i == 0 {
|
kind: if tile == town_center && i == 0 {
|
||||||
StructureKind::Keep(KeepBuilding::generate(
|
StructureKind::Keep(KeepBuilding::generate(
|
||||||
ctx.rng,
|
ctx.rng,
|
||||||
Vec3::new(
|
Vec3::new(house_pos.x, house_pos.y, alt),
|
||||||
house_pos.x,
|
|
||||||
house_pos.y,
|
|
||||||
ctx.sim
|
|
||||||
.and_then(|sim| sim.get_alt_approx(self.origin + house_pos))
|
|
||||||
.unwrap_or(0.0)
|
|
||||||
.ceil() as i32,
|
|
||||||
),
|
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
StructureKind::House(HouseBuilding::generate(
|
StructureKind::House(HouseBuilding::generate(
|
||||||
ctx.rng,
|
ctx.rng,
|
||||||
Vec3::new(
|
Vec3::new(house_pos.x, house_pos.y, alt),
|
||||||
house_pos.x,
|
|
||||||
house_pos.y,
|
|
||||||
ctx.sim
|
|
||||||
.and_then(|sim| sim.get_alt_approx(self.origin + house_pos))
|
|
||||||
.unwrap_or(0.0)
|
|
||||||
.ceil() as i32,
|
|
||||||
),
|
|
||||||
))
|
))
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -536,12 +546,13 @@ impl Settlement {
|
|||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let surface_z = col_sample.riverless_alt.floor() as i32;
|
let land_surface_z = col_sample.riverless_alt.floor() as i32;
|
||||||
|
let mut surface_z = land_surface_z;
|
||||||
|
|
||||||
// Sample settlement
|
// Sample settlement
|
||||||
let sample = self.land.get_at_block(rpos);
|
let sample = self.land.get_at_block(rpos);
|
||||||
|
|
||||||
let noisy_color = |col: Rgb<u8>, factor: u32| {
|
let noisy_color = move |col: Rgb<u8>, factor: u32| {
|
||||||
let nz = self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, surface_z));
|
let nz = self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, surface_z));
|
||||||
col.map(|e| {
|
col.map(|e| {
|
||||||
(e as u32 + nz % (factor * 2))
|
(e as u32 + nz % (factor * 2))
|
||||||
@ -550,6 +561,30 @@ impl Settlement {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// District alt
|
||||||
|
if let Some(Plot::Town { district }) = sample.plot {
|
||||||
|
if let Some(d) =
|
||||||
|
district.and_then(|d| self.town.as_ref().map(|t| t.districts().get(d)))
|
||||||
|
{
|
||||||
|
let other = self
|
||||||
|
.land
|
||||||
|
.plot_at(sample.second_closest)
|
||||||
|
.and_then(|p| match p {
|
||||||
|
Plot::Town { district } => *district,
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.and_then(|d| {
|
||||||
|
self.town.as_ref().map(|t| t.districts().get(d).alt as f32)
|
||||||
|
})
|
||||||
|
.unwrap_or(surface_z as f32);
|
||||||
|
surface_z = Lerp::lerp(
|
||||||
|
(other + d.alt as f32) / 2.0,
|
||||||
|
d.alt as f32,
|
||||||
|
(1.25 * sample.edge_dist / (d.alt as f32 - other).abs()).min(1.0),
|
||||||
|
) as i32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Paths
|
// Paths
|
||||||
if let Some((WayKind::Path, dist, nearest)) = sample.way {
|
if let Some((WayKind::Path, dist, nearest)) = sample.way {
|
||||||
let inset = -1;
|
let inset = -1;
|
||||||
@ -599,7 +634,7 @@ impl Settlement {
|
|||||||
Some(Plot::Dirt) => Some(Rgb::new(90, 70, 50)),
|
Some(Plot::Dirt) => Some(Rgb::new(90, 70, 50)),
|
||||||
Some(Plot::Grass) => Some(Rgb::new(100, 200, 0)),
|
Some(Plot::Grass) => Some(Rgb::new(100, 200, 0)),
|
||||||
Some(Plot::Water) => Some(Rgb::new(100, 150, 250)),
|
Some(Plot::Water) => Some(Rgb::new(100, 150, 250)),
|
||||||
Some(Plot::Town) => {
|
Some(Plot::Town { district }) => {
|
||||||
if let Some((_, path_nearest)) = col_sample.path {
|
if let Some((_, path_nearest)) = col_sample.path {
|
||||||
let path_dir = (path_nearest - wpos2d.map(|e| e as f32))
|
let path_dir = (path_nearest - wpos2d.map(|e| e as f32))
|
||||||
.rotated_z(f32::consts::PI / 2.0)
|
.rotated_z(f32::consts::PI / 2.0)
|
||||||
@ -693,7 +728,8 @@ impl Settlement {
|
|||||||
|
|
||||||
if let Some(color) = color {
|
if let Some(color) = color {
|
||||||
if col_sample.water_dist.map(|dist| dist > 2.0).unwrap_or(true) {
|
if col_sample.water_dist.map(|dist| dist > 2.0).unwrap_or(true) {
|
||||||
for z in -8..3 {
|
let diff = (surface_z - land_surface_z).abs();
|
||||||
|
for z in -8 - diff..3 + diff {
|
||||||
let pos = Vec3::new(offs.x, offs.y, surface_z + z);
|
let pos = Vec3::new(offs.x, offs.y, surface_z + z);
|
||||||
|
|
||||||
if let (0, Some(block)) = (z, surface_block) {
|
if let (0, Some(block)) = (z, surface_block) {
|
||||||
@ -767,7 +803,8 @@ impl Settlement {
|
|||||||
|
|
||||||
for x in bounds.min.x..bounds.max.x + 1 {
|
for x in bounds.min.x..bounds.max.x + 1 {
|
||||||
for y in bounds.min.y..bounds.max.y + 1 {
|
for y in bounds.min.y..bounds.max.y + 1 {
|
||||||
let col = if let Some(col) = get_column(self.origin + Vec2::new(x, y) - wpos2d) {
|
let col = if let Some(col) = get_column(self.origin + Vec2::new(x, y) - wpos2d)
|
||||||
|
{
|
||||||
col
|
col
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
@ -813,7 +850,7 @@ impl Settlement {
|
|||||||
|
|
||||||
let entity_wpos = Vec3::new(wpos2d.x as f32, wpos2d.y as f32, col_sample.alt + 3.0);
|
let entity_wpos = Vec3::new(wpos2d.x as f32, wpos2d.y as f32, col_sample.alt + 3.0);
|
||||||
|
|
||||||
if matches!(sample.plot, Some(Plot::Town))
|
if matches!(sample.plot, Some(Plot::Town { .. }))
|
||||||
&& RandomField::new(self.seed).chance(Vec3::from(wpos2d), 1.0 / (50.0 * 50.0))
|
&& RandomField::new(self.seed).chance(Vec3::from(wpos2d), 1.0 / (50.0 * 50.0))
|
||||||
{
|
{
|
||||||
let is_human: bool;
|
let is_human: bool;
|
||||||
@ -903,7 +940,7 @@ impl Settlement {
|
|||||||
Some(Plot::Dirt) => return Some(Rgb::new(90, 70, 50)),
|
Some(Plot::Dirt) => return Some(Rgb::new(90, 70, 50)),
|
||||||
Some(Plot::Grass) => return Some(Rgb::new(100, 200, 0)),
|
Some(Plot::Grass) => return Some(Rgb::new(100, 200, 0)),
|
||||||
Some(Plot::Water) => return Some(Rgb::new(100, 150, 250)),
|
Some(Plot::Water) => return Some(Rgb::new(100, 150, 250)),
|
||||||
Some(Plot::Town) => {
|
Some(Plot::Town { .. }) => {
|
||||||
return Some(Rgb::new(150, 110, 60).map2(Rgb::iota(), |e: u8, i: i32| {
|
return Some(Rgb::new(150, 110, 60).map2(Rgb::iota(), |e: u8, i: i32| {
|
||||||
e.saturating_add((self.noise.get(Vec3::new(pos.x, pos.y, i * 5)) % 16) as u8)
|
e.saturating_add((self.noise.get(Vec3::new(pos.x, pos.y, i * 5)) % 16) as u8)
|
||||||
.saturating_sub(8)
|
.saturating_sub(8)
|
||||||
@ -955,7 +992,9 @@ pub enum Plot {
|
|||||||
Dirt,
|
Dirt,
|
||||||
Grass,
|
Grass,
|
||||||
Water,
|
Water,
|
||||||
Town,
|
Town {
|
||||||
|
district: Option<Id<District>>,
|
||||||
|
},
|
||||||
Field {
|
Field {
|
||||||
farm: Id<Farm>,
|
farm: Id<Farm>,
|
||||||
seed: u32,
|
seed: u32,
|
||||||
@ -1015,6 +1054,8 @@ pub struct Sample<'a> {
|
|||||||
plot: Option<&'a Plot>,
|
plot: Option<&'a Plot>,
|
||||||
way: Option<(&'a WayKind, f32, Vec2<f32>)>,
|
way: Option<(&'a WayKind, f32, Vec2<f32>)>,
|
||||||
tower: Option<(&'a Tower, Vec2<i32>)>,
|
tower: Option<(&'a Tower, Vec2<i32>)>,
|
||||||
|
edge_dist: f32,
|
||||||
|
second_closest: Vec2<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Land {
|
pub struct Land {
|
||||||
@ -1049,6 +1090,15 @@ impl Land {
|
|||||||
.min_by_key(|(center, _)| center.distance_squared(pos))
|
.min_by_key(|(center, _)| center.distance_squared(pos))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.0;
|
.0;
|
||||||
|
let second_closest = neighbors
|
||||||
|
.iter()
|
||||||
|
.filter(|(center, _)| *center != closest)
|
||||||
|
.min_by_key(|(center, _)| center.distance_squared(pos))
|
||||||
|
.unwrap()
|
||||||
|
.0;
|
||||||
|
sample.second_closest = second_closest.map(to_tile);
|
||||||
|
sample.edge_dist = (second_closest - pos).map(|e| e as f32).magnitude()
|
||||||
|
- (closest - pos).map(|e| e as f32).magnitude();
|
||||||
|
|
||||||
let center_tile = self.tile_at(neighbors[4].0.map(to_tile));
|
let center_tile = self.tile_at(neighbors[4].0.map(to_tile));
|
||||||
|
|
||||||
|
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 fast_noise;
|
||||||
pub mod grid;
|
pub mod grid;
|
||||||
|
pub mod map_vec;
|
||||||
pub mod random;
|
pub mod random;
|
||||||
pub mod sampler;
|
pub mod sampler;
|
||||||
pub mod seed_expan;
|
pub mod seed_expan;
|
||||||
@ -11,6 +12,7 @@ pub mod unit_chooser;
|
|||||||
pub use self::{
|
pub use self::{
|
||||||
fast_noise::FastNoise,
|
fast_noise::FastNoise,
|
||||||
grid::Grid,
|
grid::Grid,
|
||||||
|
map_vec::MapVec,
|
||||||
random::{RandomField, RandomPerm},
|
random::{RandomField, RandomPerm},
|
||||||
sampler::{Sampler, SamplerMut},
|
sampler::{Sampler, SamplerMut},
|
||||||
small_cache::SmallCache,
|
small_cache::SmallCache,
|
||||||
@ -18,8 +20,15 @@ pub use self::{
|
|||||||
unit_chooser::UnitChooser,
|
unit_chooser::UnitChooser,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use fxhash::{FxHasher32, FxHasher64};
|
||||||
|
use hashbrown::{HashMap, HashSet};
|
||||||
|
use std::hash::BuildHasherDefault;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
|
// Deterministic HashMap and HashSet
|
||||||
|
pub type DHashMap<K, V> = HashMap<K, V, BuildHasherDefault<FxHasher32>>;
|
||||||
|
pub type DHashSet<T> = HashSet<T, BuildHasherDefault<FxHasher32>>;
|
||||||
|
|
||||||
pub fn attempt<T>(max_iters: usize, mut f: impl FnMut() -> Option<T>) -> Option<T> {
|
pub fn attempt<T>(max_iters: usize, mut f: impl FnMut() -> Option<T>) -> Option<T> {
|
||||||
(0..max_iters).find_map(|_| f())
|
(0..max_iters).find_map(|_| f())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user