Add more crops

This commit is contained in:
Monty Marz 2020-04-17 23:29:01 +00:00 committed by Joshua Barretto
parent 7d4443cbcd
commit 0fb3a115da
61 changed files with 1391 additions and 753 deletions

View File

@ -17,7 +17,7 @@ opt-level = 2
overflow-checks = true overflow-checks = true
debug-assertions = true debug-assertions = true
panic = "abort" panic = "abort"
debug = true debug = false
codegen-units = 8 codegen-units = 8
lto = false lto = false
incremental = true incremental = true

BIN
assets/voxygen/background/bg_10.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/background/bg_11.png (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/carrot/0.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/carrot/1.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/carrot/2.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/carrot/3.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/carrot/4.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/carrot/5.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/flax/flax-0.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/flax/flax-1.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/flax/flax-2.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/flax/flax-3.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/flax/flax-4.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/flax/flax-5.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/radish/0.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/radish/1.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/radish/2.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/radish/3.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/radish/4.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/tomato/0.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/tomato/1.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/tomato/2.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/tomato/3.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/tomato/4.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/turnip/turnip-0.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/turnip/turnip-1.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/turnip/turnip-2.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/turnip/turnip-3.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/turnip/turnip-4.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/turnip/turnip-5.vox (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -139,9 +139,7 @@ impl<S: Clone + Eq + Hash> Astar<S> {
} }
} }
pub fn get_cheapest_cost(&self) -> Option<f32> { pub fn get_cheapest_cost(&self) -> Option<f32> { self.cheapest_cost }
self.cheapest_cost
}
fn reconstruct_path_to(&mut self, end: S) -> Path<S> { fn reconstruct_path_to(&mut self, end: S) -> Path<S> {
let mut path = vec![end.clone()]; let mut path = vec![end.clone()];

View File

@ -47,7 +47,5 @@ pub struct ChunkSupplement {
} }
impl ChunkSupplement { impl ChunkSupplement {
pub fn add_entity(&mut self, entity: EntityInfo) { pub fn add_entity(&mut self, entity: EntityInfo) { self.entities.push(entity); }
self.entities.push(entity);
}
} }

View File

@ -28,13 +28,13 @@ pub mod region;
pub mod spiral; pub mod spiral;
pub mod state; pub mod state;
pub mod states; pub mod states;
pub mod store;
pub mod sync; pub mod sync;
pub mod sys; pub mod sys;
pub mod terrain; pub mod terrain;
pub mod util; pub mod util;
pub mod vol; pub mod vol;
pub mod volumes; pub mod volumes;
pub mod store;
/// The networking module containing high-level wrappers of `TcpListener` and /// The networking module containing high-level wrappers of `TcpListener` and
/// `TcpStream` (`PostOffice` and `PostBox` respectively) and data types used by /// `TcpStream` (`PostOffice` and `PostBox` respectively) and data types used by

View File

@ -26,9 +26,7 @@ impl<T> fmt::Debug for Id<T> {
} }
} }
impl<T> hash::Hash for Id<T> { impl<T> hash::Hash for Id<T> {
fn hash<H: hash::Hasher>(&self, h: &mut H) { fn hash<H: hash::Hasher>(&self, h: &mut H) { self.0.hash(h); }
self.0.hash(h);
}
} }
pub struct Store<T> { pub struct Store<T> {
@ -53,8 +51,7 @@ impl<T> Store<T> {
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> { self.items.iter_mut() } pub fn iter_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_ids(&self) -> impl Iterator<Item = (Id<T>, &T)> {
self self.items
.items
.iter() .iter()
.enumerate() .enumerate()
.map(|(i, item)| (Id(i, PhantomData), item)) .map(|(i, item)| (Id(i, PhantomData), item))

View File

@ -45,6 +45,10 @@ pub enum BlockKind {
WheatYellow, WheatYellow,
WheatGreen, WheatGreen,
Cabbage, Cabbage,
Flax,
Carrot,
Tomato,
Radish,
Coconut, Coconut,
} }
@ -91,7 +95,12 @@ impl BlockKind {
BlockKind::Corn => true, BlockKind::Corn => true,
BlockKind::WheatYellow => true, BlockKind::WheatYellow => true,
BlockKind::WheatGreen => true, BlockKind::WheatGreen => true,
BlockKind::Cabbage => true, BlockKind::Cabbage => false,
BlockKind::Pumpkin => false,
BlockKind::Flax => true,
BlockKind::Carrot => true,
BlockKind::Tomato => false,
BlockKind::Radish => true,
BlockKind::Coconut => true, BlockKind::Coconut => true,
_ => false, _ => false,
} }
@ -142,6 +151,10 @@ impl BlockKind {
BlockKind::WheatYellow => false, BlockKind::WheatYellow => false,
BlockKind::WheatGreen => false, BlockKind::WheatGreen => false,
BlockKind::Cabbage => false, BlockKind::Cabbage => false,
BlockKind::Flax => false,
BlockKind::Carrot => false,
BlockKind::Tomato => false,
BlockKind::Radish => false,
BlockKind::Coconut => false, BlockKind::Coconut => false,
_ => true, _ => true,
} }
@ -171,7 +184,7 @@ impl BlockKind {
BlockKind::Mushroom => false, BlockKind::Mushroom => false,
BlockKind::Liana => false, BlockKind::Liana => false,
BlockKind::Chest => true, BlockKind::Chest => true,
BlockKind::Pumpkin => true, BlockKind::Pumpkin => false,
BlockKind::Welwitch => false, BlockKind::Welwitch => false,
BlockKind::LingonBerry => false, BlockKind::LingonBerry => false,
BlockKind::LeafyPlant => false, BlockKind::LeafyPlant => false,
@ -183,6 +196,10 @@ impl BlockKind {
BlockKind::WheatYellow => false, BlockKind::WheatYellow => false,
BlockKind::WheatGreen => false, BlockKind::WheatGreen => false,
BlockKind::Cabbage => false, BlockKind::Cabbage => false,
BlockKind::Flax => false,
BlockKind::Carrot => false,
BlockKind::Tomato => true,
BlockKind::Radish => false,
BlockKind::Coconut => false, BlockKind::Coconut => false,
_ => true, _ => true,
} }
@ -205,7 +222,6 @@ impl BlockKind {
BlockKind::Velorite => true, BlockKind::Velorite => true,
BlockKind::VeloriteFrag => true, BlockKind::VeloriteFrag => true,
BlockKind::Chest => true, BlockKind::Chest => true,
BlockKind::Pumpkin => true,
BlockKind::Coconut => true, BlockKind::Coconut => true,
_ => false, _ => false,
} }

View File

@ -177,6 +177,8 @@ impl MainMenuUi {
"voxygen.background.bg_7", "voxygen.background.bg_7",
"voxygen.background.bg_8", "voxygen.background.bg_8",
"voxygen.background.bg_9", "voxygen.background.bg_9",
"voxygen.background.bg_10",
"voxygen.background.bg_11",
]; ];
let mut rng = thread_rng(); let mut rng = thread_rng();

View File

@ -88,7 +88,9 @@ impl Meshable<SpritePipeline, SpritePipeline> for Segment {
SpriteVertex::new( SpriteVertex::new(
origin, origin,
norm, norm,
linear_to_srgb(srgb_to_linear(col) * light.min(ao.powf(0.5) * 0.75 + 0.25)), linear_to_srgb(
srgb_to_linear(col) * light.min(ao.powf(0.5) * 0.75 + 0.25),
),
) )
}, },
&{ &{

View File

@ -197,11 +197,13 @@ fn calc_light<V: RectRasterableVol<Vox = Block> + ReadVol + Debug>(
None None
} else { } else {
let pos = wpos - outer.min; let pos = wpos - outer.min;
Some(light_map Some(
.get(lm_idx(pos.x, pos.y, pos.z)) light_map
.filter(|l| **l != OPAQUE && **l != UNKNOWN) .get(lm_idx(pos.x, pos.y, pos.z))
.map(|l| *l as f32 / SUNLIGHT as f32) .filter(|l| **l != OPAQUE && **l != UNKNOWN)
.unwrap_or(0.0)) .map(|l| *l as f32 / SUNLIGHT as f32)
.unwrap_or(0.0),
)
} }
} }
} }

View File

