diff --git a/common/benches/chonk_benchmark.rs b/common/benches/chonk_benchmark.rs index ae2d8a5a01..173988cdd8 100644 --- a/common/benches/chonk_benchmark.rs +++ b/common/benches/chonk_benchmark.rs @@ -4,7 +4,7 @@ use vek::*; use veloren_common::{ terrain::{ block::{Block, BlockKind}, - TerrainChunk, TerrainChunkMeta, + SpriteKind, TerrainChunk, TerrainChunkMeta, }, vol::*, }; @@ -17,7 +17,7 @@ fn criterion_benchmark(c: &mut Criterion) { let mut chunk = TerrainChunk::new( MIN_Z, Block::new(BlockKind::Rock, Rgb::zero()), - Block::empty(), + Block::air(SpriteKind::Empty), TerrainChunkMeta::void(), ); for pos in chunk.pos_iter( diff --git a/common/src/path.rs b/common/src/path.rs index f5840acf7a..83d3ea52a5 100644 --- a/common/src/path.rs +++ b/common/src/path.rs @@ -133,7 +133,7 @@ impl Route { // Only consider the node reached if there's nothing solid between us and it && (vol .ray(pos + Vec3::unit_z() * 1.5, closest_tgt + Vec3::unit_z() * 1.5) - .until(|block| block.is_solid()) + .until(Block::is_solid) .cast() .0 > pos.distance(closest_tgt) * 0.9 || dist_sqrd < 0.5) diff --git a/common/src/ray.rs b/common/src/ray.rs index 7f58dc3926..5c6f694892 100644 --- a/common/src/ray.rs +++ b/common/src/ray.rs @@ -1,11 +1,8 @@ -use crate::{ - span, - vol::{ReadVol, Vox}, -}; +use crate::{span, vol::ReadVol}; use vek::*; -pub trait RayUntil = FnMut(&V) -> bool; -pub trait RayForEach = FnMut(&V, Vec3); +pub trait RayUntil = FnMut(&V) -> bool; +pub trait RayForEach = FnMut(&V, Vec3); pub struct Ray<'a, V: ReadVol, F: RayUntil, G: RayForEach> { vol: &'a V, diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index aae09363b9..1ca7bed62f 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -15,7 +15,7 @@ use crate::{ span, state::{DeltaTime, Time}, sync::{Uid, UidAllocator}, - terrain::TerrainGrid, + terrain::{Block, TerrainGrid}, util::Dir, vol::ReadVol, }; @@ -195,7 +195,7 @@ impl<'a> System<'a> for Sys { * 5.0 + Vec3::unit_z(), ) - .until(|block| block.is_solid()) + .until(Block::is_solid) .cast() .1 .map_or(true, |b| b.is_none()) @@ -374,7 +374,7 @@ impl<'a> System<'a> for Sys { { let can_see_tgt = terrain .ray(pos.0 + Vec3::unit_z(), tgt_pos.0 + Vec3::unit_z()) - .until(|block| !block.is_air()) + .until(Block::is_opaque) .cast() .0 .powf(2.0) @@ -473,7 +473,7 @@ impl<'a> System<'a> for Sys { // Can we even see them? .filter(|(_, e_pos, _, _)| terrain .ray(pos.0 + Vec3::unit_z(), e_pos.0 + Vec3::unit_z()) - .until(|block| !block.is_air()) + .until(Block::is_opaque) .cast() .0 >= e_pos.0.distance(pos.0)) .min_by_key(|(_, e_pos, _, _)| (e_pos.0.distance_squared(pos.0) * 100.0) as i32) diff --git a/common/src/sys/phys.rs b/common/src/sys/phys.rs index c1e11b7a0b..bf754691fd 100644 --- a/common/src/sys/phys.rs +++ b/common/src/sys/phys.rs @@ -358,6 +358,7 @@ impl<'a> System<'a> for Sys { pos: Vec3, terrain: &'a TerrainGrid, hit: &'a impl Fn(&Block) -> bool, + height: &'a impl Fn(&Block) -> f32, near_iter: impl Iterator + 'a, radius: f32, z_range: Range, @@ -373,7 +374,7 @@ impl<'a> System<'a> for Sys { let block_aabb = Aabb { min: block_pos.map(|e| e as f32), max: block_pos.map(|e| e as f32) - + Vec3::new(1.0, 1.0, block.solid_height()), + + Vec3::new(1.0, 1.0, height(&block)), }; if player_aabb.collides_with_aabb(block_aabb) { @@ -391,12 +392,12 @@ impl<'a> System<'a> for Sys { fn collision_with<'a>( pos: Vec3, terrain: &'a TerrainGrid, - hit: &impl Fn(&Block) -> bool, + hit: impl Fn(&Block) -> bool, near_iter: impl Iterator + 'a, radius: f32, z_range: Range, ) -> bool { - collision_iter(pos, terrain, hit, near_iter, radius, z_range).count() + collision_iter(pos, terrain, &|block| block.is_solid() && hit(block), &Block::solid_height, near_iter, radius, z_range).count() > 0 }; @@ -412,13 +413,14 @@ impl<'a> System<'a> for Sys { .ceil() .max(1.0); let old_pos = pos.0; + fn block_true(_: &Block) -> bool { true } for _ in 0..increments as usize { pos.0 += pos_delta / increments; const MAX_ATTEMPTS: usize = 16; // While the player is colliding with the terrain... - while collision_with(pos.0, &terrain, &|block| block.is_solid(), near_iter.clone(), radius, z_range.clone()) + while collision_with(pos.0, &terrain, block_true, near_iter.clone(), radius, z_range.clone()) && attempts < MAX_ATTEMPTS { // Calculate the player's AABB @@ -492,7 +494,7 @@ impl<'a> System<'a> for Sys { // When the resolution direction is non-vertical, we must be colliding // with a wall If the space above is free... - if !collision_with(Vec3::new(pos.0.x, pos.0.y, (pos.0.z + 0.1).ceil()), &terrain, &|block| block.is_solid(), near_iter.clone(), radius, z_range.clone()) + if !collision_with(Vec3::new(pos.0.x, pos.0.y, (pos.0.z + 0.1).ceil()), &terrain, block_true, near_iter.clone(), radius, z_range.clone()) // ...and we're being pushed out horizontally... && resolve_dir.z == 0.0 // ...and the vertical resolution direction is sufficiently great... @@ -506,7 +508,7 @@ impl<'a> System<'a> for Sys { && collision_with( pos.0 + resolve_dir - Vec3::unit_z() * 1.05, &terrain, - &|block| block.is_solid(), + block_true, near_iter.clone(), radius, z_range.clone(), @@ -548,7 +550,7 @@ impl<'a> System<'a> for Sys { } else if collision_with( pos.0 - Vec3::unit_z() * 1.05, &terrain, - &|block| block.is_solid(), + block_true, near_iter.clone(), radius, z_range.clone(), @@ -558,10 +560,7 @@ impl<'a> System<'a> for Sys { && !collision_with( pos.0 - Vec3::unit_z() * 0.05, &terrain, - &|block| { - block.is_solid() - && block.solid_height() >= (pos.0.z - 0.05).rem_euclid(1.0) - }, + |block| block.solid_height() >= (pos.0.z - 0.05).rem_euclid(1.0), near_iter.clone(), radius, z_range.clone(), @@ -592,7 +591,7 @@ impl<'a> System<'a> for Sys { if collision_with( pos.0 + *dir * 0.01, &terrain, - &|block| block.is_solid(), + block_true, near_iter.clone(), radius, z_range.clone(), @@ -613,6 +612,8 @@ impl<'a> System<'a> for Sys { pos.0, &terrain, &|block| block.is_liquid(), + // The liquid part of a liquid block always extends 1 block high. + &|_block| 1.0, near_iter.clone(), radius, z_min..z_max, @@ -622,7 +623,7 @@ impl<'a> System<'a> for Sys { }, Collider::Point => { let (dist, block) = terrain.ray(pos.0, pos.0 + pos_delta) - .until(|vox| !vox.is_air() && !vox.is_liquid()) + .until(|block| block.is_filled()) .ignore_error().cast(); pos.0 += pos_delta.try_normalized().unwrap_or(Vec3::zero()) * dist; diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index cd7feff860..d807d0f548 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -1,5 +1,5 @@ use super::SpriteKind; -use crate::{make_case_elim, vol::Vox}; +use crate::make_case_elim; use enum_iterator::IntoEnumIterator; use lazy_static::lazy_static; use num_derive::FromPrimitive; @@ -101,20 +101,10 @@ impl Deref for Block { fn deref(&self) -> &Self::Target { &self.kind } } -impl Vox for Block { - fn empty() -> Self { - Self { - kind: BlockKind::Air, - attr: [0; 3], - } - } - - fn is_empty(&self) -> bool { *self == Block::empty() } -} - impl Block { pub const MAX_HEIGHT: f32 = 3.0; + #[inline] pub const fn new(kind: BlockKind, color: Rgb) -> Self { Self { kind, @@ -127,6 +117,7 @@ impl Block { } } + #[inline] pub const fn air(sprite: SpriteKind) -> Self { Self { kind: BlockKind::Air, @@ -134,6 +125,15 @@ impl Block { } } + /// TODO: See if we can generalize this somehow. + #[inline] + pub const fn water(sprite: SpriteKind) -> Self { + Self { + kind: BlockKind::Water, + attr: [sprite as u8, 0, 0], + } + } + #[inline] pub fn get_color(&self) -> Option> { if self.has_color() { @@ -237,7 +237,9 @@ impl Block { if self.is_fluid() { Block::new(self.kind(), Rgb::zero()) } else { - Block::empty() + // FIXME: Figure out if there's some sensible way to determine what medium to + // replace a filled block with if it's removed. + Block::air(SpriteKind::Empty) } } } diff --git a/common/src/terrain/chonk.rs b/common/src/terrain/chonk.rs index bda7d955e9..b41f70a592 100644 --- a/common/src/terrain/chonk.rs +++ b/common/src/terrain/chonk.rs @@ -1,7 +1,7 @@ use crate::{ vol::{ BaseVol, IntoPosIterator, IntoVolIterator, ReadVol, RectRasterableVol, RectVolSize, - VolSize, Vox, WriteVol, + VolSize, WriteVol, }, volumes::chunk::{Chunk, ChunkError, ChunkPosIter, ChunkVolIter}, }; @@ -34,7 +34,7 @@ impl VolSize for SubChunkSize { type SubChunk = Chunk, M>; #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Chonk { +pub struct Chonk { z_offset: i32, sub_chunks: Vec>, below: V, @@ -43,7 +43,7 @@ pub struct Chonk { phantom: PhantomData, } -impl Chonk { +impl Chonk { pub fn new(z_offset: i32, below: V, above: V, meta: M) -> Self { Self { z_offset, @@ -82,16 +82,16 @@ impl Chonk { fn sub_chunk_min_z(&self, z: i32) -> i32 { z - self.sub_chunk_z(z) } } -impl BaseVol for Chonk { +impl BaseVol for Chonk { type Error = ChonkError; type Vox = V; } -impl RectRasterableVol for Chonk { +impl RectRasterableVol for Chonk { const RECT_SIZE: Vec2 = S::RECT_SIZE; } -impl ReadVol for Chonk { +impl ReadVol for Chonk { #[inline(always)] fn get(&self, pos: Vec3) -> Result<&V, Self::Error> { if pos.z < self.get_min_z() { @@ -113,7 +113,7 @@ impl ReadVol for Chonk { } } -impl WriteVol for Chonk { +impl WriteVol for Chonk { #[inline(always)] fn set(&mut self, pos: Vec3, block: Self::Vox) -> Result<(), Self::Error> { let mut sub_chunk_idx = self.sub_chunk_idx(pos.z); @@ -140,14 +140,14 @@ impl WriteVol for Chonk { } } -struct ChonkIterHelper { +struct ChonkIterHelper { sub_chunk_min_z: i32, lower_bound: Vec3, upper_bound: Vec3, phantom: PhantomData>, } -impl Iterator for ChonkIterHelper { +impl Iterator for ChonkIterHelper { type Item = (i32, Vec3, Vec3); #[inline(always)] @@ -168,12 +168,12 @@ impl Iterator for ChonkIterHelper { } #[allow(clippy::type_complexity)] // TODO: Pending review in #587 -pub struct ChonkPosIter { +pub struct ChonkPosIter { outer: ChonkIterHelper, opt_inner: Option<(i32, ChunkPosIter, M>)>, } -impl Iterator for ChonkPosIter { +impl Iterator for ChonkPosIter { type Item = Vec3; #[inline(always)] @@ -195,18 +195,18 @@ impl Iterator for ChonkPosIter { } } -enum InnerChonkVolIter<'a, V: Vox, S: RectVolSize, M: Clone> { +enum InnerChonkVolIter<'a, V, S: RectVolSize, M: Clone> { Vol(ChunkVolIter<'a, V, SubChunkSize, M>), Pos(ChunkPosIter, M>), } -pub struct ChonkVolIter<'a, V: Vox, S: RectVolSize, M: Clone> { +pub struct ChonkVolIter<'a, V, S: RectVolSize, M: Clone> { chonk: &'a Chonk, outer: ChonkIterHelper, opt_inner: Option<(i32, InnerChonkVolIter<'a, V, S, M>)>, } -impl<'a, V: Vox, S: RectVolSize, M: Clone> Iterator for ChonkVolIter<'a, V, S, M> { +impl<'a, V, S: RectVolSize, M: Clone> Iterator for ChonkVolIter<'a, V, S, M> { type Item = (Vec3, &'a V); #[inline(always)] @@ -249,7 +249,7 @@ impl<'a, V: Vox, S: RectVolSize, M: Clone> Iterator for ChonkVolIter<'a, V, S, M } } -impl<'a, V: Vox, S: RectVolSize, M: Clone> IntoPosIterator for &'a Chonk { +impl<'a, V, S: RectVolSize, M: Clone> IntoPosIterator for &'a Chonk { type IntoIter = ChonkPosIter; fn pos_iter(self, lower_bound: Vec3, upper_bound: Vec3) -> Self::IntoIter { @@ -265,7 +265,7 @@ impl<'a, V: Vox, S: RectVolSize, M: Clone> IntoPosIterator for &'a Chonk IntoVolIterator<'a> for &'a Chonk { +impl<'a, V, S: RectVolSize, M: Clone> IntoVolIterator<'a> for &'a Chonk { type IntoIter = ChonkVolIter<'a, V, S, M>; fn vol_iter(self, lower_bound: Vec3, upper_bound: Vec3) -> Self::IntoIter { diff --git a/common/src/terrain/sprite.rs b/common/src/terrain/sprite.rs index cae21755e4..bc568c8378 100644 --- a/common/src/terrain/sprite.rs +++ b/common/src/terrain/sprite.rs @@ -139,6 +139,24 @@ impl SpriteKind { SpriteKind::WardrobeSingle => 3.0, SpriteKind::WardrobeDouble => 3.0, SpriteKind::Pot => 0.90, + // TODO: Find suitable heights. + SpriteKind::BarrelCactus + | SpriteKind::RoundCactus + | SpriteKind::ShortCactus + | SpriteKind::MedFlatCactus + | SpriteKind::ShortFlatCactus + | SpriteKind::Apple + | SpriteKind::Velorite + | SpriteKind::VeloriteFrag + | SpriteKind::Coconut + | SpriteKind::StreetLampTall + | SpriteKind::Window1 + | SpriteKind::Window2 + | SpriteKind::Window3 + | SpriteKind::Window4 + | SpriteKind::DropGate => 1.0, + // TODO: Figure out if this should be solid or not. + SpriteKind::Shelf => 1.0, _ => return None, }) } diff --git a/common/src/terrain/structure.rs b/common/src/terrain/structure.rs index f898d38ac2..34905ddd96 100644 --- a/common/src/terrain/structure.rs +++ b/common/src/terrain/structure.rs @@ -2,7 +2,7 @@ use super::BlockKind; use crate::{ assets::{self, Asset, Ron}, make_case_elim, - vol::{BaseVol, ReadVol, SizedVol, Vox, WriteVol}, + vol::{BaseVol, ReadVol, SizedVol, WriteVol}, volumes::dyna::{Dyna, DynaError}, }; use dot_vox::DotVoxData; @@ -34,12 +34,6 @@ make_case_elim!( } ); -impl Vox for StructureBlock { - fn empty() -> Self { StructureBlock::None } - - fn is_empty(&self) -> bool { matches!(self, StructureBlock::None) } -} - #[derive(Debug)] pub enum StructureError {} @@ -112,7 +106,7 @@ impl Asset for Structure { let mut vol = Dyna::filled( Vec3::new(model.size.x, model.size.y, model.size.z), - StructureBlock::empty(), + StructureBlock::None, (), ); @@ -147,14 +141,14 @@ impl Asset for Structure { Ok(Structure { center: Vec3::zero(), vol, - empty: StructureBlock::empty(), + empty: StructureBlock::None, default_kind: BlockKind::Misc, }) } else { Ok(Self { center: Vec3::zero(), - vol: Dyna::filled(Vec3::zero(), StructureBlock::empty(), ()), - empty: StructureBlock::empty(), + vol: Dyna::filled(Vec3::zero(), StructureBlock::None, ()), + empty: StructureBlock::None, default_kind: BlockKind::Misc, }) } diff --git a/common/src/vol.rs b/common/src/vol.rs index b0ae7a6be2..7b0a49df8b 100644 --- a/common/src/vol.rs +++ b/common/src/vol.rs @@ -22,7 +22,7 @@ pub trait Vox: Sized + Clone + PartialEq { /// A volume that contains voxel data. pub trait BaseVol { - type Vox: Vox; + type Vox; type Error: Debug; fn scaled_by(self, scale: Vec3) -> Scaled @@ -98,6 +98,9 @@ pub trait ReadVol: BaseVol { fn get<'a>(&'a self, pos: Vec3) -> Result<&'a Self::Vox, Self::Error>; #[allow(clippy::type_complexity)] // TODO: Pending review in #587 + /// NOTE: By default, this ray will simply run from `from` to `to` without + /// stopping. To make something interesting happen, call `until` or + /// `for_each`. fn ray<'a>( &'a self, from: Vec3, @@ -106,7 +109,7 @@ pub trait ReadVol: BaseVol { where Self: Sized, { - Ray::new(self, from, to, |vox| !vox.is_empty()) + Ray::new(self, from, to, |_| true) } } diff --git a/common/src/volumes/chunk.rs b/common/src/volumes/chunk.rs index 54e8453b3b..f46023c108 100644 --- a/common/src/volumes/chunk.rs +++ b/common/src/volumes/chunk.rs @@ -1,5 +1,5 @@ use crate::vol::{ - BaseVol, IntoPosIterator, IntoVolIterator, RasterableVol, ReadVol, VolSize, Vox, WriteVol, + BaseVol, IntoPosIterator, IntoVolIterator, RasterableVol, ReadVol, VolSize, WriteVol, }; use serde::{Deserialize, Serialize}; use std::{iter::Iterator, marker::PhantomData}; @@ -46,7 +46,7 @@ pub enum ChunkError { /// index buffer can consist of `u8`s. This keeps the space requirement for the /// index buffer as low as 4 cache lines. #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Chunk { +pub struct Chunk { indices: Vec, /* TODO (haslersn): Box<[u8; S::SIZE.x * S::SIZE.y * S::SIZE.z]>, this is * however not possible in Rust yet */ vox: Vec, @@ -55,7 +55,7 @@ pub struct Chunk { phantom: PhantomData, } -impl Chunk { +impl Chunk { const GROUP_COUNT: Vec3 = Vec3::new( S::SIZE.x / Self::GROUP_SIZE.x, S::SIZE.y / Self::GROUP_SIZE.y, @@ -151,7 +151,10 @@ impl Chunk { } #[inline(always)] - fn force_idx_unchecked(&mut self, pos: Vec3) -> usize { + fn force_idx_unchecked(&mut self, pos: Vec3) -> usize + where + V: Clone, + { let grp_idx = Self::grp_idx(pos); let rel_idx = Self::rel_idx(pos); let base = &mut self.indices[grp_idx as usize]; @@ -173,7 +176,10 @@ impl Chunk { } #[inline(always)] - fn set_unchecked(&mut self, pos: Vec3, vox: V) { + fn set_unchecked(&mut self, pos: Vec3, vox: V) + where + V: Clone + PartialEq, + { if vox != self.default { let idx = self.force_idx_unchecked(pos); self.vox[idx] = vox; @@ -183,16 +189,16 @@ impl Chunk { } } -impl BaseVol for Chunk { +impl BaseVol for Chunk { type Error = ChunkError; type Vox = V; } -impl RasterableVol for Chunk { +impl RasterableVol for Chunk { const SIZE: Vec3 = S::SIZE; } -impl ReadVol for Chunk { +impl ReadVol for Chunk { #[inline(always)] fn get(&self, pos: Vec3) -> Result<&Self::Vox, Self::Error> { if !pos @@ -206,7 +212,7 @@ impl ReadVol for Chunk { } } -impl WriteVol for Chunk { +impl WriteVol for Chunk { #[inline(always)] #[allow(clippy::unit_arg)] // TODO: Pending review in #587 fn set(&mut self, pos: Vec3, vox: Self::Vox) -> Result<(), Self::Error> { @@ -221,7 +227,7 @@ impl WriteVol for Chunk { } } -pub struct ChunkPosIter { +pub struct ChunkPosIter { // Store as `u8`s so as to reduce memory footprint. lb: Vec3, ub: Vec3, @@ -229,7 +235,7 @@ pub struct ChunkPosIter { phantom: PhantomData>, } -impl ChunkPosIter { +impl ChunkPosIter { fn new(lower_bound: Vec3, upper_bound: Vec3) -> Self { // If the range is empty, then we have the special case `ub = lower_bound`. let ub = if lower_bound.map2(upper_bound, |l, u| l < u).reduce_and() { @@ -246,7 +252,7 @@ impl ChunkPosIter { } } -impl Iterator for ChunkPosIter { +impl Iterator for ChunkPosIter { type Item = Vec3; #[inline(always)] @@ -301,12 +307,12 @@ impl Iterator for ChunkPosIter { } } -pub struct ChunkVolIter<'a, V: Vox, S: VolSize, M> { +pub struct ChunkVolIter<'a, V, S: VolSize, M> { chunk: &'a Chunk, iter_impl: ChunkPosIter, } -impl<'a, V: Vox, S: VolSize, M> Iterator for ChunkVolIter<'a, V, S, M> { +impl<'a, V, S: VolSize, M> Iterator for ChunkVolIter<'a, V, S, M> { type Item = (Vec3, &'a V); #[inline(always)] @@ -317,7 +323,7 @@ impl<'a, V: Vox, S: VolSize, M> Iterator for ChunkVolIter<'a, V, S, M> { } } -impl Chunk { +impl Chunk { /// It's possible to obtain a positional iterator without having a `Chunk` /// instance. pub fn pos_iter(lower_bound: Vec3, upper_bound: Vec3) -> ChunkPosIter { @@ -325,7 +331,7 @@ impl Chunk { } } -impl<'a, V: Vox, S: VolSize, M> IntoPosIterator for &'a Chunk { +impl<'a, V, S: VolSize, M> IntoPosIterator for &'a Chunk { type IntoIter = ChunkPosIter; fn pos_iter(self, lower_bound: Vec3, upper_bound: Vec3) -> Self::IntoIter { @@ -333,7 +339,7 @@ impl<'a, V: Vox, S: VolSize, M> IntoPosIterator for &'a Chunk { } } -impl<'a, V: Vox, S: VolSize, M> IntoVolIterator<'a> for &'a Chunk { +impl<'a, V, S: VolSize, M> IntoVolIterator<'a> for &'a Chunk { type IntoIter = ChunkVolIter<'a, V, S, M>; fn vol_iter(self, lower_bound: Vec3, upper_bound: Vec3) -> Self::IntoIter { diff --git a/common/src/volumes/dyna.rs b/common/src/volumes/dyna.rs index efa11daeed..3e736c5db1 100644 --- a/common/src/volumes/dyna.rs +++ b/common/src/volumes/dyna.rs @@ -1,6 +1,6 @@ use crate::vol::{ BaseVol, DefaultPosIterator, DefaultVolIterator, IntoPosIterator, IntoVolIterator, ReadVol, - SizedVol, Vox, WriteVol, + SizedVol, WriteVol, }; use serde::{Deserialize, Serialize}; use vek::*; @@ -15,14 +15,14 @@ pub enum DynaError { // S = Size (replace when const generics are a thing) // M = Metadata #[derive(Debug, Serialize, Deserialize)] -pub struct Dyna { +pub struct Dyna { vox: Vec, meta: M, pub sz: Vec3, _phantom: std::marker::PhantomData, } -impl Clone for Dyna { +impl Clone for Dyna { fn clone(&self) -> Self { Self { vox: self.vox.clone(), @@ -33,7 +33,7 @@ impl Clone for Dyna { } } -impl Dyna { +impl Dyna { /// Used to transform a voxel position in the volume into its corresponding /// index in the voxel array. #[inline(always)] @@ -46,12 +46,12 @@ impl Dyna { } } -impl BaseVol for Dyna { +impl BaseVol for Dyna { type Error = DynaError; type Vox = V; } -impl SizedVol for Dyna { +impl SizedVol for Dyna { #[inline(always)] fn lower_bound(&self) -> Vec3 { Vec3::zero() } @@ -59,7 +59,7 @@ impl SizedVol for Dyna { fn upper_bound(&self) -> Vec3 { self.sz.map(|e| e as i32) } } -impl<'a, V: Vox, M, A: Access> SizedVol for &'a Dyna { +impl<'a, V, M, A: Access> SizedVol for &'a Dyna { #[inline(always)] fn lower_bound(&self) -> Vec3 { (*self).lower_bound() } @@ -67,7 +67,7 @@ impl<'a, V: Vox, M, A: Access> SizedVol for &'a Dyna { fn upper_bound(&self) -> Vec3 { (*self).upper_bound() } } -impl ReadVol for Dyna { +impl ReadVol for Dyna { #[inline(always)] fn get(&self, pos: Vec3) -> Result<&V, DynaError> { Self::idx_for(self.sz, pos) @@ -76,7 +76,7 @@ impl ReadVol for Dyna { } } -impl WriteVol for Dyna { +impl WriteVol for Dyna { #[inline(always)] fn set(&mut self, pos: Vec3, vox: Self::Vox) -> Result<(), DynaError> { Self::idx_for(self.sz, pos) @@ -86,7 +86,7 @@ impl WriteVol for Dyna { } } -impl<'a, V: Vox, M, A: Access> IntoPosIterator for &'a Dyna { +impl<'a, V, M, A: Access> IntoPosIterator for &'a Dyna { type IntoIter = DefaultPosIterator; fn pos_iter(self, lower_bound: Vec3, upper_bound: Vec3) -> Self::IntoIter { @@ -94,7 +94,7 @@ impl<'a, V: Vox, M, A: Access> IntoPosIterator for &'a Dyna { } } -impl<'a, V: Vox, M, A: Access> IntoVolIterator<'a> for &'a Dyna { +impl<'a, V, M, A: Access> IntoVolIterator<'a> for &'a Dyna { type IntoIter = DefaultVolIterator<'a, Dyna>; fn vol_iter(self, lower_bound: Vec3, upper_bound: Vec3) -> Self::IntoIter { @@ -102,7 +102,7 @@ impl<'a, V: Vox, M, A: Access> IntoVolIterator<'a> for &'a Dyna { } } -impl Dyna { +impl Dyna { /// Create a new `Dyna` with the provided dimensions and all voxels filled /// with duplicates of the provided voxel. pub fn filled(sz: Vec3, vox: V, meta: M) -> Self { diff --git a/common/src/volumes/scaled.rs b/common/src/volumes/scaled.rs index c435c04096..6ff23c4f6b 100644 --- a/common/src/volumes/scaled.rs +++ b/common/src/volumes/scaled.rs @@ -11,7 +11,10 @@ impl BaseVol for Scaled { type Vox = V::Vox; } -impl ReadVol for Scaled { +impl ReadVol for Scaled +where + V::Vox: Vox, +{ #[inline(always)] fn get(&self, pos: Vec3) -> Result<&Self::Vox, Self::Error> { // let ideal_pos = pos.map2(self.scale, |e, scale| (e as f32 + 0.5) / scale); diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 1808ba532c..ea5a853fe6 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -14,7 +14,7 @@ use common::{ sync::{Uid, WorldSyncExt}, terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize}, util::Dir, - vol::{RectVolSize, Vox}, + vol::RectVolSize, LoadoutBuilder, }; use rand::Rng; @@ -233,7 +233,8 @@ fn handle_make_sprite( let new_block = server .state .get_block(pos) - .unwrap_or_else(Block::empty) + // TODO: Make more principled. + .unwrap_or_else(|| Block::air(SpriteKind::Empty)) .with_sprite(sk); server.state.set_block(pos, new_block); }, diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 84af7b2577..598e1974ee 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -534,6 +534,7 @@ pub fn handle_explosion( let _ = ecs .read_resource::() .ray(pos, pos + dir * color_range) + // TODO: Faster RNG .until(|_| rand::random::() < 0.05) .for_each(|_: &Block, pos| touched_blocks.push(pos)) .cast(); @@ -569,6 +570,7 @@ pub fn handle_explosion( let terrain = ecs.read_resource::(); let _ = terrain .ray(pos, pos + dir * power) + // TODO: Faster RNG .until(|block| block.is_liquid() || rand::random::() < 0.05) .for_each(|block: &Block, pos| { if block.is_explodable() { diff --git a/server/src/sys/message.rs b/server/src/sys/message.rs index a88440b963..34fcd9fad8 100644 --- a/server/src/sys/message.rs +++ b/server/src/sys/message.rs @@ -22,8 +22,8 @@ use common::{ span, state::{BlockChange, Time}, sync::Uid, - terrain::{Block, TerrainChunkSize, TerrainGrid}, - vol::{RectVolSize, Vox}, + terrain::{TerrainChunkSize, TerrainGrid}, + vol::{ReadVol, RectVolSize}, }; use futures_executor::block_on; use futures_timer::Delay; @@ -309,8 +309,8 @@ impl Sys { _ => client.error_state(RequestStateError::Impossible), }, ClientMsg::BreakBlock(pos) => { - if can_build.get(entity).is_some() { - block_changes.set(pos, Block::empty()); + if let Some(block) = can_build.get(entity).and_then(|_| terrain.get(pos).ok()) { + block_changes.set(pos, block.into_vacant()); } }, ClientMsg::PlaceBlock(pos, block) => { diff --git a/server/src/test_world.rs b/server/src/test_world.rs index 4df254bf21..43ac693104 100644 --- a/server/src/test_world.rs +++ b/server/src/test_world.rs @@ -1,7 +1,9 @@ use common::{ generation::{ChunkSupplement, EntityInfo}, - terrain::{Block, BlockKind, MapSizeLg, TerrainChunk, TerrainChunkMeta, TerrainChunkSize}, - vol::{ReadVol, RectVolSize, Vox, WriteVol}, + terrain::{ + Block, BlockKind, MapSizeLg, SpriteKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize, + }, + vol::{ReadVol, RectVolSize, WriteVol}, }; use rand::{prelude::*, rngs::SmallRng}; use std::time::Duration; @@ -60,7 +62,7 @@ impl World { TerrainChunk::new( 256 + if rng.gen::() < 64 { height } else { 0 }, Block::new(BlockKind::Grass, Rgb::new(11, 102, 35)), - Block::empty(), + Block::air(SpriteKind::Empty), TerrainChunkMeta::void(), ), supplement, diff --git a/voxygen/benches/meshing_benchmark.rs b/voxygen/benches/meshing_benchmark.rs index 74085505d2..b0cd004443 100644 --- a/voxygen/benches/meshing_benchmark.rs +++ b/voxygen/benches/meshing_benchmark.rs @@ -1,6 +1,6 @@ use common::{ - terrain::{Block, TerrainGrid}, - vol::{SampleVol, Vox}, + terrain::{Block, SpriteKind, TerrainGrid}, + vol::SampleVol, }; use criterion::{black_box, criterion_group, criterion_main, Benchmark, Criterion}; use std::sync::Arc; @@ -69,7 +69,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { "meshing", Benchmark::new("copying 1,1 into flat array", move |b| { b.iter(|| { - let mut flat = vec![Block::empty(); range.size().product() as usize]; + let mut flat = vec![Block::air(SpriteKind::Empty); range.size().product() as usize]; let mut i = 0; let mut volume = volume.cached(); for x in 0..range.size().w { diff --git a/voxygen/src/mesh/terrain.rs b/voxygen/src/mesh/terrain.rs index bc0c857f83..1256a52fa1 100644 --- a/voxygen/src/mesh/terrain.rs +++ b/voxygen/src/mesh/terrain.rs @@ -9,7 +9,7 @@ use common::{ span, terrain::Block, util::either_with, - vol::{ReadVol, RectRasterableVol, Vox}, + vol::{ReadVol, RectRasterableVol}, volumes::vol_grid_2d::{CachedVolGrid2d, VolGrid2d}, }; use std::{collections::VecDeque, fmt::Debug}; @@ -255,7 +255,12 @@ impl<'a, V: RectRasterableVol + ReadVol + Debug> let d = d + 2; let flat = { let mut volume = self.cached(); - let mut flat = vec![Block::empty(); (w * h * d) as usize]; + + const AIR: Block = Block::air(common::terrain::sprite::SpriteKind::Empty); + + // TODO: Once we can manage it sensibly, consider using something like + // Option instead of just assuming air. + let mut flat = vec![AIR; (w * h * d) as usize]; let mut i = 0; for x in 0..range.size().w { for y in 0..range.size().h { @@ -263,7 +268,9 @@ impl<'a, V: RectRasterableVol + ReadVol + Debug> let block = volume .get(range.min + Vec3::new(x, y, z)) .map(|b| *b) - .unwrap_or(Block::empty()); + // TODO: Replace with None or some other more reasonable value, + // since it's not clear this will work properly with liquid. + .unwrap_or(AIR); if block.is_opaque() { opaque_limits = opaque_limits .map(|l| l.including(z)) diff --git a/voxygen/src/scene/camera.rs b/voxygen/src/scene/camera.rs index f48db8a7f3..e378f4add2 100644 --- a/voxygen/src/scene/camera.rs +++ b/voxygen/src/scene/camera.rs @@ -1,7 +1,4 @@ -use common::{ - span, - vol::{ReadVol, Vox}, -}; +use common::{span, terrain::TerrainGrid, vol::ReadVol}; use std::f32::consts::PI; use treeculler::Frustum; use vek::*; @@ -80,7 +77,16 @@ impl Camera { /// Compute the transformation matrices (view matrix and projection matrix) /// and position of the camera. - pub fn compute_dependents(&mut self, terrain: &impl ReadVol) { + pub fn compute_dependents(&mut self, terrain: &TerrainGrid) { + self.compute_dependents_full(terrain, |block| !block.is_opaque()) + } + + /// The is_fluid argument should return true for transparent voxels. + pub fn compute_dependents_full( + &mut self, + terrain: &V, + is_transparent: fn(&V::Vox) -> bool, + ) { span!(_guard, "compute_dependents", "Camera::compute_dependents"); let dist = { let (start, end) = (self.focus - self.forward() * self.dist, self.focus); @@ -89,7 +95,7 @@ impl Camera { .ray(start, end) .ignore_error() .max_iter(500) - .until(|b| b.is_empty()) + .until(is_transparent) .cast() { (d, Ok(Some(_))) => f32::min(self.dist - d - 0.03, self.dist), diff --git a/voxygen/src/scene/simple.rs b/voxygen/src/scene/simple.rs index 64f0167117..99c8aea920 100644 --- a/voxygen/src/scene/simple.rs +++ b/voxygen/src/scene/simple.rs @@ -22,27 +22,18 @@ use common::{ comp::{humanoid, item::ItemKind, Loadout}, figure::Segment, terrain::BlockKind, - vol::{BaseVol, ReadVol, Vox}, + vol::{BaseVol, ReadVol}, }; use tracing::error; use vek::*; -#[derive(PartialEq, Eq, Copy, Clone)] -struct VoidVox; -impl Vox for VoidVox { - fn empty() -> Self { VoidVox } - - fn is_empty(&self) -> bool { true } - - fn or(self, _other: Self) -> Self { VoidVox } -} struct VoidVol; impl BaseVol for VoidVol { type Error = (); - type Vox = VoidVox; + type Vox = (); } impl ReadVol for VoidVol { - fn get<'a>(&'a self, _pos: Vec3) -> Result<&'a Self::Vox, Self::Error> { Ok(&VoidVox) } + fn get<'a>(&'a self, _pos: Vec3) -> Result<&'a Self::Vox, Self::Error> { Ok(&()) } } fn generate_mesh<'a>( @@ -234,7 +225,7 @@ impl Scene { scene_data.mouse_smoothing, ); - self.camera.compute_dependents(&VoidVol); + self.camera.compute_dependents_full(&VoidVol, |_| true); let camera::Dependents { view_mat, proj_mat, diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index 22bfe683c2..84d3eb1c77 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -18,7 +18,7 @@ use common::{ span, spiral::Spiral2d, terrain::{sprite, Block, SpriteKind, TerrainChunk}, - vol::{BaseVol, ReadVol, RectRasterableVol, SampleVol, Vox}, + vol::{BaseVol, ReadVol, RectRasterableVol, SampleVol}, volumes::vol_grid_2d::{VolGrid2d, VolGrid2dError}, }; use core::{f32, fmt::Debug, i32, marker::PhantomData, time::Duration}; @@ -145,7 +145,11 @@ fn mesh_worker + RectRasterableVol + ReadVol + Debug>( let rel_pos = Vec3::new(x, y, z); let wpos = Vec3::from(pos * V::RECT_SIZE.map(|e: u32| e as i32)) + rel_pos; - let block = volume.get(wpos).ok().copied().unwrap_or(Block::empty()); + let block = if let Ok(block) = volume.get(wpos) { + block + } else { + continue; + }; let sprite = if let Some(sprite) = block.get_sprite() { sprite } else { diff --git a/world/src/block/mod.rs b/world/src/block/mod.rs index 0dd4e388fa..0896dd7f79 100644 --- a/world/src/block/mod.rs +++ b/world/src/block/mod.rs @@ -10,7 +10,7 @@ use common::{ structure::{self, StructureBlock}, Block, BlockKind, SpriteKind, Structure, }, - vol::{ReadVol, Vox}, + vol::ReadVol, }; use core::ops::{Div, Mul, Range}; use serde::Deserialize; @@ -164,7 +164,8 @@ impl<'a> BlockGen<'a> { } = self; let world = column_gen.sim; - let sample = &z_cache?.sample; + let z_cache = z_cache?; + let sample = &z_cache.sample; let &ColumnSample { alt, basement, @@ -188,7 +189,7 @@ impl<'a> BlockGen<'a> { .. } = sample; - let structures = &z_cache?.structures; + let structures = &z_cache.structures; let wposf = wpos.map(|e| e as f64); @@ -330,7 +331,7 @@ impl<'a> BlockGen<'a> { }) .or(block); - Some(block.unwrap_or_else(Block::empty)) + block } } @@ -467,6 +468,8 @@ impl StructureInfo { self.pos.into(), self.seed, sample, + // TODO: Take environment into account. + Block::air, ) }) }, @@ -481,6 +484,7 @@ pub fn block_from_structure( structure_pos: Vec2, structure_seed: u32, sample: &ColumnSample, + mut with_sprite: impl FnMut(SpriteKind) -> Block, ) -> Option { let field = RandomField::new(structure_seed); @@ -489,41 +493,41 @@ pub fn block_from_structure( match sblock { StructureBlock::None => None, - StructureBlock::Hollow => Some(Block::empty()), + StructureBlock::Hollow => Some(with_sprite(SpriteKind::Empty)), StructureBlock::Grass => Some(Block::new( BlockKind::Grass, sample.surface_color.map(|e| (e * 255.0) as u8), )), - StructureBlock::Normal(color) => { - Some(Block::new(BlockKind::Misc, color)).filter(|block| !block.is_empty()) - }, - // Water / sludge throw away their color bits currently, so we don't set anyway. - StructureBlock::Water => Some(Block::new(BlockKind::Water, Rgb::zero())), - StructureBlock::GreenSludge => Some(Block::new( - BlockKind::Water, - // TODO: If/when liquid supports other colors again, revisit this. - Rgb::zero(), - )), + StructureBlock::Normal(color) => Some(Block::new(BlockKind::Misc, color)), + StructureBlock::Water => Some(Block::water(SpriteKind::Empty)), + // TODO: If/when liquid supports other colors again, revisit this. + StructureBlock::GreenSludge => Some(Block::water(SpriteKind::Empty)), // None of these BlockKinds has an orientation, so we just use zero for the other color // bits. - StructureBlock::Liana => Some(Block::air(SpriteKind::Liana)), - StructureBlock::Fruit => Some(if field.get(pos + structure_pos) % 24 == 0 { - Block::air(SpriteKind::Beehive) - } else if field.get(pos + structure_pos + 1) % 3 == 0 { - Block::air(SpriteKind::Apple) - } else { - Block::empty() - }), - StructureBlock::Coconut => Some(if field.get(pos + structure_pos) % 3 > 0 { - Block::empty() - } else { - Block::air(SpriteKind::Coconut) - }), - StructureBlock::Chest => Some(if structure_seed % 10 < 7 { - Block::empty() - } else { - Block::air(SpriteKind::Chest) - }), + StructureBlock::Liana => Some(with_sprite(SpriteKind::Liana)), + StructureBlock::Fruit => { + if field.get(pos + structure_pos) % 24 == 0 { + Some(with_sprite(SpriteKind::Beehive)) + } else if field.get(pos + structure_pos + 1) % 3 == 0 { + Some(with_sprite(SpriteKind::Apple)) + } else { + None + } + }, + StructureBlock::Coconut => { + if field.get(pos + structure_pos) % 3 > 0 { + None + } else { + Some(with_sprite(SpriteKind::Coconut)) + } + }, + StructureBlock::Chest => { + if structure_seed % 10 < 7 { + None + } else { + Some(with_sprite(SpriteKind::Chest)) + } + }, // We interpolate all these BlockKinds as needed. StructureBlock::TemperateLeaves | StructureBlock::PineLeaves diff --git a/world/src/layer/mod.rs b/world/src/layer/mod.rs index e132600faa..666664f49c 100644 --- a/world/src/layer/mod.rs +++ b/world/src/layer/mod.rs @@ -13,7 +13,7 @@ use common::{ generation::{ChunkSupplement, EntityInfo}, lottery::Lottery, terrain::{Block, BlockKind, SpriteKind}, - vol::{BaseVol, ReadVol, RectSizedVol, Vox, WriteVol}, + vol::{BaseVol, ReadVol, RectSizedVol, WriteVol}, }; use noise::NoiseFn; use rand::prelude::*; @@ -30,6 +30,8 @@ pub struct Colors { pub stalagtite: (u8, u8, u8), } +const EMPTY_AIR: Block = Block::air(SpriteKind::Empty); + pub fn apply_paths_to<'a>( wpos2d: Vec2, mut get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, @@ -113,7 +115,7 @@ pub fn apply_paths_to<'a>( for z in inset..inset + head_space { let pos = Vec3::new(offs.x, offs.y, surface_z + z); if vol.get(pos).unwrap().kind() != BlockKind::Water { - let _ = vol.set(pos, Block::empty()); + let _ = vol.set(pos, EMPTY_AIR); } } } @@ -163,7 +165,7 @@ pub fn apply_caves_to<'a>( .into_array(), ) < 0.0 { - let _ = vol.set(Vec3::new(offs.x, offs.y, z), Block::empty()); + let _ = vol.set(Vec3::new(offs.x, offs.y, z), EMPTY_AIR); } } @@ -207,7 +209,8 @@ pub fn apply_caves_to<'a>( } #[allow(clippy::eval_order_dependence)] pub fn apply_caves_supplement<'a>( - rng: &mut impl Rng, + // NOTE: Used only for dynamic elements like chests and entities! + dynamic_rng: &mut impl Rng, wpos2d: Vec2, mut get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, vol: &(impl BaseVol + RectSizedVol + ReadVol + WriteVol), @@ -253,42 +256,42 @@ pub fn apply_caves_supplement<'a>( wpos2d.y as f32, cave_base as f32, )) - .with_body(match rng.gen_range(0, 6) { + .with_body(match dynamic_rng.gen_range(0, 6) { 0 => { is_hostile = false; - let species = match rng.gen_range(0, 4) { + let species = match dynamic_rng.gen_range(0, 4) { 0 => comp::quadruped_small::Species::Truffler, 1 => comp::quadruped_small::Species::Dodarock, 2 => comp::quadruped_small::Species::Holladon, _ => comp::quadruped_small::Species::Batfox, }; - comp::quadruped_small::Body::random_with(rng, &species).into() + comp::quadruped_small::Body::random_with(dynamic_rng, &species).into() }, 1 => { is_hostile = true; - let species = match rng.gen_range(0, 5) { + let species = match dynamic_rng.gen_range(0, 5) { 0 => comp::quadruped_medium::Species::Tarasque, _ => comp::quadruped_medium::Species::Bonerattler, }; - comp::quadruped_medium::Body::random_with(rng, &species).into() + comp::quadruped_medium::Body::random_with(dynamic_rng, &species).into() }, 2 => { is_hostile = true; - let species = match rng.gen_range(0, 4) { + let species = match dynamic_rng.gen_range(0, 4) { 1 => comp::quadruped_low::Species::Rocksnapper, _ => comp::quadruped_low::Species::Salamander, }; - comp::quadruped_low::Body::random_with(rng, &species).into() + comp::quadruped_low::Body::random_with(dynamic_rng, &species).into() }, _ => { is_hostile = true; - let species = match rng.gen_range(0, 8) { + let species = match dynamic_rng.gen_range(0, 8) { 0 => comp::biped_large::Species::Ogre, 1 => comp::biped_large::Species::Cyclops, 2 => comp::biped_large::Species::Wendigo, _ => comp::biped_large::Species::Troll, }; - comp::biped_large::Body::random_with(rng, &species).into() + comp::biped_large::Body::random_with(dynamic_rng, &species).into() }, }) .with_alignment(if is_hostile { diff --git a/world/src/lib.rs b/world/src/lib.rs index 823dcdf1d6..bc2b4d3477 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -38,8 +38,8 @@ use common::{ comp::{self, bird_medium, quadruped_low, quadruped_medium, quadruped_small}, generation::{ChunkSupplement, EntityInfo}, msg::server::WorldMapMsg, - terrain::{Block, BlockKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize}, - vol::{ReadVol, RectVolSize, Vox, WriteVol}, + terrain::{Block, BlockKind, SpriteKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize}, + vol::{ReadVol, RectVolSize, WriteVol}, }; use rand::Rng; use serde::Deserialize; @@ -114,7 +114,7 @@ impl World { |offs| sampler.get_z_cache(chunk_wpos2d - grid_border + offs, index), ); - let air = Block::empty(); + let air = Block::air(SpriteKind::Empty); let stone = Block::new( BlockKind::Rock, zcache_grid @@ -195,7 +195,8 @@ impl World { .map(|zc| &zc.sample) }; - let mut rng = rand::thread_rng(); + // Only use for rng affecting dynamic elements like chests and entities! + let mut dynamic_rng = rand::thread_rng(); // Apply layers (paths, caves, etc.) layer::apply_caves_to(chunk_wpos2d, sample_get, &mut chunk, index); @@ -207,17 +208,17 @@ impl World { index.sites[*site].apply_to(index, chunk_wpos2d, sample_get, &mut chunk) }); - let gen_entity_pos = || { + let gen_entity_pos = |dynamic_rng: &mut rand::rngs::ThreadRng| { let lpos2d = TerrainChunkSize::RECT_SIZE - .map(|sz| rand::thread_rng().gen::().rem_euclid(sz) as i32); + .map(|sz| dynamic_rng.gen::().rem_euclid(sz) as i32); let mut lpos = Vec3::new( lpos2d.x, lpos2d.y, sample_get(lpos2d).map(|s| s.alt as i32 - 32).unwrap_or(0), ); - while chunk.get(lpos).map(|vox| !vox.is_empty()).unwrap_or(false) { - lpos.z += 1; + while let Some(block) = chunk.get(lpos).ok().copied().filter(Block::is_solid) { + lpos.z += block.solid_height().ceil() as i32; } (Vec3::from(chunk_wpos2d) + lpos).map(|e: i32| e as f32) + 0.5 @@ -225,18 +226,18 @@ impl World { const SPAWN_RATE: f32 = 0.1; let mut supplement = ChunkSupplement { - entities: if rng.gen::() < SPAWN_RATE + entities: if dynamic_rng.gen::() < SPAWN_RATE && sim_chunk.chaos < 0.5 && !sim_chunk.is_underwater() { // TODO: REFACTOR: Define specific alignments in a config file instead of here let is_hostile: bool; - let is_giant = rng.gen_range(0, 8) == 0; + let is_giant = dynamic_rng.gen_range(0, 8) == 0; let quadmed = comp::Body::QuadrupedMedium(quadruped_medium::Body::random()); // Not all of them are hostile so we have to do the rng here let quadlow = comp::Body::QuadrupedLow(quadruped_low::Body::random()); // Not all of them are hostile so we have to do the rng here - let entity = EntityInfo::at(gen_entity_pos()) + let entity = EntityInfo::at(gen_entity_pos(&mut dynamic_rng)) .do_if(is_giant, |e| e.into_giant()) - .with_body(match rng.gen_range(0, 5) { + .with_body(match dynamic_rng.gen_range(0, 5) { 0 => { match quadmed { comp::Body::QuadrupedMedium(quadruped_medium) => { @@ -292,12 +293,12 @@ impl World { }; if sim_chunk.contains_waypoint { - supplement.add_entity(EntityInfo::at(gen_entity_pos()).into_waypoint()); + supplement.add_entity(EntityInfo::at(gen_entity_pos(&mut dynamic_rng)).into_waypoint()); } // Apply layer supplement layer::apply_caves_supplement( - &mut rng, + &mut dynamic_rng, chunk_wpos2d, sample_get, &chunk, @@ -307,7 +308,12 @@ impl World { // Apply site supplementary information sim_chunk.sites.iter().for_each(|site| { - index.sites[*site].apply_supplement(&mut rng, chunk_wpos2d, sample_get, &mut supplement) + index.sites[*site].apply_supplement( + &mut dynamic_rng, + chunk_wpos2d, + sample_get, + &mut supplement, + ) }); Ok((chunk, supplement)) diff --git a/world/src/site/block_mask.rs b/world/src/site/block_mask.rs index d94afcb6eb..dea5af7714 100644 --- a/world/src/site/block_mask.rs +++ b/world/src/site/block_mask.rs @@ -1,27 +1,32 @@ -use common::{terrain::Block, vol::Vox}; +use common::terrain::Block; #[derive(Copy, Clone)] pub struct BlockMask { - block: Block, + block: Option, priority: i32, } impl BlockMask { - pub fn new(block: Block, priority: i32) -> Self { Self { block, priority } } - - pub fn nothing() -> Self { + pub const fn new(block: Block, priority: i32) -> Self { Self { - block: Block::empty(), + block: Some(block), + priority, + } + } + + pub const fn nothing() -> Self { + Self { + block: None, priority: 0, } } - pub fn with_priority(mut self, priority: i32) -> Self { + pub const fn with_priority(mut self, priority: i32) -> Self { self.priority = priority; self } - pub fn resolve_with(self, other: Self) -> Self { + pub const fn resolve_with(self, other: Self) -> Self { if self.priority >= other.priority { self } else { @@ -29,11 +34,5 @@ impl BlockMask { } } - pub fn finish(self) -> Option { - if self.priority > 0 { - Some(self.block) - } else { - None - } - } + pub const fn finish(self) -> Option { self.block } } diff --git a/world/src/site/castle/mod.rs b/world/src/site/castle/mod.rs index 2accbc7264..1532fdf1ea 100644 --- a/world/src/site/castle/mod.rs +++ b/world/src/site/castle/mod.rs @@ -10,8 +10,8 @@ use crate::{ }; use common::{ generation::ChunkSupplement, - terrain::{Block, BlockKind}, - vol::{BaseVol, ReadVol, RectSizedVol, Vox, WriteVol}, + terrain::{Block, BlockKind, SpriteKind}, + vol::{BaseVol, ReadVol, RectSizedVol, WriteVol}, }; use core::f32; use rand::prelude::*; @@ -202,7 +202,8 @@ impl Castle { if z > 0 { if vol.get(pos).unwrap().kind() != BlockKind::Water { - let _ = vol.set(pos, Block::empty()); + // TODO: Take environment into account. + let _ = vol.set(pos, Block::air(SpriteKind::Empty)); } } else { let _ = vol.set( @@ -415,7 +416,8 @@ impl Castle { #[allow(clippy::or_fun_call)] // TODO: Pending review in #587 pub fn apply_supplement<'a>( &'a self, - _rng: &mut impl Rng, + // NOTE: Used only for dynamic elements like chests and entities! + _dynamic_rng: &mut impl Rng, _wpos2d: Vec2, _get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, _supplement: &mut ChunkSupplement, diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index 4495a37e29..84c1eae4fd 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -15,7 +15,7 @@ use common::{ lottery::Lottery, store::{Id, Store}, terrain::{Block, BlockKind, SpriteKind, Structure, TerrainChunkSize}, - vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol}, + vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, WriteVol}, }; use core::{f32, hash::BuildHasherDefault}; use fxhash::FxHasher64; @@ -127,6 +127,8 @@ impl Dungeon { self.origin, self.seed, col_sample, + // TODO: Take environment into account. + Block::air, ) }) .unwrap_or(None) @@ -140,7 +142,13 @@ impl Dungeon { for floor in &self.floors { z -= floor.total_depth(); - let mut sampler = floor.col_sampler(index, rpos, z); + let mut sampler = floor.col_sampler( + index, + rpos, + z, + // TODO: Take environment into account. + Block::air, + ); for rz in 0..floor.total_depth() { if let Some(block) = sampler(rz).finish() { @@ -155,7 +163,8 @@ impl Dungeon { #[allow(clippy::or_fun_call)] // TODO: Pending review in #587 pub fn apply_supplement<'a>( &'a self, - rng: &mut impl Rng, + // NOTE: Used only for dynamic elements like chests and entities! + dynamic_rng: &mut impl Rng, wpos2d: Vec2, _get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, supplement: &mut ChunkSupplement, @@ -170,7 +179,7 @@ impl Dungeon { for floor in &self.floors { z -= floor.total_depth(); let origin = Vec3::new(self.origin.x, self.origin.y, z); - floor.apply_supplement(rng, area, origin, supplement); + floor.apply_supplement(dynamic_rng, area, origin, supplement); } } } @@ -208,7 +217,7 @@ pub struct Room { pillars: Option, // Pillars with the given separation } -pub struct Floor { +struct Floor { tile_offset: Vec2, tiles: Grid, rooms: Store, @@ -222,7 +231,7 @@ pub struct Floor { const FLOOR_SIZE: Vec2 = Vec2::new(18, 18); impl Floor { - pub fn generate( + fn generate( ctx: &mut GenCtx, stair_tile: Vec2, level: i32, @@ -406,9 +415,10 @@ impl Floor { } #[allow(clippy::match_single_binding)] // TODO: Pending review in #587 - pub fn apply_supplement( + fn apply_supplement( &self, - rng: &mut impl Rng, + // NOTE: Used only for dynamic elements like chests and entities! + dynamic_rng: &mut impl Rng, area: Aabr, origin: Vec3, supplement: &mut ChunkSupplement, @@ -417,9 +427,12 @@ impl Floor { Vec3::from((self.stair_tile + self.tile_offset).map(|e| e * TILE_SIZE + TILE_SIZE / 2)); if area.contains_point(stair_rcenter.xy()) { - let offs = Vec2::new(rng.gen_range(-1.0, 1.0), rng.gen_range(-1.0, 1.0)) - .try_normalized() - .unwrap_or_else(Vec2::unit_y) + let offs = Vec2::new( + dynamic_rng.gen_range(-1.0, 1.0), + dynamic_rng.gen_range(-1.0, 1.0), + ) + .try_normalized() + .unwrap_or_else(Vec2::unit_y) * (TILE_SIZE as f32 / 2.0 - 4.0); if !self.final_level { supplement.add_entity( @@ -453,16 +466,17 @@ impl Floor { if room .enemy_density - .map(|density| rng.gen_range(0, density.recip() as usize) == 0) + .map(|density| dynamic_rng.gen_range(0, density.recip() as usize) == 0) .unwrap_or(false) && !tile_is_pillar { // Bad - let chosen = Lottery::::load_expect(match rng.gen_range(0, 5) { - 0 => "common.loot_tables.loot_table_humanoids", - 1 => "common.loot_tables.loot_table_armor_misc", - _ => "common.loot_tables.loot_table_cultists", - }); + let chosen = + Lottery::::load_expect(match dynamic_rng.gen_range(0, 5) { + 0 => "common.loot_tables.loot_table_humanoids", + 1 => "common.loot_tables.loot_table_armor_misc", + _ => "common.loot_tables.loot_table_cultists", + }); let chosen = chosen.choose(); let entity = EntityInfo::at( tile_wcenter.map(|e| e as f32) @@ -476,7 +490,7 @@ impl Floor { .with_body(comp::Body::Humanoid(comp::humanoid::Body::random())) .with_automatic_name() .with_loot_drop(comp::Item::new_from_asset_expect(chosen)) - .with_main_tool(comp::Item::new_from_asset_expect(match rng.gen_range(0, 6) { + .with_main_tool(comp::Item::new_from_asset_expect(match dynamic_rng.gen_range(0, 6) { 0 => "common.items.npc_weapons.axe.malachite_axe-0", 1 => "common.items.npc_weapons.sword.cultist_purp_2h-0", 2 => "common.items.npc_weapons.sword.cultist_purp_2h-0", @@ -508,10 +522,10 @@ impl Floor { ); let chosen = chosen.choose(); let entity = EntityInfo::at(tile_wcenter.map(|e| e as f32)) - .with_level(rng.gen_range(1, 5)) + .with_level(dynamic_rng.gen_range(1, 5)) .with_alignment(comp::Alignment::Enemy) .with_body(comp::Body::Golem(comp::golem::Body::random_with( - rng, + dynamic_rng, &comp::golem::Species::StoneGolem, ))) .with_name("Stonework Defender".to_string()) @@ -525,9 +539,9 @@ impl Floor { } } - pub fn total_depth(&self) -> i32 { self.solid_depth + self.hollow_depth } + fn total_depth(&self) -> i32 { self.solid_depth + self.hollow_depth } - pub fn nearest_wall(&self, rpos: Vec2) -> Option> { + fn nearest_wall(&self, rpos: Vec2) -> Option> { let tile_pos = rpos.map(|e| e.div_euclid(TILE_SIZE)); DIRS.iter() @@ -548,11 +562,12 @@ impl Floor { } #[allow(clippy::unnested_or_patterns)] // TODO: Pending review in #587 - pub fn col_sampler<'a>( + fn col_sampler<'a>( &'a self, index: IndexRef<'a>, pos: Vec2, floor_z: i32, + mut with_sprite: impl FnMut(SpriteKind) -> Block, ) -> impl FnMut(i32) -> BlockMask + 'a { let rpos = pos - self.tile_offset * TILE_SIZE; let tile_pos = rpos.map(|e| e.div_euclid(TILE_SIZE)); @@ -561,7 +576,7 @@ impl Floor { let colors = &index.colors.site.dungeon; - let empty = BlockMask::new(Block::empty(), 1); + let vacant = BlockMask::new(with_sprite(SpriteKind::Empty), 1); let make_staircase = move |pos: Vec3, radius: f32, inner_radius: f32, stretch: f32| { let stone = BlockMask::new(Block::new(BlockKind::Rock, colors.stone.into()), 5); @@ -576,7 +591,7 @@ impl Floor { { stone } else { - empty + vacant } } else { BlockMask::nothing() @@ -593,7 +608,7 @@ impl Floor { let floor_sprite = if RandomField::new(7331).chance(Vec3::from(pos), 0.00005) { BlockMask::new( - Block::air( + with_sprite( match (RandomField::new(1337).get(Vec3::from(pos)) / 2) % 20 { 0 => SpriteKind::Apple, 1 => SpriteKind::VeloriteFrag, @@ -609,12 +624,12 @@ impl Floor { { let room = &self.rooms[*room]; if RandomField::new(room.seed).chance(Vec3::from(pos), room.loot_density * 0.5) { - BlockMask::new(Block::air(SpriteKind::Chest), 1) + BlockMask::new(with_sprite(SpriteKind::Chest), 1) } else { - empty + vacant } } else { - empty + vacant }; let tunnel_height = if self.final_level { 16.0 } else { 8.0 }; @@ -625,7 +640,7 @@ impl Floor { if dist_to_wall >= wall_thickness && (z as f32) < tunnel_height * (1.0 - tunnel_dist.powf(4.0)) { - if z == 0 { floor_sprite } else { empty } + if z == 0 { floor_sprite } else { vacant } } else { BlockMask::nothing() } @@ -651,12 +666,12 @@ impl Floor { if z == 0 { floor_sprite } else { - empty + vacant } }, Some(Tile::DownStair(_)) => { make_staircase(Vec3::new(rtile_pos.x, rtile_pos.y, z), 0.0, 0.5, 9.0) - .resolve_with(empty) + .resolve_with(vacant) }, Some(Tile::UpStair(room)) => { let mut block = make_staircase( @@ -666,7 +681,7 @@ impl Floor { 9.0, ); if z < self.rooms[*room].height { - block = block.resolve_with(empty); + block = block.resolve_with(vacant); } block }, diff --git a/world/src/site/mod.rs b/world/src/site/mod.rs index a107b37909..8988132afe 100644 --- a/world/src/site/mod.rs +++ b/world/src/site/mod.rs @@ -108,15 +108,18 @@ impl Site { pub fn apply_supplement<'a>( &'a self, - rng: &mut impl Rng, + // NOTE: Used only for dynamic elements like chests and entities! + dynamic_rng: &mut impl Rng, wpos2d: Vec2, get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, supplement: &mut ChunkSupplement, ) { match &self.kind { - SiteKind::Settlement(s) => s.apply_supplement(rng, wpos2d, get_column, supplement), - SiteKind::Dungeon(d) => d.apply_supplement(rng, wpos2d, get_column, supplement), - SiteKind::Castle(c) => c.apply_supplement(rng, wpos2d, get_column, supplement), + SiteKind::Settlement(s) => { + s.apply_supplement(dynamic_rng, wpos2d, get_column, supplement) + }, + SiteKind::Dungeon(d) => d.apply_supplement(dynamic_rng, wpos2d, get_column, supplement), + SiteKind::Castle(c) => c.apply_supplement(dynamic_rng, wpos2d, get_column, supplement), } } } diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs index 4b3056a47b..3c679cc973 100644 --- a/world/src/site/settlement/building/archetype/house.rs +++ b/world/src/site/settlement/building/archetype/house.rs @@ -9,7 +9,6 @@ use crate::{ use common::{ make_case_elim, terrain::{Block, BlockKind, SpriteKind}, - vol::Vox, }; use rand::prelude::*; use serde::Deserialize; @@ -293,8 +292,9 @@ impl Archetype for House { let floor = make_block(colors.floor); let wall = make_block(wall_color).with_priority(facade_layer); let roof = make_block(roof_color).with_priority(facade_layer - 1); - let empty = BlockMask::nothing(); - let internal = BlockMask::new(Block::empty(), internal_layer); + const EMPTY: BlockMask = BlockMask::nothing(); + // TODO: Take environment into account. + let internal = BlockMask::new(Block::air(SpriteKind::Empty), internal_layer); let end_window = BlockMask::new( Block::air(attr.window) .with_ori(match ori { @@ -399,7 +399,7 @@ impl Archetype for House { let roof_level = roof_top - roof_profile.x.max(mansard); if profile.y > roof_level { - return None; + return EMPTY; } // Roof @@ -409,9 +409,9 @@ impl Archetype for House { if (roof_profile.x == 0 && mansard == 0) || roof_dist == width + 2 || is_ribbing { // Eaves - return Some(log); + return log; } else { - return Some(roof); + return roof; } } @@ -427,44 +427,42 @@ impl Archetype for House { && attr.storey_fill.has_lower() && storey == 0 { - return Some( - if (bound_offset.x == (width - 1) / 2 - || bound_offset.x == (width - 1) / 2 + 1) - && profile.y <= foundation_height + 3 - { - // Doors on first floor only - if profile.y == foundation_height + 1 { - BlockMask::new( - Block::air(SpriteKind::Door) - .with_ori( - match ori { - Ori::East => 2, - Ori::North => 0, - } + if bound_offset.x == (width - 1) / 2 { - 0 - } else { - 4 - }, - ) - .unwrap(), - structural_layer, - ) - } else { - empty.with_priority(structural_layer) - } + return if (bound_offset.x == (width - 1) / 2 + || bound_offset.x == (width - 1) / 2 + 1) + && profile.y <= foundation_height + 3 + { + // Doors on first floor only + if profile.y == foundation_height + 1 { + BlockMask::new( + Block::air(SpriteKind::Door) + .with_ori( + match ori { + Ori::East => 2, + Ori::North => 0, + } + if bound_offset.x == (width - 1) / 2 { + 0 + } else { + 4 + }, + ) + .unwrap(), + structural_layer, + ) } else { - wall - }, - ); + EMPTY.with_priority(structural_layer) + } + } else { + wall + }; } if bound_offset.x == bound_offset.y || profile.y == ceil_height { // Support beams - return Some(log); + return log; } else if !attr.storey_fill.has_lower() && profile.y < ceil_height { - return Some(empty); + return EMPTY; } else if !attr.storey_fill.has_upper() { - return Some(empty); + return EMPTY; } else { let (frame_bounds, frame_borders) = if profile.y >= ceil_height { ( @@ -495,19 +493,19 @@ impl Archetype for House { // 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(end_window); + return end_window; } else if frame_bounds.contains_point(surface_pos) { - return Some(log.with_priority(structural_layer)); + return log.with_priority(structural_layer); }; } // Wall - return Some(if attr.central_supports && profile.x == 0 { + return if attr.central_supports && profile.x == 0 { // Support beams log.with_priority(structural_layer) } else { wall - }); + }; } } @@ -516,15 +514,15 @@ impl Archetype for House { if profile.y == ceil_height { if profile.x == 0 { // Rafters - return Some(log); + return log; } else if attr.storey_fill.has_upper() { // Ceiling - return Some(floor); + return floor; } } else if (!attr.storey_fill.has_lower() && profile.y < ceil_height) || (!attr.storey_fill.has_upper() && profile.y >= ceil_height) { - return Some(empty); + return EMPTY; // Furniture } else if dist == width - 1 && center_offset.sum() % 2 == 0 @@ -533,7 +531,8 @@ impl Archetype for House { .noise .chance(Vec3::new(center_offset.x, center_offset.y, z), 0.2) { - let mut rng = rand::thread_rng(); + // NOTE: Used only for dynamic elements like chests and entities! + let mut dynamic_rng = rand::thread_rng(); let furniture = match self.noise.get(Vec3::new( center_offset.x, center_offset.y, @@ -545,7 +544,7 @@ impl Archetype for House { 2 => SpriteKind::ChairDouble, 3 => SpriteKind::CoatRack, 4 => { - if rng.gen_range(0, 8) == 0 { + if dynamic_rng.gen_range(0, 8) == 0 { SpriteKind::Chest } else { SpriteKind::Crate @@ -558,12 +557,12 @@ impl Archetype for House { _ => SpriteKind::Pot, }; - return Some(BlockMask::new( + return BlockMask::new( Block::air(furniture).with_ori(edge_ori).unwrap(), internal_layer, - )); + ); } else { - return Some(internal); + return internal; } } @@ -587,38 +586,30 @@ impl Archetype for House { _ => SpriteKind::DungeonWallDecor, }; - Some(BlockMask::new( + BlockMask::new( Block::air(ornament).with_ori((edge_ori + 4) % 8).unwrap(), internal_layer, - )) + ) } else { - None + EMPTY } }; - let mut cblock = empty; - - if let Some(block) = - do_roof_wall(profile, width, dist, bound_offset, roof_top, attr.mansard) - { - cblock = cblock.resolve_with(block); - } + let mut cblock = do_roof_wall(profile, width, dist, bound_offset, roof_top, attr.mansard); if let Pillar::Tower(tower_height) = attr.pillar { let tower_top = roof_top + tower_height; 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( + cblock = cblock.resolve_with(do_roof_wall( profile, 4, dist, center_offset.map(|e| e.abs()), tower_top, attr.mansard, - ) { - cblock = cblock.resolve_with(block); - } + )); } cblock diff --git a/world/src/site/settlement/building/archetype/keep.rs b/world/src/site/settlement/building/archetype/keep.rs index 1d58501d63..99f9a31977 100644 --- a/world/src/site/settlement/building/archetype/keep.rs +++ b/world/src/site/settlement/building/archetype/keep.rs @@ -7,7 +7,6 @@ use crate::{ use common::{ make_case_elim, terrain::{Block, BlockKind, SpriteKind}, - vol::Vox, }; use rand::prelude::*; use serde::Deserialize; @@ -168,8 +167,9 @@ impl Archetype for Keep { make_block(colors.pole.0, colors.pole.1, colors.pole.2).with_priority(important_layer); let flag = make_block(flag_color.0, flag_color.1, flag_color.2).with_priority(important_layer); - let internal = BlockMask::new(Block::empty(), internal_layer); - let empty = BlockMask::nothing(); + const AIR: Block = Block::air(SpriteKind::Empty); + const EMPTY: BlockMask = BlockMask::nothing(); + let internal = BlockMask::new(AIR, internal_layer); let make_staircase = move |pos: Vec3, radius: f32, inner_radius: f32, stretch: f32| { let stone = BlockMask::new(Block::new(BlockKind::Rock, dungeon_stone.into()), 5); @@ -187,7 +187,7 @@ impl Archetype for Keep { internal } } else { - BlockMask::nothing() + EMPTY } }; @@ -255,21 +255,21 @@ impl Archetype for Keep { { flag } else { - empty + EMPTY } } else if min_dist <= rampart_width { if profile.y < rampart_height { wall } else { - empty + EMPTY } } else { - empty + EMPTY } } else if profile.y < roof_height && min_dist < width { internal } else { - empty + EMPTY } .resolve_with( if attr.is_tower && profile.y > 0 && profile.y <= roof_height { @@ -280,7 +280,7 @@ impl Archetype for Keep { 9.0, ) } else { - BlockMask::nothing() + EMPTY }, ) } diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 509f36f95d..67f899adff 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -20,7 +20,7 @@ use common::{ spiral::Spiral2d, store::{Id, Store}, terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize}, - vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol}, + vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, WriteVol}, }; use fxhash::FxHasher64; use hashbrown::{HashMap, HashSet}; @@ -726,25 +726,26 @@ impl Settlement { for z in -8 - diff..4 + diff { let pos = Vec3::new(offs.x, offs.y, surface_z + z); - let block = vol.get(pos).ok().copied().unwrap_or_else(Block::empty); - - if block.is_empty() { + let block = if let Ok(&block) = vol.get(pos) { + // TODO: Figure out whether extra filters are needed. + block + } else { break; - } + }; if let (0, Some(sprite)) = (z, surface_sprite) { let _ = vol.set( pos, + // TODO: Make more principled. if block.is_fluid() { - block + block.with_sprite(sprite) } else { - Block::empty() - } - .with_sprite(sprite), + Block::air(sprite) + }, ); } else if z >= 0 { if block.kind() != BlockKind::Water { - let _ = vol.set(pos, Block::empty()); + let _ = vol.set(pos, Block::air(SpriteKind::Empty)); } } else { let _ = vol.set( @@ -835,7 +836,8 @@ impl Settlement { #[allow(clippy::eval_order_dependence)] // TODO: Pending review in #587 pub fn apply_supplement<'a>( &'a self, - rng: &mut impl Rng, + // NOTE: Used only for dynamic elements like chests and entities! + dynamic_rng: &mut impl Rng, wpos2d: Vec2, mut get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, supplement: &mut ChunkSupplement, @@ -865,24 +867,25 @@ impl Settlement { let is_dummy = RandomField::new(self.seed + 1).chance(Vec3::from(wpos2d), 1.0 / 15.0); let entity = EntityInfo::at(entity_wpos) - .with_body(match rng.gen_range(0, 4) { + .with_body(match dynamic_rng.gen_range(0, 4) { _ if is_dummy => { is_human = false; object::Body::TrainingDummy.into() }, 0 => { - let species = match rng.gen_range(0, 3) { + let species = match dynamic_rng.gen_range(0, 3) { 0 => quadruped_small::Species::Pig, 1 => quadruped_small::Species::Sheep, _ => quadruped_small::Species::Cat, }; is_human = false; comp::Body::QuadrupedSmall(quadruped_small::Body::random_with( - rng, &species, + dynamic_rng, + &species, )) }, 1 => { - let species = match rng.gen_range(0, 4) { + let species = match dynamic_rng.gen_range(0, 4) { 0 => bird_medium::Species::Duck, 1 => bird_medium::Species::Chicken, 2 => bird_medium::Species::Goose, @@ -890,7 +893,8 @@ impl Settlement { }; is_human = false; comp::Body::BirdMedium(bird_medium::Body::random_with( - rng, &species, + dynamic_rng, + &species, )) }, _ => { @@ -906,9 +910,9 @@ impl Settlement { } else { comp::Alignment::Tame }) - .do_if(is_human && rng.gen(), |entity| { + .do_if(is_human && dynamic_rng.gen(), |entity| { entity.with_main_tool(Item::new_from_asset_expect( - match rng.gen_range(0, 7) { + match dynamic_rng.gen_range(0, 7) { 0 => "common.items.npc_weapons.tool.broom", 1 => "common.items.npc_weapons.tool.hoe", 2 => "common.items.npc_weapons.tool.pickaxe",