Add more crops

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

View File

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

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

Binary file not shown.

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

View File

@ -139,9 +139,7 @@ impl<S: Clone + Eq + Hash> Astar<S> {
}
}
pub fn get_cheapest_cost(&self) -> Option<f32> {
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
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))
.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
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) }
{
.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,
@ -47,20 +47,22 @@ pub fn buy_units<'a>(
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);
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),
// 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),
// )
// -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| {
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() {
@ -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;
@ -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 {
@ -796,7 +898,9 @@ 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
where
K: 'a,
T: 'a,
{
Self {
entries: i.into_iter().cloned().collect(),
@ -813,19 +917,18 @@ 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(),
}
}
@ -841,11 +944,10 @@ impl<K: Copy + Eq + Hash, T: Default + Clone> MapVec<K, T> {
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,21 +595,29 @@ 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
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()) {
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_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),
@ -661,7 +666,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
max_border_river_pos
);
panic!(
"Oceans should definitely have a downhill! ...Right?"
"Oceans should definitely have a downhill! \
...Right?"
);
};
let lake_water_alt = Lerp::lerp(
@ -674,11 +680,13 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
if dist == Vec2::zero() {
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((
true,
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,
alt_for_river.max(lake_water_alt),
0.0,
@ -687,7 +695,10 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
Some((
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,
downhill_water_alt,
alt_for_river,
@ -704,8 +715,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
&& 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 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
@ -722,7 +733,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
> alt_for_river,
Some(lake_dist as f32),
alt_for_river,
(downhill_water_alt.max(river_chunk.water_alt)
(downhill_water_alt
.max(river_chunk.water_alt)
- river_gouge),
alt_for_river,
river_scale_factor as f32
@ -752,7 +764,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
return Some((
true,
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,
alt_for_river.max(lake_water_alt),
0.0,
@ -766,8 +779,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
} else {
0.0
};
let in_bounds_ =
lake_dist <= TerrainChunkSize::RECT_SIZE.x as f64 * 0.5;
let in_bounds_ = lake_dist
<= TerrainChunkSize::RECT_SIZE.x as f64 * 0.5;
if gouge_factor == 1.0 {
return Some((
true,
@ -789,7 +802,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
downhill_water_alt
} - river_gouge,
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,
))
});
(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 {
(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
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)));
.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| {
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,11 +22,8 @@ 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()
})
self.get(pos)
.map(|chunk| !chunk.near_cliffs && !chunk.river.is_river() && !chunk.river.is_lake())
.unwrap_or(false)
&& self
.get_gradient_approx(pos)
@ -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,8 +197,14 @@ 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))))
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())
@ -208,11 +215,7 @@ impl 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 {
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 {
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,8 +354,14 @@ 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))
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(
.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,11 +423,14 @@ 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) {
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) {
@ -389,18 +439,32 @@ impl Floor {
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)) => {
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)
} 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::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);
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);
}
@ -410,4 +474,3 @@ impl Floor {
}
}
}
}

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 {
.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,14 +256,17 @@ impl Archetype for House {
}
// 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
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;
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)
},
};
@ -240,7 +281,9 @@ impl Archetype for House {
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
if (roof_profile.x == 0 && mansard == 0) || roof_dist == width + 2 || is_ribbing
{
// Eaves
return Some(log);
} else {
return Some(roof);
@ -250,7 +293,8 @@ impl Archetype for House {
// Wall
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);
} else if !branch.attr.storey_fill.has_lower() && profile.y < ceil_height {
return Some(empty);
@ -269,12 +313,15 @@ impl Archetype for House {
}
};
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)),
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
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);
@ -284,7 +331,8 @@ impl Archetype for House {
}
// 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)
} else {
wall
@ -292,11 +340,14 @@ impl Archetype for House {
}
}
if dist < width { // Internals
if dist < width {
// Internals
if profile.y == ceil_height {
if profile.x == 0 {// Rafters
if profile.x == 0 {
// Rafters
return Some(log);
} else if branch.attr.storey_fill.has_upper() { // Ceiling
} else if branch.attr.storey_fill.has_upper() {
// Ceiling
return Some(floor);
}
} else if (!branch.attr.storey_fill.has_lower() && profile.y < ceil_height)
@ -313,7 +364,14 @@ impl Archetype for House {
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,7 +379,14 @@ 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) {
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 {
.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 {
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 {
}
|| 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,11 +74,8 @@ 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()
})
self.get(pos)
.map(|chunk| !chunk.near_cliffs && !chunk.river.is_river() && !chunk.river.is_lake())
.unwrap_or(false)
&& self
.get_gradient_approx(pos)
@ -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(
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,19 +574,44 @@ 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,
}
@ -562,17 +619,15 @@ impl Settlement {
}
} 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)));
}