mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Add more crops
This commit is contained in:
parent
7d4443cbcd
commit
0fb3a115da
@ -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
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
BIN
assets/voxygen/background/bg_11.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/sprite/cabbage/cabbage-0.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/sprite/cabbage/cabbage-0.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/sprite/cabbage/cabbage-1.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/sprite/cabbage/cabbage-1.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/sprite/cabbage/cabbage-2.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/sprite/cabbage/cabbage-2.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/sprite/carrot/0.vox
(Stored with Git LFS)
Normal file
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
BIN
assets/voxygen/voxel/sprite/turnip/turnip-5.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -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()];
|
||||
|
@ -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); }
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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),
|
||||
),
|
||||
)
|
||||
},
|
||||
&{
|
||||
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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),
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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) }
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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 }
|
||||
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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)))
|
||||
|
@ -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)));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user