Add more crops

This commit is contained in:
Monty Marz 2020-04-17 23:29:01 +00:00 committed by Joshua Barretto
parent 6affc046ad
commit 20050f93c9
61 changed files with 1295 additions and 747 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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(
light_map
.get(lm_idx(pos.x, pos.y, pos.z)) .get(lm_idx(pos.x, pos.y, pos.z))
.filter(|l| **l != OPAQUE && **l != UNKNOWN) .filter(|l| **l != OPAQUE && **l != UNKNOWN)
.map(|l| *l as f32 / SUNLIGHT as f32) .map(|l| *l as f32 / SUNLIGHT as f32)
.unwrap_or(0.0)) .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 {
darknesses
.get_unchecked(dark_pos.z as usize) .get_unchecked(dark_pos.z as usize)
.get_unchecked(dark_pos.y as usize) .get_unchecked(dark_pos.y as usize)
.get_unchecked(dark_pos.x 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) /
// total_value * budget).min(budget),
// site.trade_states[stock].buy_belief.price, // site.trade_states[stock].buy_belief.price,
// -site.export_targets[stock].min(0.0), // -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| {
Some(
c.downhill?
.map2(Vec2::from(TerrainChunkSize::RECT_SIZE), |e, sz: u32| {
e / (sz as i32) 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() {
@ -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,21 +595,29 @@ 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) =
max_border_river
.river_kind .river_kind
.and_then(|river_kind| { .and_then(|river_kind| {
if let RiverKind::River { cross_section } = river_kind { if let RiverKind::River { cross_section } = river_kind {
if max_border_river_dist.map(|(_, dist, _, _)| dist) != Some(Vec2::zero()) { if max_border_river_dist.map(|(_, dist, _, _)| dist)
!= Some(Vec2::zero())
{
return None; return None;
} }
let (_, _, river_width, (river_t, (river_pos, _), downhill_river_chunk)) = let (
max_border_river_dist.unwrap(); _,
_,
river_width,
(river_t, (river_pos, _), downhill_river_chunk),
) = max_border_river_dist.unwrap();
let river_alt = Lerp::lerp( let river_alt = Lerp::lerp(
river_chunk.alt.max(river_chunk.water_alt), river_chunk.alt.max(river_chunk.water_alt),
downhill_river_chunk.alt.max(downhill_river_chunk.water_alt), downhill_river_chunk.alt.max(downhill_river_chunk.water_alt),
@ -661,7 +666,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
max_border_river_pos max_border_river_pos
); );
panic!( panic!(
"Oceans should definitely have a downhill! ...Right?" "Oceans should definitely have a downhill! \
...Right?"
); );
}; };
let lake_water_alt = Lerp::lerp( let lake_water_alt = Lerp::lerp(
@ -674,11 +680,13 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
if dist == Vec2::zero() { if dist == Vec2::zero() {
let river_dist = wposf.distance(river_pos); let river_dist = wposf.distance(river_pos);
let _river_height_factor = river_dist / (river_width * 0.5); let _river_height_factor =
river_dist / (river_width * 0.5);
return Some(( return Some((
true, true,
Some((river_dist - river_width * 0.5) as f32), Some((river_dist - river_width * 0.5) as f32),
alt_for_river.min(lake_water_alt - 1.0 - river_gouge), alt_for_river
.min(lake_water_alt - 1.0 - river_gouge),
lake_water_alt - river_gouge, lake_water_alt - river_gouge,
alt_for_river.max(lake_water_alt), alt_for_river.max(lake_water_alt),
0.0, 0.0,
@ -687,7 +695,10 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
Some(( Some((
river_scale_factor <= 1.0, river_scale_factor <= 1.0,
Some((wposf.distance(river_pos) - river_width * 0.5) as f32), Some(
(wposf.distance(river_pos) - river_width * 0.5)
as f32,
),
alt_for_river, alt_for_river,
downhill_water_alt, downhill_water_alt,
alt_for_river, alt_for_river,
@ -704,8 +715,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
&& lake_id_dist.y >= -1 && lake_id_dist.y >= -1
&& lake_id_dist.x <= 1 && lake_id_dist.x <= 1
&& lake_id_dist.y <= 1; && lake_id_dist.y <= 1;
let in_bounds = let in_bounds = in_bounds
in_bounds && (lake_id_dist.x >= 0 && lake_id_dist.y >= 0); && (lake_id_dist.x >= 0 && lake_id_dist.y >= 0);
let (_, dist, _, (river_t, _, downhill_river_chunk)) = let (_, dist, _, (river_t, _, downhill_river_chunk)) =
if let Some(dist) = max_border_river_dist { if let Some(dist) = max_border_river_dist {
dist dist
@ -722,7 +733,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
> alt_for_river, > alt_for_river,
Some(lake_dist as f32), Some(lake_dist as f32),
alt_for_river, alt_for_river,
(downhill_water_alt.max(river_chunk.water_alt) (downhill_water_alt
.max(river_chunk.water_alt)
- river_gouge), - river_gouge),
alt_for_river, alt_for_river,
river_scale_factor as f32 river_scale_factor as f32
@ -752,7 +764,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
return Some(( return Some((
true, true,
Some(lake_dist as f32), Some(lake_dist as f32),
alt_for_river.min(lake_water_alt - 1.0 - river_gouge), alt_for_river
.min(lake_water_alt - 1.0 - river_gouge),
lake_water_alt - river_gouge, lake_water_alt - river_gouge,
alt_for_river.max(lake_water_alt), alt_for_river.max(lake_water_alt),
0.0, 0.0,
@ -766,8 +779,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
} else { } else {
0.0 0.0
}; };
let in_bounds_ = let in_bounds_ = lake_dist
lake_dist <= TerrainChunkSize::RECT_SIZE.x as f64 * 0.5; <= TerrainChunkSize::RECT_SIZE.x as f64 * 0.5;
if gouge_factor == 1.0 { if gouge_factor == 1.0 {
return Some(( return Some((
true, true,
@ -789,7 +802,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
downhill_water_alt downhill_water_alt
} - river_gouge, } - river_gouge,
alt_for_river, alt_for_river,
river_scale_factor as f32 * (1.0 - gouge_factor), river_scale_factor as f32
* (1.0 - gouge_factor),
)); ));
} }
} }
@ -828,9 +842,23 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
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(
BlockKind::Dense,
zcache_grid
.get(grid_border + TerrainChunkSize::RECT_SIZE.map(|e| e as i32) / 2) .get(grid_border + TerrainChunkSize::RECT_SIZE.map(|e| e as i32) / 2)
.and_then(|zcache| zcache.as_ref()) .and_then(|zcache| zcache.as_ref())
.map(|zcache| zcache.sample.stone_col) .map(|zcache| zcache.sample.stone_col)
.unwrap_or(Rgb::new(125, 120, 130))); .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(
downhill.map2(Vec2::from(TerrainChunkSize::RECT_SIZE), |e, sz: u32| {
e / (sz as i32) 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,11 +22,8 @@ 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) .unwrap_or(false)
&& self && self
.get_gradient_approx(pos) .get_gradient_approx(pos)
@ -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,8 +197,14 @@ 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>,
stair_tile: Vec2<i32>,
level: i32,
) -> (Self, Vec2<i32>) {
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) .filter(|pos| *pos != stair_tile)
.take(8) .take(8)
.max_by_key(|pos| (*pos - stair_tile).map(|e| e.abs()).sum()) .max_by_key(|pos| (*pos - stair_tile).map(|e| e.abs()).sum())
@ -208,11 +215,7 @@ impl 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>| {
if optimise_longest {
(l.distance_squared(b) as f32).sqrt() (l.distance_squared(b) as f32).sqrt()
} else { } else {
100.0 - (l.distance_squared(b) as f32).sqrt() 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,
area: Aabr<i32>,
origin: Vec3<i32>,
supplement: &mut ChunkSupplement,
) {
let align = |e: i32| {
e.div_euclid(TILE_SIZE)
+ if e.rem_euclid(TILE_SIZE) > TILE_SIZE / 2 {
1 1
} else { } else {
0 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,8 +354,14 @@ 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
let entity = EntityInfo::at(
(origin
+ Vec3::from(self.tile_offset + tile_pos) * TILE_SIZE
+ TILE_SIZE / 2)
.map(|e| e as f32),
)
.into_giant() .into_giant()
.with_alignment(Alignment::Enemy); .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
.get(*other_tile_pos)
.filter(|tile| tile.is_passable())
.is_none()
})
.map(|other_tile_pos| {
rpos.clamped(
other_tile_pos * TILE_SIZE, other_tile_pos * TILE_SIZE,
(other_tile_pos + 1) * TILE_SIZE - 1, (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,11 +423,14 @@ 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) {
@ -389,18 +439,32 @@ impl Floor {
BlockMask::nothing() BlockMask::nothing()
} }
}, },
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(), 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()
},
Some(Tile::Room(room)) => { Some(Tile::Room(room)) => {
let room = &self.rooms[*room]; let room = &self.rooms[*room];
if z == 0 && RandomField::new(room.seed).chance(Vec3::from(pos), room.loot_density) { if z == 0 && RandomField::new(room.seed).chance(Vec3::from(pos), room.loot_density)
{
BlockMask::new(Block::new(BlockKind::Chest, Rgb::white()), 1) BlockMask::new(Block::new(BlockKind::Chest, Rgb::white()), 1)
} else { } else {
empty empty
} }
}, },
Some(Tile::DownStair) => make_staircase(Vec3::new(rtile_pos.x, rtile_pos.y, z), 0.0, 0.5, 9.0).resolve_with(empty), Some(Tile::DownStair) => {
make_staircase(Vec3::new(rtile_pos.x, rtile_pos.y, z), 0.0, 0.5, 9.0)
.resolve_with(empty)
},
Some(Tile::UpStair) => { 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); 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 { if z < self.hollow_depth {
block = block.resolve_with(empty); block = block.resolve_with(empty);
} }
@ -409,5 +473,4 @@ impl Floor {
None => BlockMask::nothing(), 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() {
Some((
i as i32 * len / (branches_per_side - 1).max(1) as i32,
Branch {
len: rng.gen_range(8, 16) * flip, len: rng.gen_range(8, 16) * flip,
attr: Attr::generate(rng, locus), attr: Attr::generate(rng, locus),
locus: (6 + rng.gen_range(0, 3)).min(locus), locus: (6 + rng.gen_range(0, 3)).min(locus),
border: 4, border: 4,
children: Vec::new(), children: Vec::new(),
})) },
))
} else { } else {
None 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,14 +256,17 @@ 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 =
|profile: Vec2<i32>, width, dist, bound_offset: Vec2<i32>, roof_top, mansard| {
// Roof // 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)
- 0.5)
.ceil() as i32;
(Vec2::new(circular_dist, profile.y), circular_dist) (Vec2::new(circular_dist, profile.y), circular_dist)
}, },
}; };
@ -240,7 +281,9 @@ impl Archetype for House {
if profile.y == roof_level && roof_dist <= width + 2 { if profile.y == roof_level && roof_dist <= width + 2 {
let is_ribbing = ((profile.y - ceil_height) % 3 == 0 && self.roof_ribbing) let is_ribbing = ((profile.y - ceil_height) % 3 == 0 && self.roof_ribbing)
|| (bound_offset.x == bound_offset.y && self.roof_ribbing_diagonal); || (bound_offset.x == bound_offset.y && self.roof_ribbing_diagonal);
if (roof_profile.x == 0 && mansard == 0) || roof_dist == width + 2 || is_ribbing { // Eaves if (roof_profile.x == 0 && mansard == 0) || roof_dist == width + 2 || is_ribbing
{
// Eaves
return Some(log); return Some(log);
} else { } else {
return Some(roof); return Some(roof);
@ -250,7 +293,8 @@ impl Archetype for House {
// Wall // Wall
if dist == width && profile.y < roof_level { if dist == width && profile.y < roof_level {
if bound_offset.x == bound_offset.y || profile.y == ceil_height { // Support beams if bound_offset.x == bound_offset.y || profile.y == ceil_height {
// Support beams
return Some(log); return Some(log);
} else if !branch.attr.storey_fill.has_lower() && profile.y < ceil_height { } else if !branch.attr.storey_fill.has_lower() && profile.y < ceil_height {
return Some(empty); return Some(empty);
@ -269,12 +313,15 @@ impl Archetype for House {
} }
}; };
let window_bounds = Aabr { let window_bounds = Aabr {
min: (frame_bounds.min + 1).map2(frame_bounds.center(), |a, b| a.min(b)), min: (frame_bounds.min + 1)
max: (frame_bounds.max - 1).map2(frame_bounds.center(), |a, b| a.max(b)), .map2(frame_bounds.center(), |a, b| a.min(b)),
max: (frame_bounds.max - 1)
.map2(frame_bounds.center(), |a, b| a.max(b)),
}; };
// Window // Window
if (frame_bounds.size() + 1).reduce_min() > 2 { // Window frame is large enough for a 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); let surface_pos = Vec2::new(bound_offset.x, profile.y);
if window_bounds.contains_point(surface_pos) { if window_bounds.contains_point(surface_pos) {
return Some(internal); return Some(internal);
@ -284,7 +331,8 @@ impl Archetype for House {
} }
// Wall // Wall
return Some(if branch.attr.central_supports && profile.x == 0 { // Support beams return Some(if branch.attr.central_supports && profile.x == 0 {
// Support beams
log.with_priority(structural_layer) log.with_priority(structural_layer)
} else { } else {
wall wall
@ -292,11 +340,14 @@ impl Archetype for House {
} }
} }
if dist < width { // Internals if dist < width {
// Internals
if profile.y == ceil_height { if profile.y == ceil_height {
if profile.x == 0 {// Rafters if profile.x == 0 {
// Rafters
return Some(log); return Some(log);
} else if branch.attr.storey_fill.has_upper() { // Ceiling } else if branch.attr.storey_fill.has_upper() {
// Ceiling
return Some(floor); return Some(floor);
} }
} else if (!branch.attr.storey_fill.has_lower() && profile.y < ceil_height) } else if (!branch.attr.storey_fill.has_lower() && profile.y < ceil_height)
@ -313,7 +364,14 @@ impl Archetype for House {
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,7 +379,14 @@ 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(
profile,
4,
dist,
center_offset.map(|e| e.abs()),
tower_top,
branch.attr.mansard,
) {
cblock = cblock.resolve_with(block); 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(|_| {
(
rng.gen_range(-5, len + 5).clamped(0, len.max(1) - 1),
Branch {
len: rng.gen_range(5, 12) * if rng.gen() { 1 } else { -1 }, len: rng.gen_range(5, 12) * if rng.gen() { 1 } else { -1 },
attr: Self::Attr::default(), attr: Self::Attr::default(),
locus: 5 + rng.gen_range(0, 3), locus: 5 + rng.gen_range(0, 3),
border: 3, border: 3,
children: Vec::new(), 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
|| match ori {
Ori::East => (pos.x - node.x) * branch.len.signum() >= 0, Ori::East => (pos.x - node.x) * branch.len.signum() >= 0,
Ori::North => (pos.y - node.y) * branch.len.signum() >= 0, Ori::North => (pos.y - node.y) * branch.len.signum() >= 0,
} || true { }
|| 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,11 +74,8 @@ 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) .unwrap_or(false)
&& self && self
.get_gradient_approx(pos) .get_gradient_approx(pos)
@ -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(
ctx.rng,
Vec3::new(
house_pos.x, 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))
.unwrap_or(0.0) .unwrap_or(0.0)
.ceil() as i32, .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,19 +574,44 @@ 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,
} }
@ -562,17 +619,15 @@ impl Settlement {
} }
} 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)));
} }