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
debug-assertions = true
panic = "abort"
debug = true
debug = false
codegen-units = 8
lto = false
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> {
self.cheapest_cost
}
pub fn get_cheapest_cost(&self) -> Option<f32> { self.cheapest_cost }
fn reconstruct_path_to(&mut self, end: S) -> Path<S> {
let mut path = vec![end.clone()];

View File

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

View File

@ -28,13 +28,13 @@ pub mod region;
pub mod spiral;
pub mod state;
pub mod states;
pub mod store;
pub mod sync;
pub mod sys;
pub mod terrain;
pub mod util;
pub mod vol;
pub mod volumes;
pub mod store;
/// The networking module containing high-level wrappers of `TcpListener` and
/// `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> {
fn hash<H: hash::Hasher>(&self, h: &mut H) {
self.0.hash(h);
}
fn hash<H: hash::Hasher>(&self, h: &mut H) { self.0.hash(h); }
}
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_ids(&self) -> impl Iterator<Item = (Id<T>, &T)> {
self
.items
self.items
.iter()
.enumerate()
.map(|(i, item)| (Id(i, PhantomData), item))

View File

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

View File

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

View File

@ -88,7 +88,9 @@ impl Meshable<SpritePipeline, SpritePipeline> for Segment {
SpriteVertex::new(
origin,
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
} else {
let pos = wpos - outer.min;
Some(light_map
.get(lm_idx(pos.x, pos.y, pos.z))
.filter(|l| **l != OPAQUE && **l != UNKNOWN)
.map(|l| *l as f32 / SUNLIGHT as f32)
.unwrap_or(0.0))
Some(
light_map
.get(lm_idx(pos.x, pos.y, pos.z))
.filter(|l| **l != OPAQUE && **l != UNKNOWN)
.map(|l| *l as f32 / SUNLIGHT as f32)
.unwrap_or(0.0),
)
}
}
}

View File

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

View File

@ -240,7 +240,10 @@ impl Scene {
);
// 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.
self.camera.compute_dependents(&*scene_data.state.terrain());

View File