@ -45,11 +45,12 @@ fn get_ao_quad(
for x in 0..2 { for x in 0..2 {
for y in 0..2 { for y in 0..2 {
let dark_pos = shift + offs[0] * x + offs[1] * y + 1; let dark_pos = shift + offs[0] * x + offs[1] * y + 1;
if let Some(dark) = unsafe { darknesses if let Some(dark) = unsafe {
.get_unchecked(dark_pos.z as usize) darknesses
.get_unchecked(dark_pos.y as usize) .get_unchecked(dark_pos.z as usize)
.get_unchecked(dark_pos.x as usize) } .get_unchecked(dark_pos.y as usize)
{ .get_unchecked(dark_pos.x as usize)
} {
darkness += dark; darkness += dark;
total += 1.0; total += 1.0;
} }

View File

@ -240,7 +240,10 @@ impl Scene {
); );
// Tick camera for interpolation. // Tick camera for interpolation.
self.camera.update(scene_data.state.get_time(), scene_data.state.get_delta_time()); self.camera.update(
scene_data.state.get_time(),
scene_data.state.get_delta_time(),
);
// Compute camera matrices. // Compute camera matrices.
self.camera.compute_dependents(&*scene_data.state.terrain()); self.camera.compute_dependents(&*scene_data.state.terrain());

View File

@ -190,6 +190,22 @@ fn sprite_config_for(kind: BlockKind) -> Option<SpriteConfig> {
variations: 3, variations: 3,
wind_sway: 0.0, wind_sway: 0.0,
}), }),
BlockKind::Flax => Some(SpriteConfig {
variations: 6,
wind_sway: 0.4,
}),
BlockKind::Carrot => Some(SpriteConfig {
variations: 6,
wind_sway: 0.1,
}),
BlockKind::Tomato => Some(SpriteConfig {
variations: 5,
wind_sway: 0.0,
}),
BlockKind::Radish => Some(SpriteConfig {
variations: 5,
wind_sway: 0.1,
}),
BlockKind::Coconut => Some(SpriteConfig { BlockKind::Coconut => Some(SpriteConfig {
variations: 1, variations: 1,
wind_sway: 0.0, wind_sway: 0.0,
@ -1301,6 +1317,147 @@ impl<V: RectRasterableVol> Terrain<V> {
Vec3::new(-6.0, -6.0, 0.0), Vec3::new(-6.0, -6.0, 0.0),
), ),
), ),
// Flax
(
(BlockKind::Flax, 0),
make_model(
"voxygen.voxel.sprite.flax.flax-0",
Vec3::new(-6.0, -6.0, 0.0),
),
),
(
(BlockKind::Flax, 1),
make_model(
"voxygen.voxel.sprite.flax.flax-1",
Vec3::new(-6.0, -6.0, 0.0),
),
),
(
(BlockKind::Flax, 2),
make_model(
"voxygen.voxel.sprite.flax.flax-2",
Vec3::new(-6.0, -6.0, 0.0),
),
),
(
(BlockKind::Flax, 3),
make_model(
"voxygen.voxel.sprite.flax.flax-3",
Vec3::new(-6.0, -6.0, 0.0),
),
),
(
(BlockKind::Flax, 4),
make_model(
"voxygen.voxel.sprite.flax.flax-4",
Vec3::new(-6.0, -6.0, 0.0),
),
),
(
(BlockKind::Flax, 5),
make_model(
"voxygen.voxel.sprite.flax.flax-5",
Vec3::new(-6.0, -6.0, 0.0),
),
),
// Carrot
(
(BlockKind::Carrot, 0),
make_model(
"voxygen.voxel.sprite.carrot.0",
Vec3::new(-5.5, -5.5, -0.25),
),
),
(
(BlockKind::Carrot, 1),
make_model(
"voxygen.voxel.sprite.carrot.1",
Vec3::new(-5.5, -5.5, -0.25),
),
),
(
(BlockKind::Carrot, 2),
make_model(
"voxygen.voxel.sprite.carrot.2",
Vec3::new(-5.5, -5.5, -0.25),
),
),
(
(BlockKind::Carrot, 3),
make_model(
"voxygen.voxel.sprite.carrot.3",
Vec3::new(-5.5, -5.5, -0.25),
),
),
(
(BlockKind::Carrot, 4),
make_model(
"voxygen.voxel.sprite.carrot.4",
Vec3::new(-5.5, -5.5, -0.25),
),
),
(
(BlockKind::Carrot, 5),
make_model(
"voxygen.voxel.sprite.carrot.5",
Vec3::new(-5.5, -5.5, -0.25),
),
),
(
(BlockKind::Tomato, 0),
make_model("voxygen.voxel.sprite.tomato.0", Vec3::new(-5.5, -5.5, 0.0)),
),
(
(BlockKind::Tomato, 1),
make_model("voxygen.voxel.sprite.tomato.1", Vec3::new(-5.5, -5.5, 0.0)),
),
(
(BlockKind::Tomato, 2),
make_model("voxygen.voxel.sprite.tomato.2", Vec3::new(-5.5, -5.5, 0.0)),
),
(
(BlockKind::Tomato, 3),
make_model("voxygen.voxel.sprite.tomato.3", Vec3::new(-5.5, -5.5, 0.0)),
),
(
(BlockKind::Tomato, 4),
make_model("voxygen.voxel.sprite.tomato.4", Vec3::new(-5.5, -5.5, 0.0)),
),
(
(BlockKind::Radish, 0),
make_model(
"voxygen.voxel.sprite.radish.0",
Vec3::new(-5.5, -5.5, -0.25),
),
),
(
(BlockKind::Radish, 1),
make_model(
"voxygen.voxel.sprite.radish.1",
Vec3::new(-5.5, -5.5, -0.25),
),
),
(
(BlockKind::Radish, 2),
make_model(
"voxygen.voxel.sprite.radish.2",
Vec3::new(-5.5, -5.5, -0.25),
),
),
(
(BlockKind::Radish, 3),
make_model(
"voxygen.voxel.sprite.radish.3",
Vec3::new(-5.5, -5.5, -0.25),
),
),
(
(BlockKind::Radish, 4),
make_model(
"voxygen.voxel.sprite.radish.4",
Vec3::new(-5.5, -5.5, -0.25),
),
),
// Coconut // Coconut
( (
(BlockKind::Coconut, 0), (BlockKind::Coconut, 0),

View File

@ -150,10 +150,14 @@ fn main() {
} }
if win.get_mouse_down(minifb::MouseButton::Left) { if win.get_mouse_down(minifb::MouseButton::Left) {
if let Some((mx, my)) = win.get_mouse_pos(minifb::MouseMode::Clamp) { if let Some((mx, my)) = win.get_mouse_pos(minifb::MouseMode::Clamp) {
let chunk_pos = (Vec2::<f64>::from(focus) + (Vec2::new(mx as f64, my as f64) * scale)) let chunk_pos = (Vec2::<f64>::from(focus)
+ (Vec2::new(mx as f64, my as f64) * scale))
.map(|e| e as i32); .map(|e| e as i32);
let block_pos = chunk_pos.map2(TerrainChunkSize::RECT_SIZE, |e, f| e * f as i32); let block_pos = chunk_pos.map2(TerrainChunkSize::RECT_SIZE, |e, f| e * f as i32);
println!("Block: ({}, {}), Chunk: ({}, {})", block_pos.x, block_pos.y, chunk_pos.x, chunk_pos.y); println!(
"Block: ({}, {}), Chunk: ({}, {})",
block_pos.x, block_pos.y, chunk_pos.x, chunk_pos.y
);
if let Some(chunk) = sampler.get(chunk_pos) { if let Some(chunk) = sampler.get(chunk_pos) {
//println!("Chunk info: {:#?}", chunk); //println!("Chunk info: {:#?}", chunk);
if let Some(id) = &chunk.place { if let Some(id) = &chunk.place {

View File

@ -66,7 +66,11 @@ impl<'a> BlockGen<'a> {
// Conservative range of radius: [8, 47] // Conservative range of radius: [8, 47]
let radius = RandomField::new(seed + 2).get(cliff_pos3d) % 48 + 8; let radius = RandomField::new(seed + 2).get(cliff_pos3d) % 48 + 8;
if cliff_sample.water_dist.map(|d| d > radius as f32).unwrap_or(true) { if cliff_sample
.water_dist
.map(|d| d > radius as f32)
.unwrap_or(true)
{
max_height.max( max_height.max(
if cliff_pos.map(|e| e as f32).distance_squared(wpos) if cliff_pos.map(|e| e as f32).distance_squared(wpos)
< (radius as f32 + tolerance).powf(2.0) < (radius as f32 + tolerance).powf(2.0)

View File

@ -1,5 +1,5 @@
use rand::prelude::*;
use super::GenCtx; use super::GenCtx;
use rand::prelude::*;
pub struct SellOrder { pub struct SellOrder {
pub quantity: f32, pub quantity: f32,
@ -42,25 +42,27 @@ impl Belief {
pub fn buy_units<'a>( pub fn buy_units<'a>(
ctx: &mut GenCtx<impl Rng>, ctx: &mut GenCtx<impl Rng>,
sellers: impl Iterator<Item=&'a mut SellOrder>, sellers: impl Iterator<Item = &'a mut SellOrder>,
max_quantity: f32, max_quantity: f32,
max_price: f32, max_price: f32,
max_spend: f32, max_spend: f32,
) -> (f32, f32) { ) -> (f32, f32) {
let mut sell_orders = sellers let mut sell_orders = sellers.filter(|so| so.quantity > 0.0).collect::<Vec<_>>();
.filter(|so| so.quantity > 0.0)
.collect::<Vec<_>>();
// Sort sell orders by price, cheapest first // Sort sell orders by price, cheapest first
sell_orders.sort_by(|a, b| a.price.partial_cmp(&b.price).unwrap_or_else(|| panic!("{} and {}", a.price, b.price))); sell_orders.sort_by(|a, b| {
a.price
.partial_cmp(&b.price)
.unwrap_or_else(|| panic!("{} and {}", a.price, b.price))
});
let mut quantity = 0.0; let mut quantity = 0.0;
let mut spent = 0.0; let mut spent = 0.0;
for order in sell_orders { for order in sell_orders {
if if quantity >= max_quantity || // We've purchased enough
quantity >= max_quantity || // We've purchased enough
spent >= max_spend || // We've spent enough spent >= max_spend || // We've spent enough
order.price > max_price // Price is too high order.price > max_price
// Price is too high
{ {
break; break;
} else { } else {

View File

@ -1,27 +1,23 @@
mod econ; mod econ;
use std::{ use crate::{
ops::Range, sim::{SimChunk, WorldSim},
hash::Hash, site::{Dungeon, Settlement, Site as WorldSite},
fmt, util::{attempt, seed_expan},
}; };
use hashbrown::{HashMap, HashSet};
use vek::*;
use rand::prelude::*;
use rand_chacha::ChaChaRng;
use common::{ use common::{
astar::Astar,
path::Path,
spiral::Spiral2d,
store::{Id, Store},
terrain::TerrainChunkSize, terrain::TerrainChunkSize,
vol::RectVolSize, vol::RectVolSize,
store::{Id, Store},
path::Path,
astar::Astar,
spiral::Spiral2d,
};
use crate::{
sim::{WorldSim, SimChunk},
site::{Site as WorldSite, Settlement, Dungeon},
util::{seed_expan, attempt},
}; };
use hashbrown::{HashMap, HashSet};
use rand::prelude::*;
use rand_chacha::ChaChaRng;
use std::{fmt, hash::Hash, ops::Range};
use vek::*;
const CARDINALS: [Vec2<i32>; 4] = [ const CARDINALS: [Vec2<i32>; 4] = [
Vec2::new(1, 0), Vec2::new(1, 0),
@ -107,13 +103,15 @@ impl Civs {
// Temporary! // Temporary!
for track in this.tracks.iter() { for track in this.tracks.iter() {
for loc in track.path.iter() { for loc in track.path.iter() {
ctx.sim.get_mut(*loc).unwrap().place = Some(this.civs.iter().next().unwrap().homeland); ctx.sim.get_mut(*loc).unwrap().place =
Some(this.civs.iter().next().unwrap().homeland);
} }
} }
// Flatten ground around sites // Flatten ground around sites
for site in this.sites.iter() { for site in this.sites.iter() {
if let SiteKind::Settlement = &site.kind {} else { if let SiteKind::Settlement = &site.kind {
} else {
continue; continue;
} }
@ -124,9 +122,16 @@ impl Civs {
let flatten_radius = 10.0; let flatten_radius = 10.0;
if let Some(center_alt) = ctx.sim.get_alt_approx(wpos) { if let Some(center_alt) = ctx.sim.get_alt_approx(wpos) {
for offs in Spiral2d::new().take(radius.pow(2) as usize) { for offs in Spiral2d::new().take(radius.pow(2) as usize) {
let center_alt = center_alt + if offs.magnitude_squared() <= 6i32.pow(2) { 16.0 } else { 0.0 }; // Raise the town centre up a little let center_alt = center_alt
+ if offs.magnitude_squared() <= 6i32.pow(2) {
16.0
} else {
0.0
}; // Raise the town centre up a little
let pos = site.center + offs; let pos = site.center + offs;
let factor = (1.0 - (site.center - pos).map(|e| e as f32).magnitude() / flatten_radius) * 1.15; let factor = (1.0
- (site.center - pos).map(|e| e as f32).magnitude() / flatten_radius)
* 1.15;
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
@ -147,12 +152,20 @@ impl Civs {
let wpos = site.center * Vec2::from(TerrainChunkSize::RECT_SIZE).map(|e: u32| e as i32); let wpos = site.center * Vec2::from(TerrainChunkSize::RECT_SIZE).map(|e: u32| e as i32);
let world_site = match &site.kind { let world_site = match &site.kind {
SiteKind::Settlement => WorldSite::from(Settlement::generate(wpos, Some(ctx.sim), ctx.rng)), SiteKind::Settlement => {
SiteKind::Dungeon => WorldSite::from(Dungeon::generate(wpos, Some(ctx.sim), ctx.rng)), WorldSite::from(Settlement::generate(wpos, Some(ctx.sim), ctx.rng))
},
SiteKind::Dungeon => {
WorldSite::from(Dungeon::generate(wpos, Some(ctx.sim), ctx.rng))
},
}; };
let radius_chunks = (world_site.radius() / TerrainChunkSize::RECT_SIZE.x as f32).ceil() as usize; let radius_chunks =
for pos in Spiral2d::new().map(|offs| site.center + offs).take((radius_chunks * 2).pow(2)) { (world_site.radius() / TerrainChunkSize::RECT_SIZE.x as f32).ceil() as usize;
for pos in Spiral2d::new()
.map(|offs| site.center + offs)
.take((radius_chunks * 2).pow(2))
{
ctx.sim ctx.sim
.get_mut(pos) .get_mut(pos)
.map(|chunk| chunk.sites.push(world_site.clone())); .map(|chunk| chunk.sites.push(world_site.clone()));
@ -167,9 +180,7 @@ 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> + '_ { pub fn sites(&self) -> impl Iterator<Item = &Site> + '_ { self.sites.iter() }
self.sites.iter()
}
fn display_info(&self) { fn display_info(&self) {
for (id, civ) in self.civs.iter_ids() { for (id, civ) in self.civs.iter_ids() {
@ -189,24 +200,39 @@ impl Civs {
self.track_map self.track_map
.get(&a) .get(&a)
.and_then(|dests| dests.get(&b)) .and_then(|dests| dests.get(&b))
.or_else(|| self.track_map .or_else(|| self.track_map.get(&b).and_then(|dests| dests.get(&a)))
.get(&b)
.and_then(|dests| dests.get(&a)))
.copied() .copied()
} }
/// Return an iterator over a site's neighbors /// Return an iterator over a site's neighbors
fn neighbors(&self, site: Id<Site>) -> impl Iterator<Item=Id<Site>> + '_ { fn neighbors(&self, site: Id<Site>) -> impl Iterator<Item = Id<Site>> + '_ {
let to = self.track_map.get(&site).map(|dests| dests.keys()).into_iter().flatten(); let to = self
let fro = self.track_map.iter().filter(move |(_, dests)| dests.contains_key(&site)).map(|(p, _)| p); .track_map
.get(&site)
.map(|dests| dests.keys())
.into_iter()
.flatten();
let fro = self
.track_map
.iter()
.filter(move |(_, dests)| dests.contains_key(&site))
.map(|(p, _)| p);
to.chain(fro).filter(move |p| **p != site).copied() to.chain(fro).filter(move |p| **p != site).copied()
} }
/// Find the cheapest route between two places /// Find the cheapest route between two places
fn route_between(&self, a: Id<Site>, b: Id<Site>) -> Option<(Path<Id<Site>>, f32)> { fn route_between(&self, a: Id<Site>, b: Id<Site>) -> Option<(Path<Id<Site>>, f32)> {
let heuristic = move |p: &Id<Site>| (self.sites.get(*p).center.distance_squared(self.sites.get(b).center) as f32).sqrt(); let heuristic = move |p: &Id<Site>| {
(self
.sites
.get(*p)
.center
.distance_squared(self.sites.get(b).center) as f32)
.sqrt()
};
let neighbors = |p: &Id<Site>| self.neighbors(*p); let neighbors = |p: &Id<Site>| self.neighbors(*p);
let transition = |a: &Id<Site>, b: &Id<Site>| self.tracks.get(self.track_between(*a, *b).unwrap()).cost; let transition =
|a: &Id<Site>, b: &Id<Site>| self.tracks.get(self.track_between(*a, *b).unwrap()).cost;
let satisfied = |p: &Id<Site>| *p == b; let satisfied = |p: &Id<Site>| *p == b;
let mut astar = Astar::new(100, a, heuristic); let mut astar = Astar::new(100, a, heuristic);
astar astar
@ -248,7 +274,12 @@ impl Civs {
Some(civ) Some(civ)
} }
fn establish_place(&mut self, ctx: &mut GenCtx<impl Rng>, loc: Vec2<i32>, area: Range<usize>) -> Option<Id<Place>> { fn establish_place(
&mut self,
ctx: &mut GenCtx<impl Rng>,
loc: Vec2<i32>,
area: Range<usize>,
) -> Option<Id<Place>> {
let mut dead = HashSet::new(); let mut dead = HashSet::new();
let mut alive = HashSet::new(); let mut alive = HashSet::new();
alive.insert(loc); alive.insert(loc);
@ -258,7 +289,13 @@ impl Civs {
for dir in CARDINALS.iter() { for dir in CARDINALS.iter() {
if site_in_dir(&ctx.sim, cloc, *dir) { if site_in_dir(&ctx.sim, cloc, *dir) {
let rloc = cloc + *dir; let rloc = cloc + *dir;
if !dead.contains(&rloc) && ctx.sim.get(rloc).map(|c| c.place.is_none()).unwrap_or(false) { if !dead.contains(&rloc)
&& ctx
.sim
.get(rloc)
.map(|c| c.place.is_none())
.unwrap_or(false)
{
alive.insert(rloc); alive.insert(rloc);
} }
} }
@ -291,7 +328,12 @@ impl Civs {
Some(place) Some(place)
} }
fn establish_site(&mut self, ctx: &mut GenCtx<impl Rng>, loc: Vec2<i32>, site_fn: impl FnOnce(Id<Place>) -> Site) -> Option<Id<Site>> { fn establish_site(
&mut self,
ctx: &mut GenCtx<impl Rng>,
loc: Vec2<i32>,
site_fn: impl FnOnce(Id<Place>) -> Site,
) -> Option<Id<Site>> {
const SITE_AREA: Range<usize> = 64..256; const SITE_AREA: Range<usize> = 64..256;
let place = match ctx.sim.get(loc).and_then(|site| site.place) { let place = match ctx.sim.get(loc).and_then(|site| site.place) {
@ -303,7 +345,8 @@ impl Civs {
// Find neighbors // Find neighbors
const MAX_NEIGHBOR_DISTANCE: f32 = 250.0; const MAX_NEIGHBOR_DISTANCE: f32 = 250.0;
let mut nearby = self.sites let mut nearby = self
.sites
.iter_ids() .iter_ids()
.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(|(p, dist)| *dist < MAX_NEIGHBOR_DISTANCE) .filter(|(p, dist)| *dist < MAX_NEIGHBOR_DISTANCE)
@ -320,10 +363,7 @@ impl Civs {
.filter(|(_, route_cost)| *route_cost < cost * 3.0) .filter(|(_, route_cost)| *route_cost < cost * 3.0)
.is_none() .is_none()
{ {
let track = self.tracks.insert(Track { let track = self.tracks.insert(Track { cost, path });
cost,
path,
});
self.track_map self.track_map
.entry(site) .entry(site)
.or_default() .or_default()
@ -342,15 +382,17 @@ impl Civs {
// Trade stocks // Trade stocks
// let mut stocks = TRADE_STOCKS; // let mut stocks = TRADE_STOCKS;
// stocks.shuffle(ctx.rng); // Give each stock a chance to be traded first // stocks.shuffle(ctx.rng); // Give each stock a chance to be traded
// for stock in stocks.iter().copied() { // first for stock in stocks.iter().copied() {
// let mut sell_orders = self.sites // let mut sell_orders = self.sites
// .iter_ids() // .iter_ids()
// .map(|(id, site)| (id, { // .map(|(id, site)| (id, {
// econ::SellOrder { // econ::SellOrder {
// quantity: site.export_targets[stock].max(0.0).min(site.stocks[stock]), // quantity:
// price: site.trade_states[stock].sell_belief.choose_price(ctx) * 1.25, // Trade cost // site.export_targets[stock].max(0.0).min(site.stocks[stock]),
// q_sold: 0.0, // price:
// site.trade_states[stock].sell_belief.choose_price(ctx) * 1.25, //
// Trade cost q_sold: 0.0,
// } // }
// })) // }))
// .filter(|(_, order)| order.quantity > 0.0) // .filter(|(_, order)| order.quantity > 0.0)
@ -364,17 +406,17 @@ impl Civs {
// let (max_spend, max_price, max_import) = { // let (max_spend, max_price, max_import) = {
// let site = self.sites.get(site); // let site = self.sites.get(site);
// let budget = site.coin * 0.5; // let budget = site.coin * 0.5;
// let total_value = site.values.iter().map(|(_, v)| (*v).unwrap_or(0.0)).sum::<f32>(); // let total_value = site.values.iter().map(|(_, v)|
// ( // (*v).unwrap_or(0.0)).sum::<f32>(); (
// 100000.0,//(site.values[stock].unwrap_or(0.1) / total_value * budget).min(budget), // 100000.0,//(site.values[stock].unwrap_or(0.1) /
// site.trade_states[stock].buy_belief.price, // total_value * budget).min(budget),
// -site.export_targets[stock].min(0.0), // site.trade_states[stock].buy_belief.price,
// ) // -site.export_targets[stock].min(0.0), )
// }; // };
// let (quantity, spent) = econ::buy_units(ctx, sell_orders // let (quantity, spent) = econ::buy_units(ctx, sell_orders
// .iter_mut() // .iter_mut()
// .filter(|(id, _)| site != *id && self.track_between(site, *id).is_some()) // .filter(|(id, _)| site != *id && self.track_between(site,
// .map(|(_, order)| order), // *id).is_some()) .map(|(_, order)| order),
// max_import, // max_import,
// 1000000.0, // Max price TODO // 1000000.0, // Max price TODO
// max_spend, // max_spend,
@ -384,9 +426,9 @@ impl Civs {
// if quantity > 0.0 { // if quantity > 0.0 {
// site.stocks[stock] += quantity; // site.stocks[stock] += quantity;
// site.last_exports[stock] = -quantity; // site.last_exports[stock] = -quantity;
// site.trade_states[stock].buy_belief.update_buyer(years, spent / quantity); // site.trade_states[stock].buy_belief.update_buyer(years,
// println!("Belief: {:?}", site.trade_states[stock].buy_belief); // spent / quantity); println!("Belief: {:?}",
// } // site.trade_states[stock].buy_belief); }
// } // }
// for (site, order) in sell_orders { // for (site, order) in sell_orders {
@ -395,22 +437,31 @@ impl Civs {
// if order.q_sold > 0.0 { // if order.q_sold > 0.0 {
// site.stocks[stock] -= order.q_sold; // site.stocks[stock] -= order.q_sold;
// site.last_exports[stock] = order.q_sold; // site.last_exports[stock] = order.q_sold;
// site.trade_states[stock].sell_belief.update_seller(order.q_sold / order.quantity); //
// } // site.trade_states[stock].sell_belief.update_seller(order.q_sold /
// order.quantity); }
// } // }
// } // }
} }
} }
/// Attempt to find a path between two locations /// Attempt to find a path between two locations
fn find_path(ctx: &mut GenCtx<impl Rng>, a: Vec2<i32>, b: Vec2<i32>) -> Option<(Path<Vec2<i32>>, f32)> { fn find_path(
ctx: &mut GenCtx<impl Rng>,
a: Vec2<i32>,
b: Vec2<i32>,
) -> Option<(Path<Vec2<i32>>, f32)> {
let sim = &ctx.sim; let sim = &ctx.sim;
let heuristic = move |l: &Vec2<i32>| (l.distance_squared(b) as f32).sqrt(); let heuristic = move |l: &Vec2<i32>| (l.distance_squared(b) as f32).sqrt();
let neighbors = |l: &Vec2<i32>| { let neighbors = |l: &Vec2<i32>| {
let l = *l; let l = *l;
DIAGONALS.iter().filter(move |dir| walk_in_dir(sim, l, **dir).is_some()).map(move |dir| l + *dir) DIAGONALS
.iter()
.filter(move |dir| walk_in_dir(sim, l, **dir).is_some())
.map(move |dir| l + *dir)
}; };
let transition = |a: &Vec2<i32>, b: &Vec2<i32>| 1.0 + walk_in_dir(sim, *a, *b - *a).unwrap_or(10000.0); let transition =
|a: &Vec2<i32>, b: &Vec2<i32>| 1.0 + walk_in_dir(sim, *a, *b - *a).unwrap_or(10000.0);
let satisfied = |l: &Vec2<i32>| *l == b; let satisfied = |l: &Vec2<i32>| *l == b;
let mut astar = Astar::new(20000, a, heuristic); let mut astar = Astar::new(20000, a, heuristic);
astar astar
@ -419,11 +470,10 @@ fn find_path(ctx: &mut GenCtx<impl Rng>, a: Vec2<i32>, b: Vec2<i32>) -> Option<(
.and_then(|path| astar.get_cheapest_cost().map(|cost| (path, cost))) .and_then(|path| astar.get_cheapest_cost().map(|cost| (path, cost)))
} }
/// Return true if travel between a location and a chunk next to it is permitted (TODO: by whom?) /// Return true if travel between a location and a chunk next to it is permitted
/// (TODO: by whom?)
fn walk_in_dir(sim: &WorldSim, a: Vec2<i32>, dir: Vec2<i32>) -> Option<f32> { fn walk_in_dir(sim: &WorldSim, a: Vec2<i32>, dir: Vec2<i32>) -> Option<f32> {
if loc_suitable_for_walking(sim, a) && if loc_suitable_for_walking(sim, a) && loc_suitable_for_walking(sim, a + dir) {
loc_suitable_for_walking(sim, a + dir)
{
let a_alt = sim.get(a)?.alt; let a_alt = sim.get(a)?.alt;
let b_alt = sim.get(a + dir)?.alt; let b_alt = sim.get(a + dir)?.alt;
Some((b_alt - a_alt).abs() / 2.5) Some((b_alt - a_alt).abs() / 2.5)
@ -441,18 +491,22 @@ fn loc_suitable_for_walking(sim: &WorldSim, loc: Vec2<i32>) -> bool {
} }
} }
/// Return true if a site could be constructed between a location and a chunk next to it is permitted (TODO: by whom?) /// Return true if a site could be constructed between a location and a chunk
/// next to it is permitted (TODO: by whom?)
fn site_in_dir(sim: &WorldSim, a: Vec2<i32>, dir: Vec2<i32>) -> bool { fn site_in_dir(sim: &WorldSim, a: Vec2<i32>, dir: Vec2<i32>) -> bool {
loc_suitable_for_site(sim, a) && loc_suitable_for_site(sim, a) && loc_suitable_for_site(sim, a + dir)
loc_suitable_for_site(sim, a + dir)
} }
/// Return true if a position is suitable for site construction (TODO: criteria?) /// Return true if a position is suitable for site construction (TODO:
/// criteria?)
fn loc_suitable_for_site(sim: &WorldSim, loc: Vec2<i32>) -> bool { fn loc_suitable_for_site(sim: &WorldSim, loc: Vec2<i32>) -> bool {
if let Some(chunk) = sim.get(loc) { if let Some(chunk) = sim.get(loc) {
!chunk.river.is_ocean() && !chunk.river.is_ocean()
!chunk.river.is_lake() && && !chunk.river.is_lake()
sim.get_gradient_approx(loc).map(|grad| grad < 1.0).unwrap_or(false) && sim
.get_gradient_approx(loc)
.map(|grad| grad < 1.0)
.unwrap_or(false)
} else { } else {
false false
} }
@ -464,10 +518,15 @@ fn find_site_loc(ctx: &mut GenCtx<impl Rng>, near: Option<(Vec2<i32>, f32)>) ->
let mut loc = None; let mut loc = None;
for _ in 0..MAX_ATTEMPTS { for _ in 0..MAX_ATTEMPTS {
let test_loc = loc.unwrap_or_else(|| match near { let test_loc = loc.unwrap_or_else(|| match near {
Some((origin, dist)) => origin + (Vec2::new( Some((origin, dist)) => {
ctx.rng.gen_range(-1.0, 1.0), origin
ctx.rng.gen_range(-1.0, 1.0), + (Vec2::new(ctx.rng.gen_range(-1.0, 1.0), ctx.rng.gen_range(-1.0, 1.0))
).try_normalized().unwrap_or(Vec2::zero()) * ctx.rng.gen::<f32>() * dist).map(|e| e as i32), .try_normalized()
.unwrap_or(Vec2::zero())
* ctx.rng.gen::<f32>()
* dist)
.map(|e| e as i32)
},
None => Vec2::new( None => Vec2::new(
ctx.rng.gen_range(0, ctx.sim.get_size().x as i32), ctx.rng.gen_range(0, ctx.sim.get_size().x as i32),
ctx.rng.gen_range(0, ctx.sim.get_size().y as i32), ctx.rng.gen_range(0, ctx.sim.get_size().y as i32),
@ -478,9 +537,14 @@ fn find_site_loc(ctx: &mut GenCtx<impl Rng>, near: Option<(Vec2<i32>, f32)>) ->
return Some(test_loc); return Some(test_loc);
} }
loc = ctx.sim.get(test_loc).and_then(|c| Some(c.downhill?.map2(Vec2::from(TerrainChunkSize::RECT_SIZE), |e, sz: u32| { loc = ctx.sim.get(test_loc).and_then(|c| {
e / (sz as i32) Some(
}))); c.downhill?
.map2(Vec2::from(TerrainChunkSize::RECT_SIZE), |e, sz: u32| {
e / (sz as i32)
}),
)
});
} }
None None
} }
@ -508,23 +572,36 @@ pub struct NaturalResources {
impl NaturalResources { impl NaturalResources {
fn include_chunk(&mut self, ctx: &mut GenCtx<impl Rng>, loc: Vec2<i32>) { fn include_chunk(&mut self, ctx: &mut GenCtx<impl Rng>, loc: Vec2<i32>) {
let chunk = if let Some(chunk) = ctx.sim.get(loc) { chunk } else { return }; let chunk = if let Some(chunk) = ctx.sim.get(loc) {
chunk
} else {
return;
};
self.wood += chunk.tree_density; self.wood += chunk.tree_density;
self.rock += chunk.rockiness; self.rock += chunk.rockiness;
self.river += if chunk.river.is_river() { 5.0 } else { 0.0 }; self.river += if chunk.river.is_river() { 5.0 } else { 0.0 };
self.farmland += if self.farmland += if chunk.humidity > 0.35
chunk.humidity > 0.35 && && chunk.temp > -0.3
chunk.temp > -0.3 && chunk.temp < 0.75 && && chunk.temp < 0.75
chunk.chaos < 0.5 && && chunk.chaos < 0.5
ctx.sim.get_gradient_approx(loc).map(|grad| grad < 0.7).unwrap_or(false) && ctx
{ 1.0 } else { 0.0 }; .sim
.get_gradient_approx(loc)
.map(|grad| grad < 0.7)
.unwrap_or(false)
{
1.0
} else {
0.0
};
} }
} }
pub struct Track { pub struct Track {
/// Cost of using this track relative to other paths. This cost is an arbitrary unit and /// Cost of using this track relative to other paths. This cost is an
/// doesn't make sense unless compared to other track costs. /// arbitrary unit and doesn't make sense unless compared to other track
/// costs.
cost: f32, cost: f32,
path: Path<Vec2<i32>>, path: Path<Vec2<i32>>,
} }
@ -570,7 +647,14 @@ impl fmt::Display for Site {
} }
writeln!(f, "Values")?; writeln!(f, "Values")?;
for stock in TRADE_STOCKS.iter() { for stock in TRADE_STOCKS.iter() {
writeln!(f, "- {}: {}", stock, self.values[*stock].map(|x| x.to_string()).unwrap_or_else(|| "N/A".to_string()))?; writeln!(
f,
"- {}: {}",
stock,
self.values[*stock]
.map(|x| x.to_string())
.unwrap_or_else(|| "N/A".to_string())
)?;
} }
writeln!(f, "Laborers")?; writeln!(f, "Laborers")?;
for (labor, n) in self.labors.iter() { for (labor, n) in self.labors.iter() {
@ -619,8 +703,8 @@ impl Site {
(Some(HUNTER), vec![(GAME, 4.0)]), (Some(HUNTER), vec![(GAME, 4.0)]),
(Some(FARMER), vec![(WHEAT, 4.0)]), (Some(FARMER), vec![(WHEAT, 4.0)]),
] ]
.into_iter() .into_iter()
.collect::<HashMap<_, Vec<(Stock, f32)>>>(); .collect::<HashMap<_, Vec<(Stock, f32)>>>();
// Per labourer, per year // Per labourer, per year
let production = Stocks::from_list(&[ let production = Stocks::from_list(&[
@ -634,7 +718,11 @@ impl Site {
let mut demand = Stocks::from_default(0.0); let mut demand = Stocks::from_default(0.0);
for (labor, orders) in &orders { for (labor, orders) in &orders {
let scale = if let Some(labor) = labor { self.labors[*labor] } else { 1.0 } * self.population; let scale = if let Some(labor) = labor {
self.labors[*labor]
} else {
1.0
} * self.population;
for (stock, amount) in orders { for (stock, amount) in orders {
demand[*stock] += *amount * scale; demand[*stock] += *amount * scale;
} }
@ -647,20 +735,27 @@ impl Site {
let last_exports = &self.last_exports; let last_exports = &self.last_exports;
let stocks = &self.stocks; let stocks = &self.stocks;
self.surplus = demand.clone().map(|stock, tgt| { self.surplus = demand
supply[stock] + stocks[stock] - demand[stock] - last_exports[stock] .clone()
}); .map(|stock, tgt| supply[stock] + stocks[stock] - demand[stock] - last_exports[stock]);
// Update values according to the surplus of each stock // Update values according to the surplus of each stock
let values = &mut self.values; let values = &mut self.values;
self.surplus.iter().for_each(|(stock, surplus)| { self.surplus.iter().for_each(|(stock, surplus)| {
let val = 3.5f32.powf(1.0 - *surplus / demand[stock]); let val = 3.5f32.powf(1.0 - *surplus / demand[stock]);
values[stock] = if val > 0.001 && val < 1000.0 { Some(val) } else { None }; values[stock] = if val > 0.001 && val < 1000.0 {
Some(val)
} else {
None
};
}); });
// Update export targets based on relative values // Update export targets based on relative values
let value_avg = let value_avg = values
values.iter().map(|(_, v)| (*v).unwrap_or(0.0)).sum::<f32>().max(0.01) .iter()
.map(|(_, v)| (*v).unwrap_or(0.0))
.sum::<f32>()
.max(0.01)
/ values.iter().filter(|(_, v)| v.is_some()).count() as f32; / values.iter().filter(|(_, v)| v.is_some()).count() as f32;
let export_targets = &mut self.export_targets; let export_targets = &mut self.export_targets;
let last_exports = &self.last_exports; let last_exports = &self.last_exports;
@ -668,7 +763,7 @@ impl Site {
self.values.iter().for_each(|(stock, value)| { self.values.iter().for_each(|(stock, value)| {
let rvalue = (*value).map(|v| v - value_avg).unwrap_or(0.0); 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 }; //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; export_targets[stock] = last_exports[stock] - rvalue * 0.1; // + (trade_states[stock].sell_belief.price - trade_states[stock].buy_belief.price) * 0.025;
}); });
let population = self.population; let population = self.population;
@ -680,17 +775,24 @@ impl Site {
let labor_ratio_sum = labor_ratios.iter().map(|(_, r)| *r).sum::<f32>().max(0.01); let labor_ratio_sum = labor_ratios.iter().map(|(_, r)| *r).sum::<f32>().max(0.01);
production.iter().for_each(|(labor, _)| { production.iter().for_each(|(labor, _)| {
let smooth = 0.8; let smooth = 0.8;
self.labors[labor] = smooth * self.labors[labor] + (1.0 - smooth) * (labor_ratios[labor].max(labor_ratio_sum / 1000.0) / labor_ratio_sum); self.labors[labor] = smooth * self.labors[labor]
+ (1.0 - smooth)
* (labor_ratios[labor].max(labor_ratio_sum / 1000.0) / labor_ratio_sum);
}); });
// Production // Production
let stocks_before = self.stocks.clone(); let stocks_before = self.stocks.clone();
for (labor, orders) in orders.iter() { for (labor, orders) in orders.iter() {
let scale = if let Some(labor) = labor { self.labors[*labor] } else { 1.0 } * population; let scale = if let Some(labor) = labor {
self.labors[*labor]
} else {
1.0
} * population;
// For each order, we try to find the minimum satisfaction rate - this limits how much // For each order, we try to find the minimum satisfaction rate - this limits
// we can produce! For example, if we need 0.25 fish and 0.75 oats to make 1 unit of // how much we can produce! For example, if we need 0.25 fish and
// food, but only 0.5 units of oats are available then we only need to consume 2/3rds // 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 // of other ingredients and leave the rest in stock
// In effect, this is the productivity // In effect, this is the productivity
let productivity = orders let productivity = orders
@ -703,7 +805,9 @@ impl Site {
satisfaction satisfaction
}) })
.min_by(|a, b| a.partial_cmp(b).unwrap()) .min_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap_or_else(|| panic!("Industry {:?} requires at least one input order", labor)); .unwrap_or_else(|| {
panic!("Industry {:?} requires at least one input order", labor)
});
for (stock, amount) in orders { for (stock, amount) in orders {
// What quantity is this order requesting? // What quantity is this order requesting?
@ -733,7 +837,11 @@ impl Site {
// Births/deaths // Births/deaths
const NATURAL_BIRTH_RATE: f32 = 0.15; const NATURAL_BIRTH_RATE: f32 = 0.15;
const DEATH_RATE: f32 = 0.05; const DEATH_RATE: f32 = 0.05;
let birth_rate = if self.surplus[FOOD] > 0.0 { NATURAL_BIRTH_RATE } else { 0.0 }; let birth_rate = if self.surplus[FOOD] > 0.0 {
NATURAL_BIRTH_RATE
} else {
0.0
};
self.population += years * self.population * (birth_rate - DEATH_RATE); self.population += years * self.population * (birth_rate - DEATH_RATE);
} }
} }
@ -757,13 +865,7 @@ const LOGS: Stock = "logs";
const WOOD: Stock = "wood"; const WOOD: Stock = "wood";
const ROCK: Stock = "rock"; const ROCK: Stock = "rock";
const STONE: Stock = "stone"; const STONE: Stock = "stone";
const TRADE_STOCKS: [Stock; 5] = [ const TRADE_STOCKS: [Stock; 5] = [FLOUR, MEAT, FOOD, WOOD, STONE];
FLOUR,
MEAT,
FOOD,
WOOD,
STONE,
];
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct TradeState { struct TradeState {
@ -795,8 +897,10 @@ pub struct MapVec<K, T> {
} }
impl<K: Copy + Eq + Hash, T: Default + Clone> MapVec<K, T> { impl<K: Copy + Eq + Hash, T: Default + Clone> MapVec<K, T> {
pub fn from_list<'a>(i: impl IntoIterator<Item=&'a (K, T)>) -> Self pub fn from_list<'a>(i: impl IntoIterator<Item = &'a (K, T)>) -> Self
where K: 'a, T: 'a where
K: 'a,
T: 'a,
{ {
Self { Self {
entries: i.into_iter().cloned().collect(), entries: i.into_iter().cloned().collect(),
@ -813,39 +917,37 @@ impl<K: Copy + Eq + Hash, T: Default + Clone> MapVec<K, T> {
pub fn get_mut(&mut self, entry: K) -> &mut T { pub fn get_mut(&mut self, entry: K) -> &mut T {
let default = &self.default; let default = &self.default;
self self.entries.entry(entry).or_insert_with(|| default.clone())
.entries
.entry(entry)
.or_insert_with(|| default.clone())
} }
pub fn get(&self, entry: K) -> &T { pub fn get(&self, entry: K) -> &T { self.entries.get(&entry).unwrap_or(&self.default) }
self.entries.get(&entry).unwrap_or(&self.default)
}
pub fn map<U: Default>(mut self, mut f: impl FnMut(K, T) -> U) -> MapVec<K, U> { pub fn map<U: Default>(mut self, mut f: impl FnMut(K, T) -> U) -> MapVec<K, U> {
MapVec { MapVec {
entries: self.entries.into_iter().map(|(s, v)| (s.clone(), f(s, v))).collect(), entries: self
.entries
.into_iter()
.map(|(s, v)| (s.clone(), f(s, v)))
.collect(),
default: U::default(), default: U::default(),
} }
} }
pub fn iter(&self) -> impl Iterator<Item=(K, &T)> + '_ { pub fn iter(&self) -> impl Iterator<Item = (K, &T)> + '_ {
self.entries.iter().map(|(s, v)| (*s, v)) self.entries.iter().map(|(s, v)| (*s, v))
} }
pub fn iter_mut(&mut self) -> impl Iterator<Item=(K, &mut T)> + '_ { pub fn iter_mut(&mut self) -> impl Iterator<Item = (K, &mut T)> + '_ {
self.entries.iter_mut().map(|(s, v)| (*s, v)) self.entries.iter_mut().map(|(s, v)| (*s, v))
} }
} }
impl<K: Copy + Eq + Hash, T: Default + Clone> std::ops::Index<K> for MapVec<K, T> { impl<K: Copy + Eq + Hash, T: Default + Clone> std::ops::Index<K> for MapVec<K, T> {
type Output = T; type Output = T;
fn index(&self, entry: K) -> &Self::Output { self.get(entry) } fn index(&self, entry: K) -> &Self::Output { self.get(entry) }
} }
impl<K: Copy + Eq + Hash, T: Default + Clone> std::ops::IndexMut<K> for MapVec<K, T> { impl<K: Copy + Eq + Hash, T: Default + Clone> std::ops::IndexMut<K> for MapVec<K, T> {
fn index_mut(&mut self, entry: K) -> &mut Self::Output { self.get_mut(entry) } fn index_mut(&mut self, entry: K) -> &mut Self::Output { self.get_mut(entry) }
} }

View File

@ -1,10 +1,7 @@
use crate::{ use crate::{
all::ForestKind, all::ForestKind,
block::StructureMeta, block::StructureMeta,
sim::{ sim::{local_cells, uniform_idx_as_vec2, vec2_as_uniform_idx, RiverKind, SimChunk, WorldSim},
local_cells, uniform_idx_as_vec2, vec2_as_uniform_idx, RiverKind, SimChunk,
WorldSim,
},
util::{RandomPerm, Sampler, UnitChooser}, util::{RandomPerm, Sampler, UnitChooser},
CONFIG, CONFIG,
}; };
@ -598,239 +595,270 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
river_chunk, river_chunk,
max_border_river, max_border_river,
max_border_river_dist, max_border_river_dist,
)) = max_river )) =
max_river
{ {
// This is flowing into a lake, or a lake, or is at least a non-ocean tile. // This is flowing into a lake, or a lake, or is at least a non-ocean tile.
// //
// If we are <= water_alt, we are in the lake; otherwise, we are flowing into // If we are <= water_alt, we are in the lake; otherwise, we are flowing into
// it. // it.
let (in_water, water_dist, new_alt, new_water_alt, riverless_alt, warp_factor) = max_border_river let (in_water, water_dist, new_alt, new_water_alt, riverless_alt, warp_factor) =
.river_kind max_border_river
.and_then(|river_kind| { .river_kind
if let RiverKind::River { cross_section } = river_kind { .and_then(|river_kind| {
if max_border_river_dist.map(|(_, dist, _, _)| dist) != Some(Vec2::zero()) { if let RiverKind::River { cross_section } = river_kind {
return None; if max_border_river_dist.map(|(_, dist, _, _)| dist)
!= Some(Vec2::zero())
{
return None;
}
let (
_,
_,
river_width,
(river_t, (river_pos, _), downhill_river_chunk),
) = max_border_river_dist.unwrap();
let river_alt = Lerp::lerp(
river_chunk.alt.max(river_chunk.water_alt),
downhill_river_chunk.alt.max(downhill_river_chunk.water_alt),
river_t as f32,
);
let new_alt = river_alt - river_gouge;
let river_dist = wposf.distance(river_pos);
let river_height_factor = river_dist / (river_width * 0.5);
let valley_alt = Lerp::lerp(
new_alt - cross_section.y.max(1.0),
new_alt - 1.0,
(river_height_factor * river_height_factor) as f32,
);
Some((
true,
Some((river_dist - river_width * 0.5) as f32),
valley_alt,
new_alt,
river_alt,
0.0,
))
} else {
None
} }
let (_, _, river_width, (river_t, (river_pos, _), downhill_river_chunk)) = })
max_border_river_dist.unwrap(); .unwrap_or_else(|| {
let river_alt = Lerp::lerp( max_border_river
river_chunk.alt.max(river_chunk.water_alt), .river_kind
downhill_river_chunk.alt.max(downhill_river_chunk.water_alt), .and_then(|river_kind| {
river_t as f32, match river_kind {
); RiverKind::Ocean => {
let new_alt = river_alt - river_gouge; let (
let river_dist = wposf.distance(river_pos); _,
let river_height_factor = river_dist / (river_width * 0.5); dist,
river_width,
let valley_alt = Lerp::lerp( (river_t, (river_pos, _), downhill_river_chunk),
new_alt - cross_section.y.max(1.0), ) = if let Some(dist) = max_border_river_dist {
new_alt - 1.0,
(river_height_factor * river_height_factor) as f32,
);
Some((
true,
Some((river_dist - river_width * 0.5) as f32),
valley_alt,
new_alt,
river_alt,
0.0,
))
} else {
None
}
})
.unwrap_or_else(|| {
max_border_river
.river_kind
.and_then(|river_kind| {
match river_kind {
RiverKind::Ocean => {
let (
_,
dist,
river_width,
(river_t, (river_pos, _), downhill_river_chunk),
) = if let Some(dist) = max_border_river_dist {
dist
} else {
log::error!(
"Ocean: {:?} Here: {:?}, Ocean: {:?}",
max_border_river,
chunk_pos,
max_border_river_pos
);
panic!(
"Oceans should definitely have a downhill! ...Right?"
);
};
let lake_water_alt = Lerp::lerp(
river_chunk.alt.max(river_chunk.water_alt),
downhill_river_chunk
.alt
.max(downhill_river_chunk.water_alt),
river_t as f32,
);
if dist == Vec2::zero() {
let river_dist = wposf.distance(river_pos);
let _river_height_factor = river_dist / (river_width * 0.5);
return Some((
true,
Some((river_dist - river_width * 0.5) as f32),
alt_for_river.min(lake_water_alt - 1.0 - river_gouge),
lake_water_alt - river_gouge,
alt_for_river.max(lake_water_alt),
0.0,
));
}
Some((
river_scale_factor <= 1.0,
Some((wposf.distance(river_pos) - river_width * 0.5) as f32),
alt_for_river,
downhill_water_alt,
alt_for_river,
river_scale_factor as f32,
))
},
RiverKind::Lake { .. } => {
let lake_dist = (max_border_river_pos.map(|e| e as f64)
* neighbor_coef)
.distance(wposf);
let downhill_river_chunk = max_border_river_pos;
let lake_id_dist = downhill_river_chunk - chunk_pos;
let in_bounds = lake_id_dist.x >= -1
&& lake_id_dist.y >= -1
&& lake_id_dist.x <= 1
&& lake_id_dist.y <= 1;
let in_bounds =
in_bounds && (lake_id_dist.x >= 0 && lake_id_dist.y >= 0);
let (_, dist, _, (river_t, _, downhill_river_chunk)) =
if let Some(dist) = max_border_river_dist {
dist dist
} else { } else {
if lake_dist log::error!(
<= TerrainChunkSize::RECT_SIZE.x as f64 * 1.0 "Ocean: {:?} Here: {:?}, Ocean: {:?}",
|| in_bounds max_border_river,
{ chunk_pos,
let gouge_factor = 0.0; max_border_river_pos
return Some(( );
in_bounds panic!(
|| downhill_water_alt "Oceans should definitely have a downhill! \
...Right?"
);
};
let lake_water_alt = Lerp::lerp(
river_chunk.alt.max(river_chunk.water_alt),
downhill_river_chunk
.alt
.max(downhill_river_chunk.water_alt),
river_t as f32,
);
if dist == Vec2::zero() {
let river_dist = wposf.distance(river_pos);
let _river_height_factor =
river_dist / (river_width * 0.5);
return Some((
true,
Some((river_dist - river_width * 0.5) as f32),
alt_for_river
.min(lake_water_alt - 1.0 - river_gouge),
lake_water_alt - river_gouge,
alt_for_river.max(lake_water_alt),
0.0,
));
}
Some((
river_scale_factor <= 1.0,
Some(
(wposf.distance(river_pos) - river_width * 0.5)
as f32,
),
alt_for_river,
downhill_water_alt,
alt_for_river,
river_scale_factor as f32,
))
},
RiverKind::Lake { .. } => {
let lake_dist = (max_border_river_pos.map(|e| e as f64)
* neighbor_coef)
.distance(wposf);
let downhill_river_chunk = max_border_river_pos;
let lake_id_dist = downhill_river_chunk - chunk_pos;
let in_bounds = lake_id_dist.x >= -1
&& lake_id_dist.y >= -1
&& lake_id_dist.x <= 1
&& lake_id_dist.y <= 1;
let in_bounds = in_bounds
&& (lake_id_dist.x >= 0 && lake_id_dist.y >= 0);
let (_, dist, _, (river_t, _, downhill_river_chunk)) =
if let Some(dist) = max_border_river_dist {
dist
} else {
if lake_dist
<= TerrainChunkSize::RECT_SIZE.x as f64 * 1.0
|| in_bounds
{
let gouge_factor = 0.0;
return Some((
in_bounds
|| downhill_water_alt
.max(river_chunk.water_alt)
> alt_for_river,
Some(lake_dist as f32),
alt_for_river,
(downhill_water_alt
.max(river_chunk.water_alt) .max(river_chunk.water_alt)
> alt_for_river, - river_gouge),
alt_for_river,
river_scale_factor as f32
* (1.0 - gouge_factor),
));
} else {
return Some((
false,
Some(lake_dist as f32),
alt_for_river,
downhill_water_alt,
alt_for_river,
river_scale_factor as f32,
));
}
};
let lake_dist = dist.y;
let lake_water_alt = Lerp::lerp(
river_chunk.alt.max(river_chunk.water_alt),
downhill_river_chunk
.alt
.max(downhill_river_chunk.water_alt),
river_t as f32,
);
if dist == Vec2::zero() {
return Some((
true,
Some(lake_dist as f32),
alt_for_river
.min(lake_water_alt - 1.0 - river_gouge),
lake_water_alt - river_gouge,
alt_for_river.max(lake_water_alt),
0.0,
));
}
if lake_dist <= TerrainChunkSize::RECT_SIZE.x as f64 * 1.0
|| in_bounds
{
let gouge_factor = if in_bounds && lake_dist <= 1.0 {
1.0
} else {
0.0
};
let in_bounds_ = lake_dist
<= TerrainChunkSize::RECT_SIZE.x as f64 * 0.5;
if gouge_factor == 1.0 {
return Some((
true,
Some(lake_dist as f32), Some(lake_dist as f32),
alt.min(lake_water_alt - 1.0 - river_gouge),
downhill_water_alt.max(lake_water_alt)
- river_gouge,
alt.max(lake_water_alt),
0.0,
));
} else {
return Some((
true,
None,
alt_for_river, alt_for_river,
(downhill_water_alt.max(river_chunk.water_alt) if in_bounds_ {
- river_gouge), downhill_water_alt.max(lake_water_alt)
} else {
downhill_water_alt
} - river_gouge,
alt_for_river, alt_for_river,
river_scale_factor as f32 river_scale_factor as f32
* (1.0 - gouge_factor), * (1.0 - gouge_factor),
)); ));
} else {
return Some((
false,
Some(lake_dist as f32),
alt_for_river,
downhill_water_alt,
alt_for_river,
river_scale_factor as f32,
));
} }
};
let lake_dist = dist.y;
let lake_water_alt = Lerp::lerp(
river_chunk.alt.max(river_chunk.water_alt),
downhill_river_chunk
.alt
.max(downhill_river_chunk.water_alt),
river_t as f32,
);
if dist == Vec2::zero() {
return Some((
true,
Some(lake_dist as f32),
alt_for_river.min(lake_water_alt - 1.0 - river_gouge),
lake_water_alt - river_gouge,
alt_for_river.max(lake_water_alt),
0.0,
));
}
if lake_dist <= TerrainChunkSize::RECT_SIZE.x as f64 * 1.0
|| in_bounds
{
let gouge_factor = if in_bounds && lake_dist <= 1.0 {
1.0
} else {
0.0
};
let in_bounds_ =
lake_dist <= TerrainChunkSize::RECT_SIZE.x as f64 * 0.5;
if gouge_factor == 1.0 {
return Some((
true,
Some(lake_dist as f32),
alt.min(lake_water_alt - 1.0 - river_gouge),
downhill_water_alt.max(lake_water_alt)
- river_gouge,
alt.max(lake_water_alt),
0.0,
));
} else {
return Some((
true,
None,
alt_for_river,
if in_bounds_ {
downhill_water_alt.max(lake_water_alt)
} else {
downhill_water_alt
} - river_gouge,
alt_for_river,
river_scale_factor as f32 * (1.0 - gouge_factor),
));
} }
} Some((
Some(( river_scale_factor <= 1.0,
river_scale_factor <= 1.0, Some(lake_dist as f32),
Some(lake_dist as f32), alt_for_river,
alt_for_river, downhill_water_alt,
downhill_water_alt, alt_for_river,
alt_for_river, river_scale_factor as f32,
river_scale_factor as f32, ))
)) },
}, RiverKind::River { .. } => {
RiverKind::River { .. } => { let (_, _, river_width, (_, (river_pos, _), _)) =
let (_, _, river_width, (_, (river_pos, _), _)) = max_border_river_dist.unwrap();
max_border_river_dist.unwrap(); let river_dist = wposf.distance(river_pos);
let river_dist = wposf.distance(river_pos);
// FIXME: Make water altitude accurate. // FIXME: Make water altitude accurate.
Some(( Some((
river_scale_factor <= 1.0, river_scale_factor <= 1.0,
Some((river_dist - river_width * 0.5) as f32), Some((river_dist - river_width * 0.5) as f32),
alt_for_river, alt_for_river,
downhill_water_alt, downhill_water_alt,
alt_for_river, alt_for_river,
river_scale_factor as f32, river_scale_factor as f32,
)) ))
}, },
} }
}) })
.unwrap_or(( .unwrap_or((
false, false,
None, None,
alt_for_river, alt_for_river,
downhill_water_alt, downhill_water_alt,
alt_for_river, alt_for_river,
river_scale_factor as f32, river_scale_factor as f32,
)) ))
}); });
(in_water, water_dist, new_alt, new_water_alt, riverless_alt, warp_factor) (
in_water,
water_dist,
new_alt,
new_water_alt,
riverless_alt,
warp_factor,
)
} else { } else {
(false, None, alt_for_river, downhill_water_alt, alt_for_river, 1.0) (
false,
None,
alt_for_river,
downhill_water_alt,
alt_for_river,
1.0,
)
}; };
let warp_factor = warp_factor * chunk_warp_factor; let warp_factor = warp_factor * chunk_warp_factor;
// NOTE: To disable warp, uncomment this line. // NOTE: To disable warp, uncomment this line.
@ -1106,8 +1134,13 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
), ),
sub_surface_color, sub_surface_color,
// No growing directly on bedrock. // No growing directly on bedrock.
// And, no growing on sites that don't want them TODO: More precise than this when we apply trees as a post-processing layer // And, no growing on sites that don't want them TODO: More precise than this when we
tree_density: if sim_chunk.sites.iter().all(|site| site.spawn_rules(wpos).trees) { // apply trees as a post-processing layer
tree_density: if sim_chunk
.sites
.iter()
.all(|site| 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 {
0.0 0.0

View File

@ -4,12 +4,12 @@
mod all; mod all;
mod block; mod block;
pub mod civ;
mod column; mod column;
pub mod config; pub mod config;
pub mod sim; pub mod sim;
pub mod site; pub mod site;
pub mod util; pub mod util;
pub mod civ;
// Reexports // Reexports
pub use crate::config::CONFIG; pub use crate::config::CONFIG;
@ -72,17 +72,20 @@ impl World {
let chunk_wpos2d = Vec2::from(chunk_pos) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32); let chunk_wpos2d = Vec2::from(chunk_pos) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32);
let grid_border = 4; let grid_border = 4;
let zcache_grid = let zcache_grid = Grid::populate_from(
Grid::populate_from(TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + grid_border * 2, |offs| { TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + grid_border * 2,
sampler.get_z_cache(chunk_wpos2d - grid_border + offs) |offs| sampler.get_z_cache(chunk_wpos2d - grid_border + offs),
}); );
let air = Block::empty(); let air = Block::empty();
let stone = Block::new(BlockKind::Dense, zcache_grid let stone = Block::new(
.get(grid_border + TerrainChunkSize::RECT_SIZE.map(|e| e as i32) / 2) BlockKind::Dense,
.and_then(|zcache| zcache.as_ref()) zcache_grid
.map(|zcache| zcache.sample.stone_col) .get(grid_border + TerrainChunkSize::RECT_SIZE.map(|e| e as i32) / 2)
.unwrap_or(Rgb::new(125, 120, 130))); .and_then(|zcache| zcache.as_ref())
.map(|zcache| zcache.sample.stone_col)
.unwrap_or(Rgb::new(125, 120, 130)),
);
let water = Block::new(BlockKind::Water, Rgb::new(60, 90, 190)); let water = Block::new(BlockKind::Water, Rgb::new(60, 90, 190));
let _chunk_size2d = TerrainChunkSize::RECT_SIZE; let _chunk_size2d = TerrainChunkSize::RECT_SIZE;

View File

@ -218,9 +218,7 @@ impl RiverData {
pub fn near_river(&self) -> bool { self.is_river() || self.neighbor_rivers.len() > 0 } pub fn near_river(&self) -> bool { self.is_river() || self.neighbor_rivers.len() > 0 }
pub fn near_water(&self) -> bool { pub fn near_water(&self) -> bool { self.near_river() || self.is_lake() || self.is_ocean() }
self.near_river() || self.is_lake() || self.is_ocean()
}
} }
/// Draw rivers and assign them heights, widths, and velocities. Take some /// Draw rivers and assign them heights, widths, and velocities. Take some

View File

@ -24,17 +24,17 @@ pub use self::{
use crate::{ use crate::{
all::ForestKind, all::ForestKind,
block::BlockGen, block::BlockGen,
civ::Place,
column::ColumnGen, column::ColumnGen,
site::{Settlement, Site}, site::{Settlement, Site},
civ::Place,
util::{seed_expan, FastNoise, RandomField, Sampler, StructureGen2d}, util::{seed_expan, FastNoise, RandomField, Sampler, StructureGen2d},
CONFIG, CONFIG,
}; };
use common::{ use common::{
assets, assets,
store::Id,
terrain::{BiomeKind, TerrainChunkSize}, terrain::{BiomeKind, TerrainChunkSize},
vol::RectVolSize, vol::RectVolSize,
store::Id,
}; };
use hashbrown::HashMap; use hashbrown::HashMap;
use noise::{ use noise::{
@ -1305,9 +1305,7 @@ impl WorldSim {
this this
} }
pub fn get_size(&self) -> Vec2<u32> { pub fn get_size(&self) -> Vec2<u32> { WORLD_SIZE.map(|e| e as u32) }
WORLD_SIZE.map(|e| e as u32)
}
/// 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.
@ -1558,9 +1556,11 @@ impl WorldSim {
pub fn get_gradient_approx(&self, chunk_pos: Vec2<i32>) -> Option<f32> { pub fn get_gradient_approx(&self, chunk_pos: Vec2<i32>) -> Option<f32> {
let a = self.get(chunk_pos)?; let a = self.get(chunk_pos)?;
if let Some(downhill) = a.downhill { if let Some(downhill) = a.downhill {
let b = self.get(downhill.map2(Vec2::from(TerrainChunkSize::RECT_SIZE), |e, sz: u32| { let b = self.get(
e / (sz as i32) downhill.map2(Vec2::from(TerrainChunkSize::RECT_SIZE), |e, sz: u32| {
}))?; e / (sz as i32)
}),
)?;
Some((a.alt - b.alt).abs() / TerrainChunkSize::RECT_SIZE.x as f32) Some((a.alt - b.alt).abs() / TerrainChunkSize::RECT_SIZE.x as f32)
} else { } else {
Some(0.0) Some(0.0)
@ -1868,7 +1868,8 @@ impl SimChunk {
) )
}; };
//let cliff = gen_ctx.cliff_nz.get((wposf.div(2048.0)).into_array()) as f32 + chaos * 0.2; //let cliff = gen_ctx.cliff_nz.get((wposf.div(2048.0)).into_array()) as f32 +
// chaos * 0.2;
let cliff = 0.0; // Disable cliffs let cliff = 0.0; // Disable cliffs
// Logistic regression. Make sure x ∈ (0, 1). // Logistic regression. Make sure x ∈ (0, 1).
@ -2033,7 +2034,9 @@ impl SimChunk {
} }
} }
pub fn is_underwater(&self) -> bool { self.water_alt > self.alt || self.river.river_kind.is_some() } pub fn is_underwater(&self) -> bool {
self.water_alt > self.alt || self.river.river_kind.is_some()
}
pub fn get_base_z(&self) -> f32 { self.alt - self.chaos * 50.0 - 16.0 } pub fn get_base_z(&self) -> f32 { self.alt - self.chaos * 50.0 - 16.0 }

View File

@ -1,19 +1,19 @@
use super::SpawnRules;
use crate::{ use crate::{
column::ColumnSample, column::ColumnSample,
sim::{SimChunk, WorldSim}, sim::{SimChunk, WorldSim},
util::{attempt, Grid, RandomField, Sampler, StructureGen2d},
site::BlockMask, site::BlockMask,
util::{attempt, Grid, RandomField, Sampler, StructureGen2d},
}; };
use super::SpawnRules;
use common::{ use common::{
astar::Astar, astar::Astar,
comp::Alignment,
generation::{ChunkSupplement, EntityInfo, EntityKind},
path::Path, path::Path,
spiral::Spiral2d, spiral::Spiral2d,
terrain::{Block, BlockKind, TerrainChunkSize},
vol::{BaseVol, RectSizedVol, RectVolSize, ReadVol, WriteVol, Vox},
store::{Id, Store}, store::{Id, Store},
generation::{ChunkSupplement, EntityInfo}, terrain::{Block, BlockKind, TerrainChunkSize},
comp::{Alignment}, vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol},
}; };
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
use rand::prelude::*; use rand::prelude::*;
@ -22,16 +22,13 @@ use vek::*;
impl WorldSim { impl WorldSim {
fn can_host_dungeon(&self, pos: Vec2<i32>) -> bool { fn can_host_dungeon(&self, pos: Vec2<i32>) -> bool {
self self.get(pos)
.get(pos) .map(|chunk| !chunk.near_cliffs && !chunk.river.is_river() && !chunk.river.is_lake())
.map(|chunk| {
!chunk.near_cliffs && !chunk.river.is_river() && !chunk.river.is_lake()
})
.unwrap_or(false)
&& self
.get_gradient_approx(pos)
.map(|grad| grad > 0.25 && grad < 1.5)
.unwrap_or(false) .unwrap_or(false)
&& self
.get_gradient_approx(pos)
.map(|grad| grad > 0.25 && grad < 1.5)
.unwrap_or(false)
} }
} }
@ -52,7 +49,11 @@ impl Dungeon {
let mut ctx = GenCtx { sim, rng }; let mut ctx = GenCtx { sim, rng };
let mut this = Self { let mut this = Self {
origin: wpos, origin: wpos,
alt: ctx.sim.and_then(|sim| sim.get_alt_approx(wpos)).unwrap_or(0.0) as i32 + 6, alt: ctx
.sim
.and_then(|sim| sim.get_alt_approx(wpos))
.unwrap_or(0.0) as i32
+ 6,
noise: RandomField::new(ctx.rng.gen()), noise: RandomField::new(ctx.rng.gen()),
floors: (0..6) floors: (0..6)
.scan(Vec2::zero(), |stair_tile, level| { .scan(Vec2::zero(), |stair_tile, level| {
@ -196,23 +197,25 @@ pub struct Floor {
const FLOOR_SIZE: Vec2<i32> = Vec2::new(18, 18); const FLOOR_SIZE: Vec2<i32> = Vec2::new(18, 18);
impl Floor { impl Floor {
pub fn generate(ctx: &mut GenCtx<impl Rng>, stair_tile: Vec2<i32>, level: i32) -> (Self, Vec2<i32>) { pub fn generate(
let new_stair_tile = std::iter::from_fn(|| Some(FLOOR_SIZE.map(|sz| ctx.rng.gen_range(-sz / 2 + 1, sz / 2)))) ctx: &mut GenCtx<impl Rng>,
.filter(|pos| *pos != stair_tile) stair_tile: Vec2<i32>,
.take(8) level: i32,
.max_by_key(|pos| (*pos - stair_tile).map(|e| e.abs()).sum()) ) -> (Self, Vec2<i32>) {
.unwrap(); let new_stair_tile = std::iter::from_fn(|| {
Some(FLOOR_SIZE.map(|sz| ctx.rng.gen_range(-sz / 2 + 1, sz / 2)))
})
.filter(|pos| *pos != stair_tile)
.take(8)
.max_by_key(|pos| (*pos - stair_tile).map(|e| e.abs()).sum())
.unwrap();
let tile_offset = -FLOOR_SIZE / 2; let tile_offset = -FLOOR_SIZE / 2;
let mut this = Floor { let mut this = Floor {
tile_offset, tile_offset,
tiles: Grid::new(FLOOR_SIZE, Tile::Solid), tiles: Grid::new(FLOOR_SIZE, Tile::Solid),
rooms: Store::default(), rooms: Store::default(),
solid_depth: if level == 0 { solid_depth: if level == 0 { 80 } else { 13 * 2 },
80
} else {
13 * 2
},
hollow_depth: 13, hollow_depth: 13,
stair_tile: new_stair_tile - tile_offset, stair_tile: new_stair_tile - tile_offset,
}; };
@ -224,10 +227,16 @@ impl Floor {
this.create_route(ctx, a.center(), b.center(), true); this.create_route(ctx, a.center(), b.center(), true);
} }
} }
this.create_route(ctx, stair_tile - tile_offset, new_stair_tile - tile_offset, false); this.create_route(
ctx,
stair_tile - tile_offset,
new_stair_tile - tile_offset,
false,
);
this.tiles.set(stair_tile - tile_offset, Tile::UpStair); this.tiles.set(stair_tile - tile_offset, Tile::UpStair);
this.tiles.set(new_stair_tile - tile_offset, Tile::DownStair); this.tiles
.set(new_stair_tile - tile_offset, Tile::DownStair);
(this, new_stair_tile) (this, new_stair_tile)
} }
@ -238,15 +247,16 @@ impl Floor {
for _ in 0..n { for _ in 0..n {
let area = match attempt(30, || { let area = match attempt(30, || {
let sz = Vec2::<i32>::zero().map(|_| ctx.rng.gen_range(dim_limits.0, dim_limits.1)); let sz = Vec2::<i32>::zero().map(|_| ctx.rng.gen_range(dim_limits.0, dim_limits.1));
let pos = FLOOR_SIZE.map2(sz, |floor_sz, room_sz| ctx.rng.gen_range(0, floor_sz + 1 - room_sz)); let pos = FLOOR_SIZE.map2(sz, |floor_sz, room_sz| {
ctx.rng.gen_range(0, floor_sz + 1 - room_sz)
});
let area = Rect::from((pos, Extent2::from(sz))); let area = Rect::from((pos, Extent2::from(sz)));
let area_border = Rect::from((pos - 1, Extent2::from(sz) + 2)); // The room, but with some personal space let area_border = Rect::from((pos - 1, Extent2::from(sz) + 2)); // The room, but with some personal space
// Ensure no overlap // Ensure no overlap
if self.rooms if self.rooms.iter().any(|r| {
.iter() r.area.collides_with_rect(area_border) || r.area.contains_point(self.stair_tile)
.any(|r| r.area.collides_with_rect(area_border) || r.area.contains_point(self.stair_tile)) }) {
{
return None; return None;
} }
@ -265,18 +275,27 @@ impl Floor {
for x in 0..area.extent().w { for x in 0..area.extent().w {
for y in 0..area.extent().h { for y in 0..area.extent().h {
self.tiles.set(area.position() + Vec2::new(x, y), Tile::Room(room)); self.tiles
.set(area.position() + Vec2::new(x, y), Tile::Room(room));
} }
} }
} }
} }
fn create_route(&mut self, ctx: &mut GenCtx<impl Rng>, a: Vec2<i32>, b: Vec2<i32>, optimise_longest: bool) { fn create_route(
&mut self,
ctx: &mut GenCtx<impl Rng>,
a: Vec2<i32>,
b: Vec2<i32>,
optimise_longest: bool,
) {
let sim = &ctx.sim; let sim = &ctx.sim;
let heuristic = move |l: &Vec2<i32>| if optimise_longest { let heuristic = move |l: &Vec2<i32>| {
(l.distance_squared(b) as f32).sqrt() if optimise_longest {
} else { (l.distance_squared(b) as f32).sqrt()
100.0 - (l.distance_squared(b) as f32).sqrt() } else {
100.0 - (l.distance_squared(b) as f32).sqrt()
}
}; };
let neighbors = |l: &Vec2<i32>| { let neighbors = |l: &Vec2<i32>| {
let l = *l; let l = *l;
@ -294,7 +313,13 @@ impl Floor {
let satisfied = |l: &Vec2<i32>| *l == b; let satisfied = |l: &Vec2<i32>| *l == b;
let mut astar = Astar::new(20000, a, heuristic); let mut astar = Astar::new(20000, a, heuristic);
let path = astar let path = astar
.poll(FLOOR_SIZE.product() as usize + 1, heuristic, neighbors, transition, satisfied) .poll(
FLOOR_SIZE.product() as usize + 1,
heuristic,
neighbors,
transition,
satisfied,
)
.into_path() .into_path()
.expect("No route between locations - this shouldn't be able to happen"); .expect("No route between locations - this shouldn't be able to happen");
@ -305,11 +330,19 @@ impl Floor {
} }
} }
pub fn apply_supplement(&self, area: Aabr<i32>, origin: Vec3<i32>, supplement: &mut ChunkSupplement) { pub fn apply_supplement(
let align = |e: i32| e.div_euclid(TILE_SIZE) + if e.rem_euclid(TILE_SIZE) > TILE_SIZE / 2 { &self,
1 area: Aabr<i32>,
} else { origin: Vec3<i32>,
0 supplement: &mut ChunkSupplement,
) {
let align = |e: i32| {
e.div_euclid(TILE_SIZE)
+ if e.rem_euclid(TILE_SIZE) > TILE_SIZE / 2 {
1
} else {
0
}
}; };
let aligned_area = Aabr { let aligned_area = Aabr {
min: area.min.map(align) + self.tile_offset, min: area.min.map(align) + self.tile_offset,
@ -321,10 +354,16 @@ impl Floor {
let tile_pos = Vec2::new(x, y); let tile_pos = Vec2::new(x, y);
if let Some(Tile::Room(room)) = self.tiles.get(tile_pos) { if let Some(Tile::Room(room)) = self.tiles.get(tile_pos) {
let room = &self.rooms[*room]; let room = &self.rooms[*room];
if room.enemies && tile_pos.x % 4 == 0 && tile_pos.y % 4 == 0 { // Bad if room.enemies && tile_pos.x % 4 == 0 && tile_pos.y % 4 == 0 {
let entity = EntityInfo::at((origin + Vec3::from(self.tile_offset + tile_pos) * TILE_SIZE + TILE_SIZE / 2).map(|e| e as f32)) // Bad
.into_giant() let entity = EntityInfo::at(
.with_alignment(Alignment::Enemy); (origin
+ Vec3::from(self.tile_offset + tile_pos) * TILE_SIZE
+ TILE_SIZE / 2)
.map(|e| e as f32),
)
.into_giant()
.with_alignment(Alignment::Enemy);
supplement.add_entity(entity); supplement.add_entity(entity);
} }
} }
@ -332,22 +371,26 @@ impl Floor {
} }
} }
pub fn total_depth(&self) -> i32 { pub fn total_depth(&self) -> i32 { self.solid_depth + self.hollow_depth }
self.solid_depth + self.hollow_depth
}
pub fn nearest_wall(&self, rpos: Vec2<i32>) -> Option<Vec2<i32>> { pub fn nearest_wall(&self, rpos: Vec2<i32>) -> Option<Vec2<i32>> {
let tile_pos = rpos.map(|e| e.div_euclid(TILE_SIZE)); let tile_pos = rpos.map(|e| e.div_euclid(TILE_SIZE));
let tile_center = tile_pos * TILE_SIZE + TILE_SIZE / 2; let tile_center = tile_pos * TILE_SIZE + TILE_SIZE / 2;
DIRS DIRS.iter()
.iter()
.map(|dir| tile_pos + *dir) .map(|dir| tile_pos + *dir)
.filter(|other_tile_pos| self.tiles.get(*other_tile_pos).filter(|tile| tile.is_passable()).is_none()) .filter(|other_tile_pos| {
.map(|other_tile_pos| rpos.clamped( self.tiles
other_tile_pos * TILE_SIZE, .get(*other_tile_pos)
(other_tile_pos + 1) * TILE_SIZE - 1, .filter(|tile| tile.is_passable())
)) .is_none()
})
.map(|other_tile_pos| {
rpos.clamped(
other_tile_pos * TILE_SIZE,
(other_tile_pos + 1) * TILE_SIZE - 1,
)
})
.min_by_key(|nearest| rpos.distance_squared(*nearest)) .min_by_key(|nearest| rpos.distance_squared(*nearest))
} }
@ -365,7 +408,11 @@ impl Floor {
if (pos.xy().magnitude_squared() as f32) < inner_radius.powf(2.0) { if (pos.xy().magnitude_squared() as f32) < inner_radius.powf(2.0) {
stone stone
} else if (pos.xy().magnitude_squared() as f32) < radius.powf(2.0) { } else if (pos.xy().magnitude_squared() as f32) < radius.powf(2.0) {
if ((pos.x as f32).atan2(pos.y as f32) / (f32::consts::PI * 2.0) * stretch + pos.z as f32).rem_euclid(stretch) < 1.5 { if ((pos.x as f32).atan2(pos.y as f32) / (f32::consts::PI * 2.0) * stretch
+ pos.z as f32)
.rem_euclid(stretch)
< 1.5
{
stone stone
} else { } else {
empty empty
@ -376,38 +423,54 @@ impl Floor {
}; };
let wall_thickness = 3.0; let wall_thickness = 3.0;
let dist_to_wall = self.nearest_wall(rpos).map(|nearest| (nearest.distance_squared(rpos) as f32).sqrt()).unwrap_or(TILE_SIZE as f32); let dist_to_wall = self
let tunnel_dist = 1.0 - (dist_to_wall.powf(2.0) - wall_thickness).max(0.0).sqrt() / TILE_SIZE as f32; .nearest_wall(rpos)
.map(|nearest| (nearest.distance_squared(rpos) as f32).sqrt())
.unwrap_or(TILE_SIZE as f32);
let tunnel_dist =
1.0 - (dist_to_wall.powf(2.0) - wall_thickness).max(0.0).sqrt() / TILE_SIZE as f32;
move |z| { move |z| match self.tiles.get(tile_pos) {
match self.tiles.get(tile_pos) { Some(Tile::Solid) => BlockMask::nothing(),
Some(Tile::Solid) => BlockMask::nothing(), Some(Tile::Tunnel) => {
Some(Tile::Tunnel) => { if (z as f32) < 8.0 - 8.0 * tunnel_dist.powf(4.0) {
if (z as f32) < 8.0 - 8.0 * tunnel_dist.powf(4.0) { empty
empty } else {
} else { BlockMask::nothing()
BlockMask::nothing() }
} },
}, Some(Tile::Room(_)) | Some(Tile::DownStair)
Some(Tile::Room(_)) | Some(Tile::DownStair) if dist_to_wall < wall_thickness || z as f32 >= self.hollow_depth as f32 - 13.0 * tunnel_dist.powf(4.0) => BlockMask::nothing(), if dist_to_wall < wall_thickness
Some(Tile::Room(room)) => { || z as f32 >= self.hollow_depth as f32 - 13.0 * tunnel_dist.powf(4.0) =>
let room = &self.rooms[*room]; {
if z == 0 && RandomField::new(room.seed).chance(Vec3::from(pos), room.loot_density) { BlockMask::nothing()
BlockMask::new(Block::new(BlockKind::Chest, Rgb::white()), 1) },
} else { Some(Tile::Room(room)) => {
empty let room = &self.rooms[*room];
} if z == 0 && RandomField::new(room.seed).chance(Vec3::from(pos), room.loot_density)
}, {
Some(Tile::DownStair) => make_staircase(Vec3::new(rtile_pos.x, rtile_pos.y, z), 0.0, 0.5, 9.0).resolve_with(empty), BlockMask::new(Block::new(BlockKind::Chest, Rgb::white()), 1)
Some(Tile::UpStair) => { } else {
let mut block = make_staircase(Vec3::new(rtile_pos.x, rtile_pos.y, z), TILE_SIZE as f32 / 2.0, 0.5, 9.0); empty
if z < self.hollow_depth { }
block = block.resolve_with(empty); },
} Some(Tile::DownStair) => {
block make_staircase(Vec3::new(rtile_pos.x, rtile_pos.y, z), 0.0, 0.5, 9.0)
}, .resolve_with(empty)
None => BlockMask::nothing(), },
} Some(Tile::UpStair) => {
let mut block = make_staircase(
Vec3::new(rtile_pos.x, rtile_pos.y, z),
TILE_SIZE as f32 / 2.0,
0.5,
9.0,
);
if z < self.hollow_depth {
block = block.resolve_with(empty);
}
block
},
None => BlockMask::nothing(),
} }
} }
} }

View File

@ -1,18 +1,17 @@
mod settlement;
mod dungeon; mod dungeon;
mod settlement;
// Reexports // Reexports
pub use self::settlement::Settlement; pub use self::{dungeon::Dungeon, settlement::Settlement};
pub use self::dungeon::Dungeon;
use crate::{ use crate::{
column::ColumnSample, column::ColumnSample,
util::{Grid, Sampler}, util::{Grid, Sampler},
}; };
use common::{ use common::{
terrain::Block,
vol::{Vox, BaseVol, RectSizedVol, ReadVol, WriteVol},
generation::ChunkSupplement, generation::ChunkSupplement,
terrain::Block,
vol::{BaseVol, ReadVol, RectSizedVol, Vox, WriteVol},
}; };
use std::{fmt, sync::Arc}; use std::{fmt, sync::Arc};
use vek::*; use vek::*;
@ -24,9 +23,7 @@ pub struct BlockMask {
} }
impl BlockMask { impl BlockMask {
pub fn new(block: Block, priority: i32) -> Self { pub fn new(block: Block, priority: i32) -> Self { Self { block, priority } }
Self { block, priority }
}
pub fn nothing() -> Self { pub fn nothing() -> Self {
Self { Self {
@ -62,11 +59,7 @@ pub struct SpawnRules {
} }
impl Default for SpawnRules { impl Default for SpawnRules {
fn default() -> Self { fn default() -> Self { Self { trees: true } }
Self {
trees: true,
}
}
} }
#[derive(Clone)] #[derive(Clone)]
@ -109,7 +102,9 @@ impl Site {
supplement: &mut ChunkSupplement, supplement: &mut ChunkSupplement,
) { ) {
match self { match self {
Site::Settlement(settlement) => settlement.apply_supplement(wpos2d, get_column, supplement), Site::Settlement(settlement) => {
settlement.apply_supplement(wpos2d, get_column, supplement)
},
Site::Dungeon(dungeon) => dungeon.apply_supplement(wpos2d, get_column, supplement), Site::Dungeon(dungeon) => dungeon.apply_supplement(wpos2d, get_column, supplement),
} }
} }

View File

@ -1,17 +1,14 @@
use vek::*; use super::{super::skeleton::*, Archetype};
use rand::prelude::*; use crate::{
site::BlockMask,
util::{RandomField, Sampler},
};
use common::{ use common::{
terrain::{Block, BlockKind}, terrain::{Block, BlockKind},
vol::Vox, vol::Vox,
}; };
use crate::{ use rand::prelude::*;
util::{RandomField, Sampler}, use vek::*;
site::BlockMask,
};
use super::{
Archetype,
super::skeleton::*,
};
const COLOR_THEMES: [Rgb<u8>; 11] = [ const COLOR_THEMES: [Rgb<u8>; 11] = [
Rgb::new(0x1D, 0x4D, 0x45), Rgb::new(0x1D, 0x4D, 0x45),
@ -53,8 +50,21 @@ enum StoreyFill {
} }
impl StoreyFill { impl StoreyFill {
fn has_lower(&self) -> bool { if let StoreyFill::All = self { true } else { false } } fn has_lower(&self) -> bool {
fn has_upper(&self) -> bool { if let StoreyFill::None = self { false } else { true } } if let StoreyFill::All = self {
true
} else {
false
}
}
fn has_upper(&self) -> bool {
if let StoreyFill::None = self {
false
} else {
true
}
}
} }
pub struct Attr { pub struct Attr {
@ -116,16 +126,21 @@ impl Archetype for House {
.iter() .iter()
.map(|flip| (0..branches_per_side).map(move |i| (i, *flip))) .map(|flip| (0..branches_per_side).map(move |i| (i, *flip)))
.flatten() .flatten()
.filter_map(|(i, flip)| if rng.gen() { .filter_map(|(i, flip)| {
Some((i as i32 * len / (branches_per_side - 1).max(1) as i32, Branch { if rng.gen() {
len: rng.gen_range(8, 16) * flip, Some((
attr: Attr::generate(rng, locus), i as i32 * len / (branches_per_side - 1).max(1) as i32,
locus: (6 + rng.gen_range(0, 3)).min(locus), Branch {
border: 4, len: rng.gen_range(8, 16) * flip,
children: Vec::new(), attr: Attr::generate(rng, locus),
})) locus: (6 + rng.gen_range(0, 3)).min(locus),
} else { border: 4,
None children: Vec::new(),
},
))
} else {
None
}
}) })
.collect(), .collect(),
}, },
@ -155,8 +170,17 @@ impl Archetype for House {
let profile = Vec2::new(bound_offset.x, z); let profile = Vec2::new(bound_offset.x, z);
let make_block = |r, g, b| { let make_block = |r, g, b| {
let nz = self.noise.get(Vec3::new(center_offset.x, center_offset.y, z * 8)); let nz = self
BlockMask::new(Block::new(BlockKind::Normal, Rgb::new(r, g, b).map(|e: u8| e.saturating_add((nz & 0x0F) as u8).saturating_sub(8))), 2) .noise
.get(Vec3::new(center_offset.x, center_offset.y, z * 8));
BlockMask::new(
Block::new(
BlockKind::Normal,
Rgb::new(r, g, b)
.map(|e: u8| e.saturating_add((nz & 0x0F) as u8).saturating_sub(8)),
),
2,
)
}; };
let facade_layer = 3; let facade_layer = 3;
@ -168,7 +192,8 @@ impl Archetype for House {
let log = make_block(60, 45, 30); let log = make_block(60, 45, 30);
let floor = make_block(100, 75, 50); let floor = make_block(100, 75, 50);
let wall = make_block(200, 180, 150).with_priority(facade_layer); let wall = make_block(200, 180, 150).with_priority(facade_layer);
let roof = make_block(self.roof_color.r, self.roof_color.g, self.roof_color.b).with_priority(facade_layer); let roof = make_block(self.roof_color.r, self.roof_color.g, self.roof_color.b)
.with_priority(facade_layer);
let empty = BlockMask::nothing(); let empty = BlockMask::nothing();
let internal = BlockMask::new(Block::empty(), structural_layer); let internal = BlockMask::new(Block::empty(), structural_layer);
let fire = BlockMask::new(Block::new(BlockKind::Ember, Rgb::white()), foundation_layer); let fire = BlockMask::new(Block::new(BlockKind::Ember, Rgb::white()), foundation_layer);
@ -176,13 +201,19 @@ impl Archetype for House {
let ceil_height = 6; let ceil_height = 6;
let lower_width = branch.locus - 1; let lower_width = branch.locus - 1;
let upper_width = branch.locus; let upper_width = branch.locus;
let width = if profile.y >= ceil_height { upper_width } else { lower_width }; let width = if profile.y >= ceil_height {
upper_width
} else {
lower_width
};
let foundation_height = 0 - (dist - width - 1).max(0); let foundation_height = 0 - (dist - width - 1).max(0);
let roof_top = 8 + width; let roof_top = 8 + width;
if let Pillar::Chimney(chimney_top) = branch.attr.pillar { if let Pillar::Chimney(chimney_top) = branch.attr.pillar {
// Chimney shaft // Chimney shaft
if center_offset.map(|e| e.abs()).reduce_max() == 0 && profile.y >= foundation_height + 1 { if center_offset.map(|e| e.abs()).reduce_max() == 0
&& profile.y >= foundation_height + 1
{
return if profile.y == foundation_height + 1 { return if profile.y == foundation_height + 1 {
fire fire
} else { } else {
@ -193,7 +224,10 @@ impl Archetype for House {
// Chimney // Chimney
if center_offset.map(|e| e.abs()).reduce_max() <= 1 && profile.y < chimney_top { if center_offset.map(|e| e.abs()).reduce_max() <= 1 && profile.y < chimney_top {
// Fireplace // Fireplace
if center_offset.product() == 0 && profile.y > foundation_height + 1 && profile.y <= foundation_height + 3 { if center_offset.product() == 0
&& profile.y > foundation_height + 1
&& profile.y <= foundation_height + 3
{
return internal; return internal;
} else { } else {
return foundation; return foundation;
@ -201,16 +235,20 @@ impl Archetype for House {
} }
} }
if profile.y <= foundation_height && dist < width + 3 { // Foundations if profile.y <= foundation_height && dist < width + 3 {
// Foundations
if branch.attr.storey_fill.has_lower() { if branch.attr.storey_fill.has_lower() {
if dist == width - 1 { // Floor lining if dist == width - 1 {
// Floor lining
return log.with_priority(floor_layer); return log.with_priority(floor_layer);
} else if dist < width - 1 && profile.y == foundation_height { // Floor } else if dist < width - 1 && profile.y == foundation_height {
// Floor
return floor.with_priority(floor_layer); return floor.with_priority(floor_layer);
} }
} }
if dist < width && profile.y < foundation_height && profile.y >= foundation_height - 3 { // Basement if dist < width && profile.y < foundation_height && profile.y >= foundation_height - 3 {
// Basement
return internal; return internal;
} else { } else {
return foundation.with_priority(1); return foundation.with_priority(1);
@ -218,102 +256,122 @@ impl Archetype for House {
} }
// Roofs and walls // Roofs and walls
let do_roof_wall = |profile: Vec2<i32>, width, dist, bound_offset: Vec2<i32>, roof_top, mansard| { let do_roof_wall =
// Roof |profile: Vec2<i32>, width, dist, bound_offset: Vec2<i32>, roof_top, mansard| {
// Roof
let (roof_profile, roof_dist) = match &branch.attr.roof_style { let (roof_profile, roof_dist) = match &branch.attr.roof_style {
RoofStyle::Hip => (Vec2::new(dist, profile.y), dist), RoofStyle::Hip => (Vec2::new(dist, profile.y), dist),
RoofStyle::Gable => (profile, dist), RoofStyle::Gable => (profile, dist),
RoofStyle::Rounded => { RoofStyle::Rounded => {
let circular_dist = (bound_offset.map(|e| e.pow(4) as f32).sum().powf(0.25) - 0.5).ceil() as i32; let circular_dist = (bound_offset.map(|e| e.pow(4) as f32).sum().powf(0.25)
(Vec2::new(circular_dist, profile.y), circular_dist) - 0.5)
}, .ceil() as i32;
}; (Vec2::new(circular_dist, profile.y), circular_dist)
},
};
let roof_level = roof_top - roof_profile.x.max(mansard); let roof_level = roof_top - roof_profile.x.max(mansard);
if profile.y > roof_level { if profile.y > roof_level {
return None; return None;
}
// Roof
if profile.y == roof_level && roof_dist <= width + 2 {
let is_ribbing = ((profile.y - ceil_height) % 3 == 0 && self.roof_ribbing)
|| (bound_offset.x == bound_offset.y && self.roof_ribbing_diagonal);
if (roof_profile.x == 0 && mansard == 0) || roof_dist == width + 2 || is_ribbing { // Eaves
return Some(log);
} else {
return Some(roof);
} }
}
// Wall // Roof
if profile.y == roof_level && roof_dist <= width + 2 {
if dist == width && profile.y < roof_level { let is_ribbing = ((profile.y - ceil_height) % 3 == 0 && self.roof_ribbing)
if bound_offset.x == bound_offset.y || profile.y == ceil_height { // Support beams || (bound_offset.x == bound_offset.y && self.roof_ribbing_diagonal);
return Some(log); if (roof_profile.x == 0 && mansard == 0) || roof_dist == width + 2 || is_ribbing
} else if !branch.attr.storey_fill.has_lower() && profile.y < ceil_height { {
return Some(empty); // Eaves
} else if !branch.attr.storey_fill.has_upper() {
return Some(empty);
} else {
let frame_bounds = if profile.y >= ceil_height {
Aabr {
min: Vec2::new(-1, ceil_height + 2),
max: Vec2::new(1, ceil_height + 5),
}
} else {
Aabr {
min: Vec2::new(2, foundation_height + 2),
max: Vec2::new(width - 2, ceil_height - 2),
}
};
let window_bounds = Aabr {
min: (frame_bounds.min + 1).map2(frame_bounds.center(), |a, b| a.min(b)),
max: (frame_bounds.max - 1).map2(frame_bounds.center(), |a, b| a.max(b)),
};
// Window
if (frame_bounds.size() + 1).reduce_min() > 2 { // Window frame is large enough for a window
let surface_pos = Vec2::new(bound_offset.x, profile.y);
if window_bounds.contains_point(surface_pos) {
return Some(internal);
} else if frame_bounds.contains_point(surface_pos) {
return Some(log.with_priority(3));
};
}
// Wall
return Some(if branch.attr.central_supports && profile.x == 0 { // Support beams
log.with_priority(structural_layer)
} else {
wall
});
}
}
if dist < width { // Internals
if profile.y == ceil_height {
if profile.x == 0 {// Rafters
return Some(log); return Some(log);
} else if branch.attr.storey_fill.has_upper() { // Ceiling } else {
return Some(floor); return Some(roof);
} }
} else if (!branch.attr.storey_fill.has_lower() && profile.y < ceil_height)
|| (!branch.attr.storey_fill.has_upper() && profile.y >= ceil_height)
{
return Some(empty);
} else {
return Some(internal);
} }
}
None // Wall
};
if dist == width && profile.y < roof_level {
if bound_offset.x == bound_offset.y || profile.y == ceil_height {
// Support beams
return Some(log);
} else if !branch.attr.storey_fill.has_lower() && profile.y < ceil_height {
return Some(empty);
} else if !branch.attr.storey_fill.has_upper() {
return Some(empty);
} else {
let frame_bounds = if profile.y >= ceil_height {
Aabr {
min: Vec2::new(-1, ceil_height + 2),
max: Vec2::new(1, ceil_height + 5),
}
} else {
Aabr {
min: Vec2::new(2, foundation_height + 2),
max: Vec2::new(width - 2, ceil_height - 2),
}
};
let window_bounds = Aabr {
min: (frame_bounds.min + 1)
.map2(frame_bounds.center(), |a, b| a.min(b)),
max: (frame_bounds.max - 1)
.map2(frame_bounds.center(), |a, b| a.max(b)),
};
// Window
if (frame_bounds.size() + 1).reduce_min() > 2 {
// Window frame is large enough for a window
let surface_pos = Vec2::new(bound_offset.x, profile.y);
if window_bounds.contains_point(surface_pos) {
return Some(internal);
} else if frame_bounds.contains_point(surface_pos) {
return Some(log.with_priority(3));
};
}
// Wall
return Some(if branch.attr.central_supports && profile.x == 0 {
// Support beams
log.with_priority(structural_layer)
} else {
wall
});
}
}
if dist < width {
// Internals
if profile.y == ceil_height {
if profile.x == 0 {
// Rafters
return Some(log);
} else if branch.attr.storey_fill.has_upper() {
// Ceiling
return Some(floor);
}
} else if (!branch.attr.storey_fill.has_lower() && profile.y < ceil_height)
|| (!branch.attr.storey_fill.has_upper() && profile.y >= ceil_height)
{
return Some(empty);
} else {
return Some(internal);
}
}
None
};
let mut cblock = empty; let mut cblock = empty;
if let Some(block) = do_roof_wall(profile, width, dist, bound_offset, roof_top, branch.attr.mansard) { if let Some(block) = do_roof_wall(
profile,
width,
dist,
bound_offset,
roof_top,
branch.attr.mansard,
) {
cblock = cblock.resolve_with(block); cblock = cblock.resolve_with(block);
} }
@ -321,8 +379,15 @@ impl Archetype for House {
let profile = Vec2::new(center_offset.x.abs(), profile.y); let profile = Vec2::new(center_offset.x.abs(), profile.y);
let dist = center_offset.map(|e| e.abs()).reduce_max(); let dist = center_offset.map(|e| e.abs()).reduce_max();
if let Some(block) = do_roof_wall(profile, 4, dist, center_offset.map(|e| e.abs()), tower_top, branch.attr.mansard) { if let Some(block) = do_roof_wall(
cblock = cblock.resolve_with(block); profile,
4,
dist,
center_offset.map(|e| e.abs()),
tower_top,
branch.attr.mansard,
) {
cblock = cblock.resolve_with(block);
} }
} }

View File

@ -1,14 +1,11 @@
use vek::*; use super::{super::skeleton::*, Archetype};
use rand::prelude::*; use crate::site::BlockMask;
use common::{ use common::{
terrain::{Block, BlockKind}, terrain::{Block, BlockKind},
vol::Vox, vol::Vox,
}; };
use super::{ use rand::prelude::*;
Archetype, use vek::*;
super::skeleton::*,
};
use crate::site::BlockMask;
pub struct Keep; pub struct Keep;
@ -26,13 +23,18 @@ impl Archetype for Keep {
locus: 5 + rng.gen_range(0, 5), locus: 5 + rng.gen_range(0, 5),
border: 3, border: 3,
children: (0..rng.gen_range(0, 4)) children: (0..rng.gen_range(0, 4))
.map(|_| (rng.gen_range(-5, len + 5).clamped(0, len.max(1) - 1), Branch { .map(|_| {
len: rng.gen_range(5, 12) * if rng.gen() { 1 } else { -1 }, (
attr: Self::Attr::default(), rng.gen_range(-5, len + 5).clamped(0, len.max(1) - 1),
locus: 5 + rng.gen_range(0, 3), Branch {
border: 3, len: rng.gen_range(5, 12) * if rng.gen() { 1 } else { -1 },
children: Vec::new(), attr: Self::Attr::default(),
})) locus: 5 + rng.gen_range(0, 3),
border: 3,
children: Vec::new(),
},
)
})
.collect(), .collect(),
}, },
}; };
@ -50,9 +52,8 @@ impl Archetype for Keep {
) -> BlockMask { ) -> BlockMask {
let profile = Vec2::new(bound_offset.x, z); let profile = Vec2::new(bound_offset.x, z);
let make_block = |r, g, b| { let make_block =
BlockMask::new(Block::new(BlockKind::Normal, Rgb::new(r, g, b)), 2) |r, g, b| BlockMask::new(Block::new(BlockKind::Normal, Rgb::new(r, g, b)), 2);
};
let foundation = make_block(100, 100, 100); let foundation = make_block(100, 100, 100);
let log = make_block(60, 45, 30); let log = make_block(60, 45, 30);
@ -65,7 +66,8 @@ impl Archetype for Keep {
let roof_height = 12 + width; let roof_height = 12 + width;
let ceil_height = 16; let ceil_height = 16;
if profile.y <= 1 - (dist - width - 1).max(0) && dist < width + 3 { // Foundations if profile.y <= 1 - (dist - width - 1).max(0) && dist < width + 3 {
// Foundations
foundation foundation
} else if profile.y == ceil_height && dist < rampart_width { } else if profile.y == ceil_height && dist < rampart_width {
roof roof

View File

@ -1,15 +1,17 @@
pub mod house; pub mod house;
pub mod keep; pub mod keep;
use vek::*;
use rand::prelude::*;
use super::skeleton::*; use super::skeleton::*;
use crate::site::BlockMask; use crate::site::BlockMask;
use rand::prelude::*;
use vek::*;
pub trait Archetype { pub trait Archetype {
type Attr; type Attr;
fn generate<R: Rng>(rng: &mut R) -> (Self, Skeleton<Self::Attr>) where Self: Sized; fn generate<R: Rng>(rng: &mut R) -> (Self, Skeleton<Self::Attr>)
where
Self: Sized;
fn draw( fn draw(
&self, &self,
dist: i32, dist: i32,

View File

@ -1,13 +1,13 @@
mod skeleton;
mod archetype; mod archetype;
mod skeleton;
// Reexports // Reexports
pub use self::archetype::Archetype; pub use self::archetype::Archetype;
use vek::*;
use rand::prelude::*;
use self::skeleton::*; use self::skeleton::*;
use common::terrain::Block; use common::terrain::Block;
use rand::prelude::*;
use vek::*;
pub type HouseBuilding = Building<archetype::house::House>; pub type HouseBuilding = Building<archetype::house::House>;
@ -19,7 +19,8 @@ pub struct Building<A: Archetype> {
impl<A: Archetype> Building<A> { impl<A: Archetype> Building<A> {
pub fn generate(rng: &mut impl Rng, origin: Vec3<i32>) -> Self pub fn generate(rng: &mut impl Rng, origin: Vec3<i32>) -> Self
where A: Sized where
A: Sized,
{ {
let len = rng.gen_range(-8, 12).max(0); let len = rng.gen_range(-8, 12).max(0);
let (archetype, skel) = A::generate(rng); let (archetype, skel) = A::generate(rng);
@ -48,8 +49,11 @@ impl<A: Archetype> Building<A> {
pub fn sample(&self, pos: Vec3<i32>) -> Option<Block> { pub fn sample(&self, pos: Vec3<i32>) -> Option<Block> {
let rpos = pos - self.origin; let rpos = pos - self.origin;
self.skel.sample_closest(rpos.into(), |dist, bound_offset, center_offset, branch| { self.skel
self.archetype.draw(dist, bound_offset, center_offset, rpos.z, branch) .sample_closest(rpos.into(), |dist, bound_offset, center_offset, branch| {
}).finish() self.archetype
.draw(dist, bound_offset, center_offset, rpos.z, branch)
})
.finish()
} }
} }

View File

@ -1,5 +1,5 @@
use vek::*;
use crate::site::BlockMask; use crate::site::BlockMask;
use vek::*;
#[derive(Copy, Clone, PartialEq, Eq)] #[derive(Copy, Clone, PartialEq, Eq)]
pub enum Ori { pub enum Ori {
@ -32,7 +32,14 @@ pub struct Branch<T> {
} }
impl<T> Branch<T> { impl<T> Branch<T> {
fn for_each<'a>(&'a self, node: Vec2<i32>, ori: Ori, is_child: bool, parent_locus: i32, f: &mut impl FnMut(Vec2<i32>, Ori, &'a Branch<T>, bool, i32)) { fn for_each<'a>(
&'a self,
node: Vec2<i32>,
ori: Ori,
is_child: bool,
parent_locus: i32,
f: &mut impl FnMut(Vec2<i32>, Ori, &'a Branch<T>, bool, i32),
) {
f(node, ori, self, is_child, parent_locus); f(node, ori, self, is_child, parent_locus);
for (offset, child) in &self.children { for (offset, child) in &self.children {
child.for_each(node + ori.dir() * *offset, ori.flip(), true, self.locus, f); child.for_each(node + ori.dir() * *offset, ori.flip(), true, self.locus, f);
@ -48,7 +55,8 @@ pub struct Skeleton<T> {
impl<T> Skeleton<T> { impl<T> Skeleton<T> {
pub fn for_each<'a>(&'a self, mut f: impl FnMut(Vec2<i32>, Ori, &'a Branch<T>, bool, i32)) { pub fn for_each<'a>(&'a self, mut f: impl FnMut(Vec2<i32>, Ori, &'a Branch<T>, bool, i32)) {
self.root.for_each(self.ori.dir() * self.offset, self.ori, false, 0, &mut f); self.root
.for_each(self.ori.dir() * self.offset, self.ori, false, 0, &mut f);
} }
pub fn bounds(&self) -> Aabr<i32> { pub fn bounds(&self) -> Aabr<i32> {
@ -64,24 +72,35 @@ impl<T> Skeleton<T> {
bounds bounds
} }
pub fn sample_closest(&self, pos: Vec2<i32>, mut f: impl FnMut(i32, Vec2<i32>, Vec2<i32>, &Branch<T>) -> BlockMask) -> BlockMask { pub fn sample_closest(
&self,
pos: Vec2<i32>,
mut f: impl FnMut(i32, Vec2<i32>, Vec2<i32>, &Branch<T>) -> BlockMask,
) -> BlockMask {
let mut min = None::<(_, BlockMask)>; let mut min = None::<(_, BlockMask)>;
self.for_each(|node, ori, branch, is_child, parent_locus| { self.for_each(|node, ori, branch, is_child, parent_locus| {
let node2 = node + ori.dir() * branch.len; let node2 = node + ori.dir() * branch.len;
let node = node + if is_child { ori.dir() * branch.len.signum() * (branch.locus - parent_locus).clamped(0, branch.len.abs()) } else { Vec2::zero() }; let node = node
let bounds = Aabr::new_empty(node) + if is_child {
.expanded_to_contain_point(node2); ori.dir()
* branch.len.signum()
* (branch.locus - parent_locus).clamped(0, branch.len.abs())
} else {
Vec2::zero()
};
let bounds = Aabr::new_empty(node).expanded_to_contain_point(node2);
let bound_offset = if ori == Ori::East { let bound_offset = if ori == Ori::East {
Vec2::new( Vec2::new(
node.y - pos.y, node.y - pos.y,
pos.x - pos.x.clamped(bounds.min.x, bounds.max.x) pos.x - pos.x.clamped(bounds.min.x, bounds.max.x),
) )
} else { } else {
Vec2::new( Vec2::new(
node.x - pos.x, node.x - pos.x,
pos.y - pos.y.clamped(bounds.min.y, bounds.max.y) pos.y - pos.y.clamped(bounds.min.y, bounds.max.y),
) )
}.map(|e| e.abs()); }
.map(|e| e.abs());
let center_offset = if ori == Ori::East { let center_offset = if ori == Ori::East {
Vec2::new(pos.y - bounds.center().y, pos.x - bounds.center().x) Vec2::new(pos.y - bounds.center().y, pos.x - bounds.center().x)
} else { } else {
@ -89,10 +108,13 @@ impl<T> Skeleton<T> {
}; };
let dist = bound_offset.reduce_max(); let dist = bound_offset.reduce_max();
let dist_locus = dist - branch.locus; let dist_locus = dist - branch.locus;
if !is_child || match ori { if !is_child
Ori::East => (pos.x - node.x) * branch.len.signum() >= 0, || match ori {
Ori::North => (pos.y - node.y) * branch.len.signum() >= 0, Ori::East => (pos.x - node.x) * branch.len.signum() >= 0,
} || true { Ori::North => (pos.y - node.y) * branch.len.signum() >= 0,
}
|| true
{
let new_bm = f(dist, bound_offset, center_offset, branch); let new_bm = f(dist, bound_offset, center_offset, branch);
min = min min = min
.map(|(_, bm)| (dist_locus, bm.resolve_with(new_bm))) .map(|(_, bm)| (dist_locus, bm.resolve_with(new_bm)))

View File

@ -1,20 +1,20 @@
mod building; mod building;
use self::building::HouseBuilding;
use super::SpawnRules;
use crate::{ use crate::{
column::ColumnSample, column::ColumnSample,
sim::{SimChunk, WorldSim}, sim::{SimChunk, WorldSim},
util::{Grid, RandomField, Sampler, StructureGen2d}, util::{Grid, RandomField, Sampler, StructureGen2d},
}; };
use self::building::HouseBuilding;
use super::SpawnRules;
use common::{ use common::{
astar::Astar, astar::Astar,
generation::ChunkSupplement,
path::Path, path::Path,
spiral::Spiral2d, spiral::Spiral2d,
terrain::{Block, BlockKind, TerrainChunkSize},
vol::{BaseVol, RectSizedVol, RectVolSize, ReadVol, WriteVol, Vox},
store::{Id, Store}, store::{Id, Store},
generation::ChunkSupplement, terrain::{Block, BlockKind, TerrainChunkSize},
vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol},
}; };
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
use rand::prelude::*; use rand::prelude::*;
@ -74,16 +74,13 @@ pub fn center_of(p: [Vec2<f32>; 3]) -> Vec2<f32> {
impl WorldSim { impl WorldSim {
fn can_host_settlement(&self, pos: Vec2<i32>) -> bool { fn can_host_settlement(&self, pos: Vec2<i32>) -> bool {
self self.get(pos)
.get(pos) .map(|chunk| !chunk.near_cliffs && !chunk.river.is_river() && !chunk.river.is_lake())
.map(|chunk| {
!chunk.near_cliffs && !chunk.river.is_river() && !chunk.river.is_lake()
})
.unwrap_or(false)
&& self
.get_gradient_approx(pos)
.map(|grad| grad < 0.75)
.unwrap_or(false) .unwrap_or(false)
&& self
.get_gradient_approx(pos)
.map(|grad| grad < 0.75)
.unwrap_or(false)
} }
} }
@ -172,7 +169,8 @@ impl Settlement {
let cpos = wpos.map(|e| e.div_euclid(TerrainChunkSize::RECT_SIZE.x as i32)); let cpos = wpos.map(|e| e.div_euclid(TerrainChunkSize::RECT_SIZE.x as i32));
!sim.can_host_settlement(cpos) !sim.can_host_settlement(cpos)
}) })
|| rng.gen_range(0, 16) == 0 // Randomly consider some tiles inaccessible || rng.gen_range(0, 16) == 0
// Randomly consider some tiles inaccessible
{ {
self.land.set(tile, hazard); self.land.set(tile, hazard);
} }
@ -328,35 +326,49 @@ impl Settlement {
return; return;
}; };
for tile in Spiral2d::new().map(|offs| town_center + offs).take(16usize.pow(2)) { for tile in Spiral2d::new()
.map(|offs| town_center + offs)
.take(16usize.pow(2))
{
// 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 _ 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(|_| ctx.rng.gen_range(-(AREA_SIZE as i32) / 2, AREA_SIZE as i32 / 2)); + Vec2::<i32>::zero().map(|_| {
ctx.rng
.gen_range(-(AREA_SIZE as i32) / 2, AREA_SIZE as i32 / 2)
});
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 !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) || self
.land
.tile_at(tile_pos)
.map(|t| t.contains(WayKind::Path))
.unwrap_or(true)
{ {
continue; continue;
} }
let structure = Structure { let structure = Structure {
kind: StructureKind::House(HouseBuilding::generate(ctx.rng, Vec3::new( kind: StructureKind::House(HouseBuilding::generate(
house_pos.x, ctx.rng,
house_pos.y, Vec3::new(
ctx.sim house_pos.x,
.and_then(|sim| sim.get_alt_approx(self.origin + house_pos)) house_pos.y,
.unwrap_or(0.0) ctx.sim
.ceil() as i32, .and_then(|sim| sim.get_alt_approx(self.origin + house_pos))
))), .unwrap_or(0.0)
.ceil() as i32,
),
)),
}; };
let bounds = structure.bounds_2d(); let bounds = structure.bounds_2d();
// Check for collision with other structures // Check for collision with other structures
if self.structures if self
.structures
.iter() .iter()
.any(|s| s.bounds_2d().collides_with_aabr(bounds)) .any(|s| s.bounds_2d().collides_with_aabr(bounds))
{ {
@ -385,12 +397,13 @@ impl Settlement {
// Farmhouses // Farmhouses
// for _ in 0..ctx.rng.gen_range(1, 3) { // for _ in 0..ctx.rng.gen_range(1, 3) {
// let house_pos = base_tile.map(|e| e * AREA_SIZE as i32 + AREA_SIZE as i32 / 2) // let house_pos = base_tile.map(|e| e * AREA_SIZE as i32 + AREA_SIZE as i32
// + Vec2::new(ctx.rng.gen_range(-16, 16), ctx.rng.gen_range(-16, 16)); // / 2) + Vec2::new(ctx.rng.gen_range(-16, 16),
// ctx.rng.gen_range(-16, 16));
// self.structures.push(Structure { // self.structures.push(Structure {
// kind: StructureKind::House(HouseBuilding::generate(ctx.rng, Vec3::new( // kind: StructureKind::House(HouseBuilding::generate(ctx.rng,
// house_pos.x, // Vec3::new( house_pos.x,
// house_pos.y, // house_pos.y,
// ctx.sim // ctx.sim
// .and_then(|sim| sim.get_alt_approx(self.origin + house_pos)) // .and_then(|sim| sim.get_alt_approx(self.origin + house_pos))
@ -421,11 +434,15 @@ impl Settlement {
let field = self.land.new_plot(Plot::Field { let field = self.land.new_plot(Plot::Field {
farm, farm,
seed: rng.gen(), seed: rng.gen(),
crop: match rng.gen_range(0, 5) { crop: match rng.gen_range(0, 8) {
0 => Crop::Corn, 0 => Crop::Corn,
1 => Crop::Wheat, 1 => Crop::Wheat,
2 => Crop::Cabbage, 2 => Crop::Cabbage,
3 => Crop::Pumpkin, 3 => Crop::Pumpkin,
4 => Crop::Flax,
5 => Crop::Carrot,
6 => Crop::Tomato,
7 => Crop::Radish,
_ => Crop::Sunflower, _ => Crop::Sunflower,
}, },
}); });
@ -447,7 +464,8 @@ impl Settlement {
pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules { pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
SpawnRules { SpawnRules {
trees: self.land trees: self
.land
.get_at_block(wpos - self.origin) .get_at_block(wpos - self.origin)
.plot .plot
.map(|p| if let Plot::Hazard = p { true } else { false }) .map(|p| if let Plot::Hazard = p { true } else { false })
@ -484,19 +502,28 @@ impl Settlement {
let noisy_color = |col: Rgb<u8>, factor: u32| { let noisy_color = |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| (e as u32 + nz % (factor * 2)).saturating_sub(factor).min(255) as u8) col.map(|e| {
(e as u32 + nz % (factor * 2))
.saturating_sub(factor)
.min(255) as u8
})
}; };
// 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;
// Try to use the column at the centre of the path for sampling to make them flatter // Try to use the column at the centre of the path for sampling to make them
let col = get_column(offs + (nearest.floor().map(|e| e as i32) - rpos)).unwrap_or(col_sample); // flatter
let col = get_column(offs + (nearest.floor().map(|e| e as i32) - rpos))
.unwrap_or(col_sample);
let (bridge_offset, depth) = if let Some(water_dist) = col.water_dist { let (bridge_offset, depth) = if let Some(water_dist) = col.water_dist {
( (
((water_dist.max(0.0) * 0.2).min(f32::consts::PI).cos() + 1.0) * 5.0, ((water_dist.max(0.0) * 0.2).min(f32::consts::PI).cos() + 1.0) * 5.0,
((1.0 - ((water_dist + 2.0) * 0.3).min(0.0).cos().abs()) * (col.riverless_alt + 5.0 - col.alt).max(0.0) * 1.75 + 3.0) as i32, ((1.0 - ((water_dist + 2.0) * 0.3).min(0.0).cos().abs())
* (col.riverless_alt + 5.0 - col.alt).max(0.0)
* 1.75
+ 3.0) as i32,
) )
} else { } else {
(0.0, 3) (0.0, 3)
@ -528,10 +555,15 @@ 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(Rgb::new(150, 110, 60) Some(Plot::Town) => {
.map2(Rgb::iota(), |e: u8, i: i32| e Some(Rgb::new(150, 110, 60).map2(Rgb::iota(), |e: u8, i: i32| {
.saturating_add((self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, i * 5)) % 16) as u8) e.saturating_add(
.saturating_sub(8))), (self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, i * 5)) % 16)
as u8,
)
.saturating_sub(8)
}))
},
Some(Plot::Field { seed, crop, .. }) => { Some(Plot::Field { seed, crop, .. }) => {
let furrow_dirs = [ let furrow_dirs = [
Vec2::new(1, 0), Vec2::new(1, 0),
@ -542,37 +574,60 @@ impl Settlement {
let furrow_dir = furrow_dirs[*seed as usize % furrow_dirs.len()]; let furrow_dir = furrow_dirs[*seed as usize % furrow_dirs.len()];
let in_furrow = (wpos2d * furrow_dir).sum().rem_euclid(5) < 2; let in_furrow = (wpos2d * furrow_dir).sum().rem_euclid(5) < 2;
let roll = |seed, n| self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, seed * 5)) % n; let roll = |seed, n| {
self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, seed * 5)) % n
};
let dirt = Rgb::new(80, 55, 35).map(|e| e + (self.noise.get(Vec3::broadcast((seed % 4096 + 0) as i32)) % 32) as u8); let dirt = Rgb::new(80, 55, 35).map(|e| {
let mound = Rgb::new(70, 80, 30).map(|e| e + roll(0, 8) as u8).map(|e| e + (self.noise.get(Vec3::broadcast((seed % 4096 + 1) as i32)) % 32) as u8); e + (self.noise.get(Vec3::broadcast((seed % 4096 + 0) as i32)) % 32)
as u8
});
let mound =
Rgb::new(70, 80, 30).map(|e| e + roll(0, 8) as u8).map(|e| {
e + (self.noise.get(Vec3::broadcast((seed % 4096 + 1) as i32))
% 32) as u8
});
if in_furrow { if in_furrow {
if roll(0, 5) == 0 { if roll(0, 5) == 0 {
surface_block = match crop { surface_block = match crop {
Crop::Corn => Some(BlockKind::Corn), Crop::Corn => Some(BlockKind::Corn),
Crop::Wheat if roll(1, 2) == 0 => Some(BlockKind::WheatYellow), Crop::Wheat if roll(1, 2) == 0 => {
Some(BlockKind::WheatYellow)
},
Crop::Wheat => Some(BlockKind::WheatGreen), Crop::Wheat => Some(BlockKind::WheatGreen),
Crop::Cabbage if roll(2, 2) == 0 => Some(BlockKind::Cabbage), Crop::Cabbage if roll(2, 2) == 0 => {
Crop::Pumpkin if roll(3, 2) == 0 => Some(BlockKind::Pumpkin), Some(BlockKind::Cabbage)
},
Crop::Pumpkin if roll(3, 2) == 0 => {
Some(BlockKind::Pumpkin)
},
Crop::Flax if roll(4, 100) < 80 => Some(BlockKind::Flax),
Crop::Carrot if roll(5, 100) < 80 => {
Some(BlockKind::Carrot)
},
Crop::Tomato if roll(6, 100) < 80 => {
Some(BlockKind::Tomato)
},
Crop::Radish if roll(7, 100) < 80 => {
Some(BlockKind::Radish)
},
Crop::Sunflower => Some(BlockKind::Sunflower), Crop::Sunflower => Some(BlockKind::Sunflower),
_ => None, _ => None,
} }
.map(|kind| Block::new(kind, Rgb::white())); .map(|kind| Block::new(kind, Rgb::white()));
} }
} else { } else {
if roll(0, 30) == 0 { if roll(0, 30) == 0 {
surface_block = Some(Block::new(BlockKind::ShortGrass, Rgb::white())); surface_block =
Some(Block::new(BlockKind::ShortGrass, Rgb::white()));
} else if roll(0, 30) == 0 { } else if roll(0, 30) == 0 {
surface_block = Some(Block::new(BlockKind::MediumGrass, Rgb::white())); surface_block =
Some(Block::new(BlockKind::MediumGrass, Rgb::white()));
} }
} }
Some(if in_furrow { Some(if in_furrow { dirt } else { mound })
dirt
} else {
mound
})
}, },
_ => None, _ => None,
}; };
@ -589,7 +644,10 @@ impl Settlement {
vol.set(pos, Block::empty()); vol.set(pos, Block::empty());
} }
} else { } else {
vol.set(pos, Block::new(BlockKind::Normal, noisy_color(color, 4))); vol.set(
pos,
Block::new(BlockKind::Normal, noisy_color(color, 4)),
);
} }
} }
} }
@ -613,9 +671,7 @@ impl Settlement {
} as i32; } as i32;
for z in z_offset..12 { for z in z_offset..12 {
if dist / WayKind::Wall.width() if dist / WayKind::Wall.width() < ((1.0 - z as f32 / 12.0) * 2.0).min(1.0) {
< ((1.0 - z as f32 / 12.0) * 2.0).min(1.0)
{
vol.set( vol.set(
Vec3::new(offs.x, offs.y, surface_z + z), Vec3::new(offs.x, offs.y, surface_z + z),
Block::new(BlockKind::Normal, color), Block::new(BlockKind::Normal, color),
@ -655,13 +711,16 @@ 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;
}; };
for z in bounds.min.z.min(col.alt.floor() as i32 - 1)..bounds.max.z + 1 { for z in bounds.min.z.min(col.alt.floor() as i32 - 1)..bounds.max.z + 1
{
let rpos = Vec3::new(x, y, z); let rpos = Vec3::new(x, y, z);
let wpos = Vec3::from(self.origin) + rpos; let wpos = Vec3::from(self.origin) + rpos;
let coffs = wpos - Vec3::from(wpos2d); let coffs = wpos - Vec3::from(wpos2d);
@ -705,10 +764,12 @@ 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) => return Some(Rgb::new(150, 110, 60) Some(Plot::Town) => {
.map2(Rgb::iota(), |e: u8, i: i32| e return Some(Rgb::new(150, 110, 60).map2(Rgb::iota(), |e: u8, i: i32| {
.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)
}));
},
Some(Plot::Field { seed, .. }) => { Some(Plot::Field { seed, .. }) => {
let furrow_dirs = [ let furrow_dirs = [
Vec2::new(1, 0), Vec2::new(1, 0),
@ -741,6 +802,10 @@ pub enum Crop {
Wheat, Wheat,
Cabbage, Cabbage,
Pumpkin, Pumpkin,
Flax,
Carrot,
Tomato,
Radish,
Sunflower, Sunflower,
} }
@ -751,7 +816,11 @@ pub enum Plot {
Grass, Grass,
Water, Water,
Town, Town,
Field { farm: Id<Farm>, seed: u32, crop: Crop }, Field {
farm: Id<Farm>,
seed: u32,
crop: Crop,
},
} }
const CARDINALS: [Vec2<i32>; 4] = [ const CARDINALS: [Vec2<i32>; 4] = [
@ -855,7 +924,8 @@ impl Land {
let proj_point = line.projected_point(pos.map(|e| e as f32)); let proj_point = line.projected_point(pos.map(|e| e as f32));
let dist = proj_point.distance(pos.map(|e| e as f32)); let dist = proj_point.distance(pos.map(|e| e as f32));
if dist < way.width() { if dist < way.width() {
sample.way = sample.way sample.way = sample
.way
.filter(|(_, d, _)| *d < dist) .filter(|(_, d, _)| *d < dist)
.or(Some((way, dist, proj_point))); .or(Some((way, dist, proj_point)));
} }