@ -190,6 +190,22 @@ fn sprite_config_for(kind: BlockKind) -> Option<SpriteConfig> {
variations: 3,
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 {
variations: 1,
wind_sway: 0.0,
@ -1301,6 +1317,147 @@ impl<V: RectRasterableVol> Terrain<V> {
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
(
(BlockKind::Coconut, 0),

View File

@ -150,10 +150,14 @@ fn main() {
}
if win.get_mouse_down(minifb::MouseButton::Left) {
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);
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) {
//println!("Chunk info: {:#?}", chunk);
if let Some(id) = &chunk.place {

View File

@ -66,7 +66,11 @@ impl<'a> BlockGen<'a> {
// Conservative range of radius: [8, 47]
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(
if cliff_pos.map(|e| e as f32).distance_squared(wpos)
< (radius as f32 + tolerance).powf(2.0)

View File

@ -1,5 +1,5 @@
use rand::prelude::*;
use super::GenCtx;
use rand::prelude::*;
pub struct SellOrder {
pub quantity: f32,
@ -42,25 +42,27 @@ impl Belief {
pub fn buy_units<'a>(
ctx: &mut GenCtx<impl Rng>,
sellers: impl Iterator<Item=&'a mut SellOrder>,
sellers: impl Iterator<Item = &'a mut SellOrder>,
max_quantity: f32,
max_price: f32,
max_spend: f32,
) -> (f32, f32) {
let mut sell_orders = sellers
.filter(|so| so.quantity > 0.0)
.collect::<Vec<_>>();
let mut sell_orders = sellers.filter(|so| so.quantity > 0.0).collect::<Vec<_>>();
// 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 spent = 0.0;
for order in sell_orders {
if
quantity >= max_quantity || // We've purchased enough
if quantity >= max_quantity || // We've purchased 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;
} else {

View File

@ -1,27 +1,23 @@
mod econ;
use std::{
ops::Range,
hash::Hash,
fmt,
use crate::{
sim::{SimChunk, WorldSim},
site::{Dungeon, Settlement, Site as WorldSite},
util::{attempt, seed_expan},
};
use hashbrown::{HashMap, HashSet};
use vek::*;
use rand::prelude::*;
use rand_chacha::ChaChaRng;
use common::{
astar::Astar,
path::Path,
spiral::Spiral2d,
store::{Id, Store},
terrain::TerrainChunkSize,
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] = [
Vec2::new(1, 0),
@ -107,13 +103,15 @@ impl Civs {
// Temporary!
for track in this.tracks.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
for site in this.sites.iter() {
if let SiteKind::Settlement = &site.kind {} else {
if let SiteKind::Settlement = &site.kind {
} else {
continue;
}
@ -124,9 +122,16 @@ impl Civs {
let flatten_radius = 10.0;
if let Some(center_alt) = ctx.sim.get_alt_approx(wpos) {
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 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
.get_mut(pos)
// 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 world_site = match &site.kind {
SiteKind::Settlement => WorldSite::from(Settlement::generate(wpos, Some(ctx.sim), ctx.rng)),
SiteKind::Dungeon => WorldSite::from(Dungeon::generate(wpos, Some(ctx.sim), ctx.rng)),
SiteKind::Settlement => {
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;
for pos in Spiral2d::new().map(|offs| site.center + offs).take((radius_chunks * 2).pow(2)) {
let radius_chunks =
(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
.get_mut(pos)
.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 sites(&self) -> impl Iterator<Item=&Site> + '_ {
self.sites.iter()
}
pub fn sites(&self) -> impl Iterator<Item = &Site> + '_ { self.sites.iter() }
fn display_info(&self) {
for (id, civ) in self.civs.iter_ids() {
@ -189,24 +200,39 @@ impl Civs {
self.track_map
.get(&a)
.and_then(|dests| dests.get(&b))
.or_else(|| self.track_map
.get(&b)
.and_then(|dests| dests.get(&a)))
.or_else(|| self.track_map.get(&b).and_then(|dests| dests.get(&a)))
.copied()
}
/// Return an iterator over a site's neighbors
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 fro = self.track_map.iter().filter(move |(_, dests)| dests.contains_key(&site)).map(|(p, _)| p);
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 fro = self
.track_map
.iter()
.filter(move |(_, dests)| dests.contains_key(&site))
.map(|(p, _)| p);
to.chain(fro).filter(move |p| **p != site).copied()
}
/// Find the cheapest route between two places
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 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 mut astar = Astar::new(100, a, heuristic);
astar
@ -248,7 +274,12 @@ impl Civs {
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 alive = HashSet::new();
alive.insert(loc);
@ -258,7 +289,13 @@ impl Civs {
for dir in CARDINALS.iter() {
if site_in_dir(&ctx.sim, 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);
}
}
@ -291,7 +328,12 @@ impl Civs {
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;
let place = match ctx.sim.get(loc).and_then(|site| site.place) {
@ -303,7 +345,8 @@ impl Civs {
// Find neighbors
const MAX_NEIGHBOR_DISTANCE: f32 = 250.0;
let mut nearby = self.sites
let mut nearby = self
.sites
.iter_ids()
.map(|(id, p)| (id, (p.center.distance_squared(loc) as f32).sqrt()))
.filter(|(p, dist)| *dist < MAX_NEIGHBOR_DISTANCE)
@ -320,10 +363,7 @@ impl Civs {
.filter(|(_, route_cost)| *route_cost < cost * 3.0)
.is_none()
{
let track = self.tracks.insert(Track {
cost,
path,
});
let track = self.tracks.insert(Track { cost, path });
self.track_map
.entry(site)
.or_default()
@ -342,15 +382,17 @@ impl Civs {
// Trade stocks
// let mut stocks = TRADE_STOCKS;
// stocks.shuffle(ctx.rng); // Give each stock a chance to be traded first
// for stock in stocks.iter().copied() {
// stocks.shuffle(ctx.rng); // Give each stock a chance to be traded
// first for stock in stocks.iter().copied() {
// let mut sell_orders = self.sites
// .iter_ids()
// .map(|(id, site)| (id, {
// econ::SellOrder {
// quantity: site.export_targets[stock].max(0.0).min(site.stocks[stock]),
// price: site.trade_states[stock].sell_belief.choose_price(ctx) * 1.25, // Trade cost
// q_sold: 0.0,
// quantity:
// site.export_targets[stock].max(0.0).min(site.stocks[stock]),
// price:
// site.trade_states[stock].sell_belief.choose_price(ctx) * 1.25, //
// Trade cost q_sold: 0.0,
// }
// }))
// .filter(|(_, order)| order.quantity > 0.0)
@ -364,17 +406,17 @@ impl Civs {
// let (max_spend, max_price, max_import) = {
// let site = self.sites.get(site);
// let budget = site.coin * 0.5;
// 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),
// site.trade_states[stock].buy_belief.price,
// -site.export_targets[stock].min(0.0),
// )
// 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),
// site.trade_states[stock].buy_belief.price,
// -site.export_targets[stock].min(0.0), )
// };
// let (quantity, spent) = econ::buy_units(ctx, sell_orders
// .iter_mut()
// .filter(|(id, _)| site != *id && self.track_between(site, *id).is_some())
// .map(|(_, order)| order),
// .filter(|(id, _)| site != *id && self.track_between(site,
// *id).is_some()) .map(|(_, order)| order),
// max_import,
// 1000000.0, // Max price TODO
// max_spend,
@ -384,9 +426,9 @@ impl Civs {
// if quantity > 0.0 {
// site.stocks[stock] += quantity;
// site.last_exports[stock] = -quantity;
// site.trade_states[stock].buy_belief.update_buyer(years, spent / quantity);
// println!("Belief: {:?}", site.trade_states[stock].buy_belief);
// }
// site.trade_states[stock].buy_belief.update_buyer(years,
// spent / quantity); println!("Belief: {:?}",
// site.trade_states[stock].buy_belief); }
// }
// for (site, order) in sell_orders {
@ -395,22 +437,31 @@ impl Civs {
// if order.q_sold > 0.0 {
// site.stocks[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
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 heuristic = move |l: &Vec2<i32>| (l.distance_squared(b) as f32).sqrt();
let neighbors = |l: &Vec2<i32>| {
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 mut astar = Astar::new(20000, a, heuristic);
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)))
}
/// 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> {
if loc_suitable_for_walking(sim, a) &&
loc_suitable_for_walking(sim, a + dir)
{
if loc_suitable_for_walking(sim, a) && loc_suitable_for_walking(sim, a + dir) {
let a_alt = sim.get(a)?.alt;
let b_alt = sim.get(a + dir)?.alt;
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 {
loc_suitable_for_site(sim, a) &&
loc_suitable_for_site(sim, a + dir)
loc_suitable_for_site(sim, a) && 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 {
if let Some(chunk) = sim.get(loc) {
!chunk.river.is_ocean() &&
!chunk.river.is_lake() &&
sim.get_gradient_approx(loc).map(|grad| grad < 1.0).unwrap_or(false)
!chunk.river.is_ocean()
&& !chunk.river.is_lake()
&& sim
.get_gradient_approx(loc)
.map(|grad| grad < 1.0)
.unwrap_or(false)
} else {
false
}
@ -464,10 +518,15 @@ fn find_site_loc(ctx: &mut GenCtx<impl Rng>, near: Option<(Vec2<i32>, f32)>) ->
let mut loc = None;
for _ in 0..MAX_ATTEMPTS {
let test_loc = loc.unwrap_or_else(|| match near {
Some((origin, dist)) => origin + (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),
Some((origin, dist)) => {
origin
+ (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)
},
None => Vec2::new(
ctx.rng.gen_range(0, ctx.sim.get_size().x 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);
}
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)
})));
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)
}),
)
});
}
None
}
@ -508,23 +572,36 @@ pub struct NaturalResources {
impl NaturalResources {
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.rock += chunk.rockiness;
self.river += if chunk.river.is_river() { 5.0 } else { 0.0 };
self.farmland += if
chunk.humidity > 0.35 &&
chunk.temp > -0.3 && chunk.temp < 0.75 &&
chunk.chaos < 0.5 &&
ctx.sim.get_gradient_approx(loc).map(|grad| grad < 0.7).unwrap_or(false)
{ 1.0 } else { 0.0 };
self.farmland += if chunk.humidity > 0.35
&& chunk.temp > -0.3
&& chunk.temp < 0.75
&& chunk.chaos < 0.5
&& ctx
.sim
.get_gradient_approx(loc)
.map(|grad| grad < 0.7)
.unwrap_or(false)
{
1.0
} else {
0.0
};
}
}
pub struct Track {
/// Cost of using this track relative to other paths. This cost is an arbitrary unit and
/// doesn't make sense unless compared to other track costs.
/// Cost of using this track relative to other paths. This cost is an
/// arbitrary unit and doesn't make sense unless compared to other track
/// costs.
cost: f32,
path: Path<Vec2<i32>>,
}
@ -570,7 +647,14 @@ impl fmt::Display for Site {
}
writeln!(f, "Values")?;
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")?;
for (labor, n) in self.labors.iter() {
@ -619,8 +703,8 @@ impl Site {
(Some(HUNTER), vec![(GAME, 4.0)]),
(Some(FARMER), vec![(WHEAT, 4.0)]),
]
.into_iter()
.collect::<HashMap<_, Vec<(Stock, f32)>>>();
.into_iter()
.collect::<HashMap<_, Vec<(Stock, f32)>>>();
// Per labourer, per year
let production = Stocks::from_list(&[
@ -634,7 +718,11 @@ impl Site {
let mut demand = Stocks::from_default(0.0);
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 {
demand[*stock] += *amount * scale;
}
@ -647,20 +735,27 @@ impl Site {
let last_exports = &self.last_exports;
let stocks = &self.stocks;
self.surplus = demand.clone().map(|stock, tgt| {
supply[stock] + stocks[stock] - demand[stock] - last_exports[stock]
});
self.surplus = demand
.clone()
.map(|stock, tgt| supply[stock] + stocks[stock] - demand[stock] - last_exports[stock]);
// Update values according to the surplus of each stock
let values = &mut self.values;
self.surplus.iter().for_each(|(stock, surplus)| {
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
let value_avg =
values.iter().map(|(_, v)| (*v).unwrap_or(0.0)).sum::<f32>().max(0.01)
let value_avg = values
.iter()
.map(|(_, v)| (*v).unwrap_or(0.0))
.sum::<f32>()
.max(0.01)
/ values.iter().filter(|(_, v)| v.is_some()).count() as f32;
let export_targets = &mut self.export_targets;
let last_exports = &self.last_exports;
@ -668,7 +763,7 @@ impl Site {
self.values.iter().for_each(|(stock, value)| {
let rvalue = (*value).map(|v| v - value_avg).unwrap_or(0.0);
//let factor = if export_targets[stock] > 0.0 { 1.0 / rvalue } else { rvalue };
export_targets[stock] = last_exports[stock] - rvalue * 0.1;// + (trade_states[stock].sell_belief.price - trade_states[stock].buy_belief.price) * 0.025;
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;
@ -680,17 +775,24 @@ impl Site {
let labor_ratio_sum = labor_ratios.iter().map(|(_, r)| *r).sum::<f32>().max(0.01);
production.iter().for_each(|(labor, _)| {
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
let stocks_before = self.stocks.clone();
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
// we can produce! For example, if we need 0.25 fish and 0.75 oats to make 1 unit of
// food, but only 0.5 units of oats are available then we only need to consume 2/3rds
// For each order, we try to find the minimum satisfaction rate - this limits
// how much we can produce! For example, if we need 0.25 fish and
// 0.75 oats to make 1 unit of food, but only 0.5 units of oats are
// available then we only need to consume 2/3rds
// of other ingredients and leave the rest in stock
// In effect, this is the productivity
let productivity = orders
@ -703,7 +805,9 @@ impl Site {
satisfaction
})
.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 {
// What quantity is this order requesting?
@ -733,7 +837,11 @@ impl Site {
// Births/deaths
const NATURAL_BIRTH_RATE: f32 = 0.15;
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);
}
}
@ -757,13 +865,7 @@ const LOGS: Stock = "logs";
const WOOD: Stock = "wood";
const ROCK: Stock = "rock";
const STONE: Stock = "stone";
const TRADE_STOCKS: [Stock; 5] = [
FLOUR,
MEAT,
FOOD,
WOOD,
STONE,
];
const TRADE_STOCKS: [Stock; 5] = [FLOUR, MEAT, FOOD, WOOD, STONE];
#[derive(Debug, Clone)]
struct TradeState {
@ -795,8 +897,10 @@ pub struct 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
where K: 'a, T: 'a
pub fn from_list<'a>(i: impl IntoIterator<Item = &'a (K, T)>) -> Self
where
K: 'a,
T: 'a,
{
Self {
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 {
let default = &self.default;
self
.entries
.entry(entry)
.or_insert_with(|| default.clone())
self.entries.entry(entry).or_insert_with(|| default.clone())
}
pub fn get(&self, entry: K) -> &T {
self.entries.get(&entry).unwrap_or(&self.default)
}
pub fn get(&self, entry: K) -> &T { 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> {
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(),
}
}
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))
}
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))
}
}
impl<K: Copy + Eq + Hash, T: Default + Clone> std::ops::Index<K> for MapVec<K, T> {
type Output = T;
fn index(&self, entry: K) -> &Self::Output { self.get(entry) }
}
impl<K: Copy + Eq + Hash, T: Default + Clone> std::ops::IndexMut<K> for MapVec<K, T> {
fn index_mut(&mut self, entry: K) -> &mut Self::Output { self.get_mut(entry) }
}

View File

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

View File

@ -4,12 +4,12 @@
mod all;
mod block;
pub mod civ;
mod column;
pub mod config;
pub mod sim;
pub mod site;
pub mod util;
pub mod civ;
// Reexports
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 grid_border = 4;
let zcache_grid =
Grid::populate_from(TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + grid_border * 2, |offs| {
sampler.get_z_cache(chunk_wpos2d - grid_border + offs)
});
let zcache_grid = Grid::populate_from(
TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + grid_border * 2,
|offs| sampler.get_z_cache(chunk_wpos2d - grid_border + offs),
);
let air = Block::empty();
let stone = Block::new(BlockKind::Dense, zcache_grid
.get(grid_border + TerrainChunkSize::RECT_SIZE.map(|e| e as i32) / 2)
.and_then(|zcache| zcache.as_ref())
.map(|zcache| zcache.sample.stone_col)
.unwrap_or(Rgb::new(125, 120, 130)));
let stone = Block::new(
BlockKind::Dense,
zcache_grid
.get(grid_border + TerrainChunkSize::RECT_SIZE.map(|e| e as i32) / 2)
.and_then(|zcache| zcache.as_ref())
.map(|zcache| zcache.sample.stone_col)
.unwrap_or(Rgb::new(125, 120, 130)),
);
let water = Block::new(BlockKind::Water, Rgb::new(60, 90, 190));
let _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_water(&self) -> bool {
self.near_river() || self.is_lake() || self.is_ocean()
}
pub fn near_water(&self) -> bool { self.near_river() || self.is_lake() || self.is_ocean() }
}
/// Draw rivers and assign them heights, widths, and velocities. Take some

View File

@ -24,17 +24,17 @@ pub use self::{
use crate::{
all::ForestKind,
block::BlockGen,
civ::Place,
column::ColumnGen,
site::{Settlement, Site},
civ::Place,
util::{seed_expan, FastNoise, RandomField, Sampler, StructureGen2d},
CONFIG,
};
use common::{
assets,
store::Id,
terrain::{BiomeKind, TerrainChunkSize},
vol::RectVolSize,
store::Id,
};
use hashbrown::HashMap;
use noise::{
@ -1305,9 +1305,7 @@ impl WorldSim {
this
}
pub fn get_size(&self) -> Vec2<u32> {
WORLD_SIZE.map(|e| e as u32)
}
pub fn get_size(&self) -> Vec2<u32> { WORLD_SIZE.map(|e| e as u32) }
/// Draw a map of the world based on chunk information. Returns a buffer of
/// u32s.
@ -1558,9 +1556,11 @@ impl WorldSim {
pub fn get_gradient_approx(&self, chunk_pos: Vec2<i32>) -> Option<f32> {
let a = self.get(chunk_pos)?;
if let Some(downhill) = a.downhill {
let b = self.get(downhill.map2(Vec2::from(TerrainChunkSize::RECT_SIZE), |e, sz: u32| {
e / (sz as i32)
}))?;
let b = self.get(
downhill.map2(Vec2::from(TerrainChunkSize::RECT_SIZE), |e, sz: u32| {
e / (sz as i32)
}),
)?;
Some((a.alt - b.alt).abs() / TerrainChunkSize::RECT_SIZE.x as f32)
} else {
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
// 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 }

View File

@ -1,19 +1,19 @@
use super::SpawnRules;
use crate::{
column::ColumnSample,
sim::{SimChunk, WorldSim},
util::{attempt, Grid, RandomField, Sampler, StructureGen2d},
site::BlockMask,
util::{attempt, Grid, RandomField, Sampler, StructureGen2d},
};
use super::SpawnRules;
use common::{
astar::Astar,
comp::Alignment,
generation::{ChunkSupplement, EntityInfo, EntityKind},
path::Path,
spiral::Spiral2d,
terrain::{Block, BlockKind, TerrainChunkSize},
vol::{BaseVol, RectSizedVol, RectVolSize, ReadVol, WriteVol, Vox},
store::{Id, Store},
generation::{ChunkSupplement, EntityInfo},
comp::{Alignment},
terrain::{Block, BlockKind, TerrainChunkSize},
vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol},
};
use hashbrown::{HashMap, HashSet};
use rand::prelude::*;
@ -22,16 +22,13 @@ use vek::*;
impl WorldSim {
fn can_host_dungeon(&self, pos: Vec2<i32>) -> bool {
self
.get(pos)
.map(|chunk| {
!chunk.near_cliffs && !chunk.river.is_river() && !chunk.river.is_lake()
})
.unwrap_or(false)
&& self
.get_gradient_approx(pos)
.map(|grad| grad > 0.25 && grad < 1.5)
self.get(pos)
.map(|chunk| !chunk.near_cliffs && !chunk.river.is_river() && !chunk.river.is_lake())
.unwrap_or(false)
&& self
.get_gradient_approx(pos)
.map(|grad| grad > 0.25 && grad < 1.5)
.unwrap_or(false)
}
}
@ -52,7 +49,11 @@ impl Dungeon {
let mut ctx = GenCtx { sim, rng };
let mut this = Self {
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()),
floors: (0..6)
.scan(Vec2::zero(), |stair_tile, level| {
@ -196,23 +197,25 @@ pub struct Floor {
const FLOOR_SIZE: Vec2<i32> = Vec2::new(18, 18);
impl Floor {
pub fn generate(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)
.take(8)
.max_by_key(|pos| (*pos - stair_tile).map(|e| e.abs()).sum())
.unwrap();
pub fn generate(
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)
.take(8)
.max_by_key(|pos| (*pos - stair_tile).map(|e| e.abs()).sum())
.unwrap();
let tile_offset = -FLOOR_SIZE / 2;
let mut this = Floor {
tile_offset,
tiles: Grid::new(FLOOR_SIZE, Tile::Solid),
rooms: Store::default(),
solid_depth: if level == 0 {
80
} else {
13 * 2
},
solid_depth: if level == 0 { 80 } else { 13 * 2 },
hollow_depth: 13,
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, 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(new_stair_tile - tile_offset, Tile::DownStair);
this.tiles
.set(new_stair_tile - tile_offset, Tile::DownStair);
(this, new_stair_tile)
}
@ -238,15 +247,16 @@ impl Floor {
for _ in 0..n {
let area = match attempt(30, || {
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_border = Rect::from((pos - 1, Extent2::from(sz) + 2)); // The room, but with some personal space
// Ensure no overlap
if self.rooms
.iter()
.any(|r| r.area.collides_with_rect(area_border) || r.area.contains_point(self.stair_tile))
{
if self.rooms.iter().any(|r| {
r.area.collides_with_rect(area_border) || r.area.contains_point(self.stair_tile)
}) {
return None;
}
@ -265,18 +275,27 @@ impl Floor {
for x in 0..area.extent().w {
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 heuristic = move |l: &Vec2<i32>| if optimise_longest {
(l.distance_squared(b) as f32).sqrt()
} else {
100.0 - (l.distance_squared(b) as f32).sqrt()
let heuristic = move |l: &Vec2<i32>| {
if optimise_longest {
(l.distance_squared(b) as f32).sqrt()
} else {
100.0 - (l.distance_squared(b) as f32).sqrt()
}
};
let neighbors = |l: &Vec2<i32>| {
let l = *l;
@ -294,7 +313,13 @@ impl Floor {
let satisfied = |l: &Vec2<i32>| *l == b;
let mut astar = Astar::new(20000, a, heuristic);
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()
.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) {
let align = |e: i32| e.div_euclid(TILE_SIZE) + if e.rem_euclid(TILE_SIZE) > TILE_SIZE / 2 {
1
} else {
0
pub fn apply_supplement(
&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
} else {
0
}
};
let aligned_area = Aabr {
min: area.min.map(align) + self.tile_offset,
@ -321,10 +354,16 @@ impl Floor {
let tile_pos = Vec2::new(x, y);
if let Some(Tile::Room(room)) = self.tiles.get(tile_pos) {
let room = &self.rooms[*room];
if room.enemies && tile_pos.x % 4 == 0 && tile_pos.y % 4 == 0 { // 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()
.with_alignment(Alignment::Enemy);
if room.enemies && tile_pos.x % 4 == 0 && tile_pos.y % 4 == 0 {
// 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()
.with_alignment(Alignment::Enemy);
supplement.add_entity(entity);
}
}
@ -332,22 +371,26 @@ impl Floor {
}
}
pub fn total_depth(&self) -> i32 {
self.solid_depth + self.hollow_depth
}
pub fn total_depth(&self) -> i32 { self.solid_depth + self.hollow_depth }
pub fn nearest_wall(&self, rpos: Vec2<i32>) -> Option<Vec2<i32>> {
let tile_pos = rpos.map(|e| e.div_euclid(TILE_SIZE));
let tile_center = tile_pos * TILE_SIZE + TILE_SIZE / 2;
DIRS
.iter()
DIRS.iter()
.map(|dir| tile_pos + *dir)
.filter(|other_tile_pos| 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 + 1) * TILE_SIZE - 1,
))
.filter(|other_tile_pos| {
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 + 1) * TILE_SIZE - 1,
)
})
.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) {
stone
} 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
} else {
empty
@ -376,38 +423,54 @@ impl Floor {
};
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 tunnel_dist = 1.0 - (dist_to_wall.powf(2.0) - wall_thickness).max(0.0).sqrt() / TILE_SIZE as f32;
let dist_to_wall = self
.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| {
match self.tiles.get(tile_pos) {
Some(Tile::Solid) => BlockMask::nothing(),
Some(Tile::Tunnel) => {
if (z as f32) < 8.0 - 8.0 * tunnel_dist.powf(4.0) {
empty
} else {
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)) => {
let room = &self.rooms[*room];
if z == 0 && RandomField::new(room.seed).chance(Vec3::from(pos), room.loot_density) {
BlockMask::new(Block::new(BlockKind::Chest, Rgb::white()), 1)
} else {
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) => {
let mut block = make_staircase(Vec3::new(rtile_pos.x, rtile_pos.y, z), TILE_SIZE as f32 / 2.0, 0.5, 9.0);
if z < self.hollow_depth {
block = block.resolve_with(empty);
}
block
},
None => BlockMask::nothing(),
}
move |z| match self.tiles.get(tile_pos) {
Some(Tile::Solid) => BlockMask::nothing(),
Some(Tile::Tunnel) => {
if (z as f32) < 8.0 - 8.0 * tunnel_dist.powf(4.0) {
empty
} else {
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)) => {
let room = &self.rooms[*room];
if z == 0 && RandomField::new(room.seed).chance(Vec3::from(pos), room.loot_density)
{
BlockMask::new(Block::new(BlockKind::Chest, Rgb::white()), 1)
} else {
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) => {
let mut block = make_staircase(
Vec3::new(rtile_pos.x, rtile_pos.y, z),
TILE_SIZE as f32 / 2.0,
0.5,
9.0,
);
if z < self.hollow_depth {
block = block.resolve_with(empty);
}
block
},
None => BlockMask::nothing(),
}
}
}

View File

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

View File

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

View File

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

View File

@ -1,15 +1,17 @@
pub mod house;
pub mod keep;
use vek::*;
use rand::prelude::*;
use super::skeleton::*;
use crate::site::BlockMask;
use rand::prelude::*;
use vek::*;
pub trait Archetype {
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(
&self,
dist: i32,

View File

@ -1,13 +1,13 @@
mod skeleton;
mod archetype;
mod skeleton;
// Reexports
pub use self::archetype::Archetype;
use vek::*;
use rand::prelude::*;
use self::skeleton::*;
use common::terrain::Block;
use rand::prelude::*;
use vek::*;
pub type HouseBuilding = Building<archetype::house::House>;
@ -19,7 +19,8 @@ pub struct Building<A: Archetype> {
impl<A: Archetype> Building<A> {
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 (archetype, skel) = A::generate(rng);
@ -48,8 +49,11 @@ impl<A: Archetype> Building<A> {
pub fn sample(&self, pos: Vec3<i32>) -> Option<Block> {
let rpos = pos - self.origin;
self.skel.sample_closest(rpos.into(), |dist, bound_offset, center_offset, branch| {
self.archetype.draw(dist, bound_offset, center_offset, rpos.z, branch)
}).finish()
self.skel
.sample_closest(rpos.into(), |dist, bound_offset, center_offset, branch| {
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 vek::*;
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum Ori {
@ -32,7 +32,14 @@ pub struct 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);
for (offset, child) in &self.children {
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> {
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> {
@ -64,24 +72,35 @@ impl<T> Skeleton<T> {
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)>;
self.for_each(|node, ori, branch, is_child, parent_locus| {
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 bounds = Aabr::new_empty(node)
.expanded_to_contain_point(node2);
let node = node
+ if is_child {
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 {
Vec2::new(
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 {
Vec2::new(
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 {
Vec2::new(pos.y - bounds.center().y, pos.x - bounds.center().x)
} else {
@ -89,10 +108,13 @@ impl<T> Skeleton<T> {
};
let dist = bound_offset.reduce_max();
let dist_locus = dist - branch.locus;
if !is_child || match ori {
Ori::East => (pos.x - node.x) * branch.len.signum() >= 0,
Ori::North => (pos.y - node.y) * branch.len.signum() >= 0,
} || true {
if !is_child
|| match ori {
Ori::East => (pos.x - node.x) * branch.len.signum() >= 0,
Ori::North => (pos.y - node.y) * branch.len.signum() >= 0,
}
|| true
{
let new_bm = f(dist, bound_offset, center_offset, branch);
min = min
.map(|(_, bm)| (dist_locus, bm.resolve_with(new_bm)))

View File

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