diff --git a/common/Cargo.toml b/common/Cargo.toml index fe945f7621..6217569469 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -34,6 +34,8 @@ sum_type = "0.2.0" authc = { git = "https://gitlab.com/veloren/auth.git", rev = "b943c85e4a38f5ec60cd18c34c73097640162bfe" } slab = "0.4.2" enum-iterator = "0.6" +num-traits = "0.2" +num-derive = "0.3" # Tracy tracy-client = { version = "0.8.0", optional = true } diff --git a/common/src/comp/inventory/item/mod.rs b/common/src/comp/inventory/item/mod.rs index 5bf5f8cdba..a3e912af18 100644 --- a/common/src/comp/inventory/item/mod.rs +++ b/common/src/comp/inventory/item/mod.rs @@ -8,7 +8,7 @@ use crate::{ assets::{self, Asset, Error}, effect::Effect, lottery::Lottery, - terrain::{Block, BlockKind}, + terrain::{Block, SpriteKind}, }; use crossbeam::atomic::AtomicCell; use rand::prelude::*; @@ -277,23 +277,23 @@ impl Item { pub fn try_reclaim_from_block(block: Block) -> Option { let chosen; let mut rng = rand::thread_rng(); - Some(Item::new_from_asset_expect(match block.kind() { - BlockKind::Apple => "common.items.food.apple", - BlockKind::Mushroom => "common.items.food.mushroom", - BlockKind::Velorite => "common.items.ore.velorite", - BlockKind::VeloriteFrag => "common.items.ore.veloritefrag", - BlockKind::BlueFlower => "common.items.flowers.blue", - BlockKind::PinkFlower => "common.items.flowers.pink", - BlockKind::PurpleFlower => "common.items.flowers.purple", - BlockKind::RedFlower => "common.items.flowers.red", - BlockKind::WhiteFlower => "common.items.flowers.white", - BlockKind::YellowFlower => "common.items.flowers.yellow", - BlockKind::Sunflower => "common.items.flowers.sun", - BlockKind::LongGrass => "common.items.grasses.long", - BlockKind::MediumGrass => "common.items.grasses.medium", - BlockKind::ShortGrass => "common.items.grasses.short", - BlockKind::Coconut => "common.items.food.coconut", - BlockKind::Chest => { + Some(Item::new_from_asset_expect(match block.get_sprite()? { + SpriteKind::Apple => "common.items.food.apple", + SpriteKind::Mushroom => "common.items.food.mushroom", + SpriteKind::Velorite => "common.items.ore.velorite", + SpriteKind::VeloriteFrag => "common.items.ore.veloritefrag", + SpriteKind::BlueFlower => "common.items.flowers.blue", + SpriteKind::PinkFlower => "common.items.flowers.pink", + SpriteKind::PurpleFlower => "common.items.flowers.purple", + SpriteKind::RedFlower => "common.items.flowers.red", + SpriteKind::WhiteFlower => "common.items.flowers.white", + SpriteKind::YellowFlower => "common.items.flowers.yellow", + SpriteKind::Sunflower => "common.items.flowers.sun", + SpriteKind::LongGrass => "common.items.grasses.long", + SpriteKind::MediumGrass => "common.items.grasses.medium", + SpriteKind::ShortGrass => "common.items.grasses.short", + SpriteKind::Coconut => "common.items.food.coconut", + SpriteKind::Chest => { chosen = Lottery::::load_expect(match rng.gen_range(0, 7) { 0 => "common.loot_tables.loot_table_weapon_uncommon", 1 => "common.loot_tables.loot_table_weapon_common", @@ -304,13 +304,13 @@ impl Item { }); chosen.choose() }, - BlockKind::Crate => { + SpriteKind::Crate => { chosen = Lottery::::load_expect("common.loot_tables.loot_table_food"); chosen.choose() }, - BlockKind::Stones => "common.items.crafting_ing.stones", - BlockKind::Twigs => "common.items.crafting_ing.twigs", - BlockKind::ShinyGem => "common.items.crafting_ing.shiny_gem", + SpriteKind::Stones => "common.items.crafting_ing.stones", + SpriteKind::Twigs => "common.items.crafting_ing.twigs", + SpriteKind::ShinyGem => "common.items.crafting_ing.shiny_gem", _ => return None, })) } diff --git a/common/src/path.rs b/common/src/path.rs index f065f5f2db..f5840acf7a 100644 --- a/common/src/path.rs +++ b/common/src/path.rs @@ -434,7 +434,7 @@ where V: BaseVol + ReadVol, { vol.get(pos - Vec3::new(0, 0, 1)) - .map(|b| b.is_solid() && b.get_height() == 1.0) + .map(|b| b.is_solid() && b.solid_height() == 1.0) .unwrap_or(false) && vol .get(pos + Vec3::new(0, 0, 0)) diff --git a/common/src/sys/phys.rs b/common/src/sys/phys.rs index 15b3bb0c54..bfc70a8e98 100644 --- a/common/src/sys/phys.rs +++ b/common/src/sys/phys.rs @@ -8,7 +8,7 @@ use crate::{ span, state::DeltaTime, sync::{Uid, UidAllocator}, - terrain::{Block, BlockKind, TerrainGrid}, + terrain::{Block, TerrainGrid}, vol::ReadVol, }; use rayon::iter::ParallelIterator; @@ -341,7 +341,7 @@ impl<'a> System<'a> for Sys { let near_iter = (-hdist..hdist + 1) .map(move |i| { (-hdist..hdist + 1).map(move |j| { - (1 - BlockKind::MAX_HEIGHT.ceil() as i32 + z_min.floor() as i32 + (1 - Block::MAX_HEIGHT.ceil() as i32 + z_min.floor() as i32 ..z_max.ceil() as i32 + 1) .map(move |k| (i, j, k)) }) @@ -370,7 +370,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.get_height()), + + Vec3::new(1.0, 1.0, block.solid_height()), }; if player_aabb.collides_with_aabb(block_aabb) { @@ -442,9 +442,9 @@ impl<'a> System<'a> for Sys { block_pos, Aabb { min: block_pos.map(|e| e as f32), - max: block_pos.map(|e| e as f32) + Vec3::new(1.0, 1.0, block.get_height()), + max: block_pos.map(|e| e as f32) + Vec3::new(1.0, 1.0, block.solid_height()), }, - block.get_height(), + block.solid_height(), )) } else { None @@ -557,7 +557,7 @@ impl<'a> System<'a> for Sys { &terrain, &|block| { block.is_solid() - && block.get_height() >= (pos.0.z - 0.05).rem_euclid(1.0) + && block.solid_height() >= (pos.0.z - 0.05).rem_euclid(1.0) }, near_iter.clone(), radius, @@ -571,7 +571,7 @@ impl<'a> System<'a> for Sys { ) .ok() .filter(|block| block.is_solid()) - .map(|block| block.get_height()) + .map(|block| block.solid_height()) .unwrap_or(0.0); pos.0.z = (pos.0.z - 0.05).floor() + snap_height; physics_state.on_ground = true; @@ -609,7 +609,7 @@ impl<'a> System<'a> for Sys { physics_state.in_fluid = collision_iter( pos.0, &terrain, - &|block| block.is_fluid(), + &|block| block.is_liquid(), near_iter.clone(), radius, z_min..z_max, diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index 60674b0f95..3046aab360 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -1,103 +1,74 @@ +use super::SpriteKind; use crate::{make_case_elim, vol::Vox}; use enum_iterator::IntoEnumIterator; use lazy_static::lazy_static; +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, convert::TryFrom, fmt, ops::Deref}; use vek::*; make_case_elim!( block_kind, - #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize, IntoEnumIterator)] + #[derive( + Copy, + Clone, + Debug, + Hash, + Eq, + PartialEq, + Serialize, + Deserialize, + IntoEnumIterator, + FromPrimitive, + )] #[repr(u8)] pub enum BlockKind { - Air = 0x00, - Normal = 0x01, - Dense = 0x02, - Rock = 0x03, - Grass = 0x04, - Leaves = 0x05, - Water = 0x06, - LargeCactus = 0x07, - BarrelCactus = 0x08, - RoundCactus = 0x09, - ShortCactus = 0x0A, - MedFlatCactus = 0x0B, - ShortFlatCactus = 0x0C, - BlueFlower = 0x0D, - PinkFlower = 0x0E, - PurpleFlower = 0x0F, - RedFlower = 0x10, - WhiteFlower = 0x11, - YellowFlower = 0x12, - Sunflower = 0x13, - LongGrass = 0x14, - MediumGrass = 0x15, - ShortGrass = 0x16, - Apple = 0x17, - Mushroom = 0x18, - Liana = 0x19, - Velorite = 0x1A, - VeloriteFrag = 0x1B, - Chest = 0x1C, - Pumpkin = 0x1D, - Welwitch = 0x1E, - LingonBerry = 0x1F, - LeafyPlant = 0x20, - Fern = 0x21, - DeadBush = 0x22, - Blueberry = 0x23, - Ember = 0x24, - Corn = 0x25, - WheatYellow = 0x26, - WheatGreen = 0x27, - Cabbage = 0x28, - Flax = 0x29, - Carrot = 0x2A, - Tomato = 0x2B, - Radish = 0x2C, - Coconut = 0x2D, - Turnip = 0x2E, - Window1 = 0x2F, - Window2 = 0x30, - Window3 = 0x31, - Window4 = 0x32, - Scarecrow = 0x33, - StreetLamp = 0x34, - StreetLampTall = 0x35, - Door = 0x36, - Bed = 0x37, - Bench = 0x38, - ChairSingle = 0x39, - ChairDouble = 0x3A, - CoatRack = 0x3B, - Crate = 0x3C, - DrawerLarge = 0x3D, - DrawerMedium = 0x3E, - DrawerSmall = 0x3F, - DungeonWallDecor = 0x40, - HangingBasket = 0x41, - HangingSign = 0x42, - WallLamp = 0x43, - Planter = 0x44, - Shelf = 0x45, - TableSide = 0x46, - TableDining = 0x47, - TableDouble = 0x48, - WardrobeSingle = 0x49, - WardrobeDouble = 0x4A, - LargeGrass = 0x4B, - Pot = 0x4C, - Stones = 0x4D, - Twigs = 0x4E, - ShinyGem = 0x4F, - DropGate = 0x50, - DropGateBottom = 0x51, - GrassSnow = 0x52, - Reed = 0x53, - Beehive = 0x54, + Air = 0x00, // Air counts as a fluid + Water = 0x01, + // 0x02 <= x < 0x10 are reserved for other fluids. These are 2^n aligned to allow bitwise + // checking of common conditions. For example, `is_fluid` is just `block_kind & + // 0x0F == 0` (this is a very common operation used in meshing that could do with + // being *very* fast). + Rock = 0x10, + WeakRock = 0x11, // Explodable + // 0x12 <= x < 0x20 is reserved for future rocks + Grass = 0x20, // Note: *not* the same as grass sprites + // 0x21 <= x < 0x30 is reserved for future grasses + Earth = 0x30, + Sand = 0x31, + // 0x32 <= x < 0x40 is reserved for future earths/muds/gravels/sands/etc. + Wood = 0x40, + Leaves = 0x41, + // 0x42 <= x < 0x50 is reserved for future tree parts + + // Covers all other cases (we sometimes have bizarrely coloured misc blocks, and also we + // often want to experiment with new kinds of block without allocating them a + // dedicated block kind. + Misc = 0xFF, } ); +impl BlockKind { + pub const fn is_air(&self) -> bool { + match self { + BlockKind::Air => true, + _ => false, + } + } + + pub const fn is_fluid(&self) -> bool { *self as u8 & 0xF0 == 0x00 } + + pub const fn is_liquid(&self) -> bool { + match self { + BlockKind::Water => true, + _ => false, + } + } + + pub const fn is_filled(&self) -> bool { !self.is_fluid() } +} + impl fmt::Display for BlockKind { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) } } @@ -114,408 +85,11 @@ impl<'a> TryFrom<&'a str> for BlockKind { fn try_from(s: &'a str) -> Result { BLOCK_KINDS.get(s).copied().ok_or(()) } } -impl BlockKind { - pub const MAX_HEIGHT: f32 = 3.0; - - pub fn is_tangible(&self) -> bool { - match self { - BlockKind::Air => false, - kind => !kind.is_fluid(), - } - } - - pub fn is_air(&self) -> bool { - match self { - BlockKind::Air => true, - BlockKind::LargeCactus => true, - BlockKind::BarrelCactus => true, - BlockKind::RoundCactus => true, - BlockKind::ShortCactus => true, - BlockKind::MedFlatCactus => true, - BlockKind::ShortFlatCactus => true, - BlockKind::BlueFlower => true, - BlockKind::PinkFlower => true, - BlockKind::PurpleFlower => true, - BlockKind::RedFlower => true, - BlockKind::WhiteFlower => true, - BlockKind::YellowFlower => true, - BlockKind::Sunflower => true, - BlockKind::LongGrass => true, - BlockKind::MediumGrass => true, - BlockKind::ShortGrass => true, - BlockKind::Apple => true, - BlockKind::Mushroom => true, - BlockKind::Liana => true, - BlockKind::Velorite => true, - BlockKind::VeloriteFrag => true, - BlockKind::Chest => true, - BlockKind::Welwitch => true, - BlockKind::LingonBerry => true, - BlockKind::LeafyPlant => true, - BlockKind::Fern => true, - BlockKind::DeadBush => true, - BlockKind::Blueberry => true, - BlockKind::Ember => true, - BlockKind::Corn => true, - BlockKind::WheatYellow => true, - BlockKind::WheatGreen => true, - BlockKind::Cabbage => false, - BlockKind::Pumpkin => false, - BlockKind::Flax => true, - BlockKind::Carrot => true, - BlockKind::Tomato => false, - BlockKind::Radish => true, - BlockKind::Turnip => true, - BlockKind::Coconut => true, - BlockKind::Window1 => true, - BlockKind::Window2 => true, - BlockKind::Window3 => true, - BlockKind::Window4 => true, - BlockKind::Scarecrow => true, - BlockKind::StreetLamp => true, - BlockKind::StreetLampTall => true, - BlockKind::Door => false, - BlockKind::Bed => false, - BlockKind::Bench => false, - BlockKind::ChairSingle => false, - BlockKind::ChairDouble => false, - BlockKind::CoatRack => false, - BlockKind::Crate => false, - BlockKind::DrawerLarge => false, - BlockKind::DrawerMedium => false, - BlockKind::DrawerSmall => false, - BlockKind::DungeonWallDecor => false, - BlockKind::HangingBasket => true, - BlockKind::HangingSign => true, - BlockKind::WallLamp => true, - BlockKind::Planter => false, - BlockKind::Shelf => true, - BlockKind::TableSide => false, - BlockKind::TableDining => false, - BlockKind::TableDouble => false, - BlockKind::WardrobeSingle => false, - BlockKind::WardrobeDouble => false, - BlockKind::Pot => false, - BlockKind::Stones => true, - BlockKind::Twigs => true, - BlockKind::ShinyGem => true, - BlockKind::DropGate => false, - BlockKind::DropGateBottom => false, - BlockKind::GrassSnow => true, - BlockKind::Reed => true, - BlockKind::Beehive => true, - _ => false, - } - } - - pub fn is_fluid(&self) -> bool { matches!(self, BlockKind::Water) } - - pub fn get_glow(&self) -> Option { - // TODO: When we have proper volumetric lighting - // match self { - // BlockKind::StreetLamp | BlockKind::StreetLampTall => Some(20), - // BlockKind::Velorite | BlockKind::VeloriteFrag => Some(10), - // _ => None, - // } - None - } - - pub fn is_opaque(&self) -> bool { - match self { - BlockKind::Air => false, - BlockKind::Water => false, - BlockKind::LargeCactus => false, - BlockKind::BarrelCactus => false, - BlockKind::RoundCactus => false, - BlockKind::ShortCactus => false, - BlockKind::MedFlatCactus => false, - BlockKind::ShortFlatCactus => false, - BlockKind::BlueFlower => false, - BlockKind::PinkFlower => false, - BlockKind::PurpleFlower => false, - BlockKind::RedFlower => false, - BlockKind::WhiteFlower => false, - BlockKind::YellowFlower => false, - BlockKind::Sunflower => false, - BlockKind::LongGrass => false, - BlockKind::MediumGrass => false, - BlockKind::ShortGrass => false, - BlockKind::Apple => false, - BlockKind::Mushroom => false, - BlockKind::Liana => false, - BlockKind::Velorite => false, - BlockKind::VeloriteFrag => false, - BlockKind::Chest => false, - BlockKind::Pumpkin => false, - BlockKind::Welwitch => false, - BlockKind::LingonBerry => false, - BlockKind::LeafyPlant => false, - BlockKind::Fern => false, - BlockKind::DeadBush => false, - BlockKind::Blueberry => false, - BlockKind::Ember => false, - BlockKind::Corn => false, - BlockKind::WheatYellow => false, - BlockKind::WheatGreen => false, - BlockKind::Cabbage => false, - BlockKind::Flax => false, - BlockKind::Carrot => false, - BlockKind::Tomato => false, - BlockKind::Radish => false, - BlockKind::Turnip => false, - BlockKind::Coconut => false, - BlockKind::Window1 => false, - BlockKind::Window2 => false, - BlockKind::Window3 => false, - BlockKind::Window4 => false, - BlockKind::Scarecrow => false, - BlockKind::StreetLamp => false, - BlockKind::StreetLampTall => false, - BlockKind::Door => false, - BlockKind::Bed => false, - BlockKind::Bench => false, - BlockKind::ChairSingle => false, - BlockKind::ChairDouble => false, - BlockKind::CoatRack => false, - BlockKind::Crate => false, - BlockKind::DrawerLarge => false, - BlockKind::DrawerMedium => false, - BlockKind::DrawerSmall => false, - BlockKind::DungeonWallDecor => false, - BlockKind::HangingBasket => false, - BlockKind::HangingSign => false, - BlockKind::WallLamp => false, - BlockKind::Planter => false, - BlockKind::Shelf => false, - BlockKind::TableSide => false, - BlockKind::TableDining => false, - BlockKind::TableDouble => false, - BlockKind::WardrobeSingle => false, - BlockKind::WardrobeDouble => false, - BlockKind::LargeGrass => false, - BlockKind::Pot => false, - BlockKind::Stones => false, - BlockKind::Twigs => false, - BlockKind::ShinyGem => false, - BlockKind::DropGate => false, - BlockKind::DropGateBottom => false, - BlockKind::GrassSnow => false, - BlockKind::Reed => false, - BlockKind::Beehive => false, - _ => true, - } - } - - pub fn is_solid(&self) -> bool { - match self { - BlockKind::Air => false, - BlockKind::Water => false, - BlockKind::LargeCactus => true, - BlockKind::BarrelCactus => true, - BlockKind::RoundCactus => true, - BlockKind::ShortCactus => true, - BlockKind::MedFlatCactus => true, - BlockKind::ShortFlatCactus => true, - BlockKind::BlueFlower => false, - BlockKind::PinkFlower => false, - BlockKind::PurpleFlower => false, - BlockKind::RedFlower => false, - BlockKind::WhiteFlower => false, - BlockKind::YellowFlower => false, - BlockKind::Sunflower => false, - BlockKind::LongGrass => false, - BlockKind::MediumGrass => false, - BlockKind::ShortGrass => false, - BlockKind::Apple => true, - BlockKind::Mushroom => false, - BlockKind::Liana => false, - BlockKind::Chest => true, - BlockKind::Pumpkin => true, - BlockKind::Welwitch => false, - BlockKind::LingonBerry => false, - BlockKind::LeafyPlant => false, - BlockKind::Fern => false, - BlockKind::DeadBush => false, - BlockKind::Blueberry => false, - BlockKind::Ember => false, - BlockKind::Corn => false, - BlockKind::WheatYellow => false, - BlockKind::WheatGreen => false, - BlockKind::Cabbage => true, - BlockKind::Flax => false, - BlockKind::Carrot => true, - BlockKind::Tomato => true, - BlockKind::Radish => true, - BlockKind::Turnip => true, - BlockKind::Coconut => true, - BlockKind::Scarecrow => true, - BlockKind::StreetLamp => true, - BlockKind::StreetLampTall => true, - BlockKind::Door => false, - BlockKind::Bed => true, - BlockKind::Bench => true, - BlockKind::ChairSingle => true, - BlockKind::ChairDouble => true, - BlockKind::CoatRack => true, - BlockKind::Crate => true, - BlockKind::DrawerLarge => true, - BlockKind::DrawerMedium => true, - BlockKind::DrawerSmall => true, - BlockKind::DungeonWallDecor => true, - BlockKind::HangingBasket => false, - BlockKind::HangingSign => false, - BlockKind::WallLamp => false, - BlockKind::Planter => true, - BlockKind::Shelf => false, - BlockKind::TableSide => true, - BlockKind::TableDining => true, - BlockKind::TableDouble => true, - BlockKind::WardrobeSingle => true, - BlockKind::WardrobeDouble => true, - BlockKind::Pot => true, - BlockKind::Stones => false, - BlockKind::Twigs => false, - BlockKind::ShinyGem => false, - BlockKind::DropGate => true, - BlockKind::DropGateBottom => false, - BlockKind::GrassSnow => false, - BlockKind::Reed => false, - _ => true, - } - } - - pub fn is_explodable(&self) -> bool { - match self { - BlockKind::Leaves | BlockKind::Grass | BlockKind::Rock | BlockKind::GrassSnow => true, - BlockKind::Air => false, - bk => bk.is_air(), // Temporary catch for terrain sprites - } - } - - // TODO: Integrate this into `is_solid` by returning an `Option` - pub fn get_height(&self) -> f32 { - // Beware: the height *must* be <= `MAX_HEIGHT` or the collision system will not - // properly detect it! - match self { - BlockKind::Tomato => 1.65, - BlockKind::LargeCactus => 2.5, - BlockKind::Scarecrow => 3.0, - BlockKind::Turnip => 0.36, - BlockKind::Pumpkin => 0.81, - BlockKind::Cabbage => 0.45, - BlockKind::Chest => 1.09, - BlockKind::StreetLamp => 3.0, - BlockKind::Carrot => 0.18, - BlockKind::Radish => 0.18, - BlockKind::Door => 3.0, - BlockKind::Bed => 1.54, - BlockKind::Bench => 0.5, - BlockKind::ChairSingle => 0.5, - BlockKind::ChairDouble => 0.5, - BlockKind::CoatRack => 2.36, - BlockKind::Crate => 0.90, - BlockKind::DrawerSmall => 1.0, - BlockKind::DrawerMedium => 2.0, - BlockKind::DrawerLarge => 2.0, - BlockKind::DungeonWallDecor => 1.0, - BlockKind::Planter => 1.09, - BlockKind::TableSide => 1.27, - BlockKind::TableDining => 1.45, - BlockKind::TableDouble => 1.45, - BlockKind::WardrobeSingle => 3.0, - BlockKind::WardrobeDouble => 3.0, - BlockKind::Pot => 0.90, - _ => 1.0, - } - } - - pub fn is_collectible(&self) -> bool { - match self { - BlockKind::BlueFlower => false, - BlockKind::PinkFlower => false, - BlockKind::PurpleFlower => false, - BlockKind::RedFlower => false, - BlockKind::WhiteFlower => false, - BlockKind::YellowFlower => false, - BlockKind::Sunflower => false, - BlockKind::LongGrass => false, - BlockKind::MediumGrass => false, - BlockKind::ShortGrass => false, - BlockKind::Apple => true, - BlockKind::Mushroom => true, - BlockKind::Velorite => true, - BlockKind::VeloriteFrag => true, - BlockKind::Chest => true, - BlockKind::Coconut => true, - BlockKind::Stones => true, - BlockKind::Twigs => true, - BlockKind::ShinyGem => true, - BlockKind::Crate => true, - _ => false, - } - } -} - #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] #[repr(packed)] pub struct Block { kind: BlockKind, - color: [u8; 3], -} - -impl Block { - pub const fn new(kind: BlockKind, color: Rgb) -> Self { - Self { - kind, - color: [color.r, color.g, color.b], - } - } - - pub fn get_color(&self) -> Option> { - if !self.is_air() { - Some(self.color.into()) - } else { - None - } - } - - pub fn get_ori(&self) -> Option { - match self.kind { - BlockKind::Window1 - | BlockKind::Window2 - | BlockKind::Window3 - | BlockKind::Window4 - | BlockKind::Bed - | BlockKind::Bench - | BlockKind::ChairSingle - | BlockKind::ChairDouble - | BlockKind::CoatRack - | BlockKind::Crate - | BlockKind::DrawerLarge - | BlockKind::DrawerMedium - | BlockKind::DrawerSmall - | BlockKind::DungeonWallDecor - | BlockKind::HangingBasket - | BlockKind::HangingSign - | BlockKind::WallLamp - | BlockKind::Planter - | BlockKind::Shelf - | BlockKind::TableSide - | BlockKind::TableDining - | BlockKind::TableDouble - | BlockKind::WardrobeSingle - | BlockKind::WardrobeDouble - | BlockKind::Pot - | BlockKind::Chest - | BlockKind::DropGate - | BlockKind::DropGateBottom - | BlockKind::Door - | BlockKind::Beehive => Some(self.color[0] & 0b111), - _ => None, - } - } - - pub fn kind(&self) -> BlockKind { self.kind } + attr: [u8; 3], } impl Deref for Block { @@ -528,11 +102,119 @@ impl Vox for Block { fn empty() -> Self { Self { kind: BlockKind::Air, - color: [0; 3], + attr: [0; 3], } } - fn is_empty(&self) -> bool { self.is_air() } + fn is_empty(&self) -> bool { *self == Block::empty() } +} + +impl Block { + pub const MAX_HEIGHT: f32 = 3.0; + + pub const fn new(kind: BlockKind, color: Rgb) -> Self { + Self { + kind, + // Colours are only valid for non-fluids + attr: if kind.is_filled() { + [color.r, color.g, color.b] + } else { + [0; 3] + }, + } + } + + pub fn get_color(&self) -> Option> { + if self.is_filled() { + Some(self.attr.into()) + } else { + None + } + } + + pub fn get_sprite(&self) -> Option { + if !self.is_filled() { + SpriteKind::from_u8(self.attr[0]) + } else { + None + } + } + + pub fn get_ori(&self) -> Option { + if self.get_sprite()?.has_ori() { + // TODO: Formalise this a bit better + Some(self.attr[1] & 0b111) + } else { + None + } + } + + pub fn get_glow(&self) -> Option { + // TODO: When we have proper volumetric lighting + // match self.get_sprite()? { + // SpriteKind::StreetLamp | SpriteKind::StreetLampTall => Some(20), + // SpriteKind::Velorite | SpriteKind::VeloriteFrag => Some(10), + // _ => None, + // } + None + } + + pub fn is_solid(&self) -> bool { + self.get_sprite() + .map(|s| s.solid_height().is_some()) + .unwrap_or(true) + } + + pub fn is_explodable(&self) -> bool { + match self.kind() { + BlockKind::Leaves | BlockKind::Grass | BlockKind::WeakRock => true, + // Explodable means that the terrain sprite will get removed anyway, so is good for + // empty fluids TODO: Handle the case of terrain sprites we don't want to + // have explode + _ => true, + } + } + + pub fn is_collectible(&self) -> bool { + self.get_sprite() + .map(|s| s.is_collectible()) + .unwrap_or(false) + } + + pub fn is_opaque(&self) -> bool { self.kind().is_filled() } + + pub fn solid_height(&self) -> f32 { + self.get_sprite() + .map(|s| s.solid_height().unwrap_or(0.0)) + .unwrap_or(1.0) + } + + pub fn kind(&self) -> BlockKind { self.kind } + + /// If this block is a fluid, replace its sprite. + pub fn with_sprite(mut self, sprite: SpriteKind) -> Self { + if !self.is_filled() { + self.attr[0] = sprite as u8; + } + self + } + + /// If this block can have orientation, replace its sprite. + pub fn with_ori(mut self, ori: u8) -> Self { + if self.get_sprite().map(|s| s.has_ori()).unwrap_or(false) { + self.attr[1] = (self.attr[1] & !0b111) | (ori & 0b111); + } + self + } + + /// Remove the terrain sprite or solid aspects of a block + pub fn into_vacant(mut self) -> Self { + if self.is_fluid() { + Block::new(self.kind(), Rgb::zero()) + } else { + Block::empty() + } + } } #[cfg(test)] diff --git a/common/src/terrain/mod.rs b/common/src/terrain/mod.rs index ada6b7ab85..899eaa016c 100644 --- a/common/src/terrain/mod.rs +++ b/common/src/terrain/mod.rs @@ -2,6 +2,7 @@ pub mod biome; pub mod block; pub mod chonk; pub mod map; +pub mod sprite; pub mod structure; // Reexports @@ -9,6 +10,7 @@ pub use self::{ biome::BiomeKind, block::{Block, BlockKind}, map::MapSizeLg, + sprite::SpriteKind, structure::Structure, }; use roots::find_roots_cubic; diff --git a/common/src/terrain/sprite.rs b/common/src/terrain/sprite.rs new file mode 100644 index 0000000000..95c96eb777 --- /dev/null +++ b/common/src/terrain/sprite.rs @@ -0,0 +1,223 @@ +use crate::make_case_elim; +use enum_iterator::IntoEnumIterator; +use lazy_static::lazy_static; +use num_derive::FromPrimitive; +use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, convert::TryFrom, fmt}; + +make_case_elim!( + sprite_kind, + #[derive( + Copy, + Clone, + Debug, + Hash, + Eq, + PartialEq, + Serialize, + Deserialize, + IntoEnumIterator, + FromPrimitive, + )] + #[repr(u8)] + pub enum SpriteKind { + // Note that the values of these should be linearly contiguous to allow for quick + // bounds-checking when casting to a u8. + Empty = 0x00, + BarrelCactus = 0x01, + RoundCactus = 0x02, + ShortCactus = 0x03, + MedFlatCactus = 0x04, + ShortFlatCactus = 0x05, + BlueFlower = 0x06, + PinkFlower = 0x07, + PurpleFlower = 0x08, + RedFlower = 0x09, + WhiteFlower = 0x0A, + YellowFlower = 0x0B, + Sunflower = 0x0C, + LongGrass = 0x0D, + MediumGrass = 0x0E, + ShortGrass = 0x0F, + Apple = 0x10, + Mushroom = 0x11, + Liana = 0x12, + Velorite = 0x13, + VeloriteFrag = 0x14, + Chest = 0x15, + Pumpkin = 0x16, + Welwitch = 0x17, + LingonBerry = 0x18, + LeafyPlant = 0x19, + Fern = 0x1A, + DeadBush = 0x1B, + Blueberry = 0x1C, + Ember = 0x1D, + Corn = 0x1E, + WheatYellow = 0x1F, + WheatGreen = 0x20, + Cabbage = 0x21, + Flax = 0x22, + Carrot = 0x23, + Tomato = 0x24, + Radish = 0x25, + Coconut = 0x26, + Turnip = 0x27, + Window1 = 0x28, + Window2 = 0x29, + Window3 = 0x2A, + Window4 = 0x2B, + Scarecrow = 0x2C, + StreetLamp = 0x2D, + StreetLampTall = 0x2E, + Door = 0x2F, + Bed = 0x30, + Bench = 0x31, + ChairSingle = 0x32, + ChairDouble = 0x33, + CoatRack = 0x34, + Crate = 0x35, + DrawerLarge = 0x36, + DrawerMedium = 0x37, + DrawerSmall = 0x38, + DungeonWallDecor = 0x39, + HangingBasket = 0x3A, + HangingSign = 0x3B, + WallLamp = 0x3C, + Planter = 0x3D, + Shelf = 0x3E, + TableSide = 0x3F, + TableDining = 0x40, + TableDouble = 0x41, + WardrobeSingle = 0x42, + WardrobeDouble = 0x43, + LargeGrass = 0x44, + Pot = 0x45, + Stones = 0x46, + Twigs = 0x47, + ShinyGem = 0x48, + DropGate = 0x49, + DropGateBottom = 0x4A, + GrassSnow = 0x4B, + Reed = 0x4C, + Beehive = 0x4D, + LargeCactus = 0x4E, + } +); + +impl SpriteKind { + pub fn solid_height(&self) -> Option { + // Beware: the height *must* be <= `MAX_HEIGHT` or the collision system will not + // properly detect it! + Some(match self { + SpriteKind::Tomato => 1.65, + SpriteKind::LargeCactus => 2.5, + SpriteKind::Scarecrow => 3.0, + SpriteKind::Turnip => 0.36, + SpriteKind::Pumpkin => 0.81, + SpriteKind::Cabbage => 0.45, + SpriteKind::Chest => 1.09, + SpriteKind::StreetLamp => 3.0, + SpriteKind::Carrot => 0.18, + SpriteKind::Radish => 0.18, + // TODO: Uncomment this when we have a way to open doors + // SpriteKind::Door => 3.0, + SpriteKind::Bed => 1.54, + SpriteKind::Bench => 0.5, + SpriteKind::ChairSingle => 0.5, + SpriteKind::ChairDouble => 0.5, + SpriteKind::CoatRack => 2.36, + SpriteKind::Crate => 0.90, + SpriteKind::DrawerSmall => 1.0, + SpriteKind::DrawerMedium => 2.0, + SpriteKind::DrawerLarge => 2.0, + SpriteKind::DungeonWallDecor => 1.0, + SpriteKind::Planter => 1.09, + SpriteKind::TableSide => 1.27, + SpriteKind::TableDining => 1.45, + SpriteKind::TableDouble => 1.45, + SpriteKind::WardrobeSingle => 3.0, + SpriteKind::WardrobeDouble => 3.0, + SpriteKind::Pot => 0.90, + _ => return None, + }) + } + + pub fn is_collectible(&self) -> bool { + match self { + SpriteKind::BlueFlower => false, + SpriteKind::PinkFlower => false, + SpriteKind::PurpleFlower => false, + SpriteKind::RedFlower => false, + SpriteKind::WhiteFlower => false, + SpriteKind::YellowFlower => false, + SpriteKind::Sunflower => false, + SpriteKind::LongGrass => false, + SpriteKind::MediumGrass => false, + SpriteKind::ShortGrass => false, + SpriteKind::Apple => true, + SpriteKind::Mushroom => true, + SpriteKind::Velorite => true, + SpriteKind::VeloriteFrag => true, + SpriteKind::Chest => true, + SpriteKind::Coconut => true, + SpriteKind::Stones => true, + SpriteKind::Twigs => true, + SpriteKind::ShinyGem => true, + SpriteKind::Crate => true, + _ => false, + } + } + + pub fn has_ori(&self) -> bool { + match self { + SpriteKind::Window1 + | SpriteKind::Window2 + | SpriteKind::Window3 + | SpriteKind::Window4 + | SpriteKind::Bed + | SpriteKind::Bench + | SpriteKind::ChairSingle + | SpriteKind::ChairDouble + | SpriteKind::CoatRack + | SpriteKind::Crate + | SpriteKind::DrawerLarge + | SpriteKind::DrawerMedium + | SpriteKind::DrawerSmall + | SpriteKind::DungeonWallDecor + | SpriteKind::HangingBasket + | SpriteKind::HangingSign + | SpriteKind::WallLamp + | SpriteKind::Planter + | SpriteKind::Shelf + | SpriteKind::TableSide + | SpriteKind::TableDining + | SpriteKind::TableDouble + | SpriteKind::WardrobeSingle + | SpriteKind::WardrobeDouble + | SpriteKind::Pot + | SpriteKind::Chest + | SpriteKind::DropGate + | SpriteKind::DropGateBottom + | SpriteKind::Door + | SpriteKind::Beehive => true, + _ => false, + } + } +} + +impl fmt::Display for SpriteKind { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) } +} + +lazy_static! { + pub static ref SPRITE_KINDS: HashMap = SpriteKind::into_enum_iter() + .map(|sk| (sk.to_string(), sk)) + .collect(); +} + +impl<'a> TryFrom<&'a str> for SpriteKind { + type Error = (); + + fn try_from(s: &'a str) -> Result { SPRITE_KINDS.get(s).copied().ok_or(()) } +} diff --git a/common/src/terrain/structure.rs b/common/src/terrain/structure.rs index 607510b262..f898d38ac2 100644 --- a/common/src/terrain/structure.rs +++ b/common/src/terrain/structure.rs @@ -148,14 +148,14 @@ impl Asset for Structure { center: Vec3::zero(), vol, empty: StructureBlock::empty(), - default_kind: BlockKind::Normal, + default_kind: BlockKind::Misc, }) } else { Ok(Self { center: Vec3::zero(), vol: Dyna::filled(Vec3::zero(), StructureBlock::empty(), ()), empty: StructureBlock::empty(), - default_kind: BlockKind::Normal, + default_kind: BlockKind::Misc, }) } } diff --git a/common/src/vol.rs b/common/src/vol.rs index 9f30174f71..b0ae7a6be2 100644 --- a/common/src/vol.rs +++ b/common/src/vol.rs @@ -131,6 +131,22 @@ pub trait WriteVol: BaseVol { /// Set the voxel at the provided position in the volume to the provided /// value. fn set(&mut self, pos: Vec3, vox: Self::Vox) -> Result<(), Self::Error>; + + /// Map a voxel to another using the provided function. + // TODO: Is `map` the right name? Implies a change in type. + fn map Self::Vox>( + &mut self, + pos: Vec3, + f: F, + ) -> Result<(), Self::Error> + where + Self: ReadVol, + Self::Vox: Clone, + { + // This is *deliberately* not using a get_mut since this might trigger a + // repr change of the underlying volume + self.set(pos, f(self.get(pos)?.clone())) + } } /// A volume (usually rather a reference to a volume) that is convertible into diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index ea1cdabb6b..ee91002177 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -569,7 +569,7 @@ pub fn handle_explosion( let terrain = ecs.read_resource::(); let _ = terrain .ray(pos, pos + dir * power) - .until(|block| block.is_fluid() || rand::random::() < 0.05) + .until(|block| block.is_liquid() || rand::random::() < 0.05) .for_each(|block: &Block, pos| { if block.is_explodable() { block_change.set(pos, Block::empty()); diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index cbc4a0082e..86fc9c73ed 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -132,7 +132,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv state.write_component(entity, event); if item_was_added { // we made sure earlier the block was not already modified this tick - state.set_block(pos, Block::empty()) + state.set_block(pos, block.into_vacant()) }; } } else { diff --git a/voxygen/src/mesh/terrain.rs b/voxygen/src/mesh/terrain.rs index d11dc9fe43..bc0c857f83 100644 --- a/voxygen/src/mesh/terrain.rs +++ b/voxygen/src/mesh/terrain.rs @@ -97,7 +97,7 @@ fn calc_light + ReadVol + Debug>( if vol .get(outer.min + pos) .ok() - .map_or(false, |b| b.is_air() || b.is_fluid()) + .map_or(false, |b| b.is_fluid()) { *dest = src - 1; // Can't propagate further @@ -127,14 +127,14 @@ fn calc_light + ReadVol + Debug>( // Down is special cased and we know up is a ray // Special cased ray propagation let pos = Vec3::new(pos.x, pos.y, pos.z - 1); - let (is_air, is_fluid) = vol_cached + let (is_air, is_liquid) = vol_cached .get(outer.min + pos) .ok() - .map_or((false, false), |b| (b.is_air(), b.is_fluid())); + .map_or((false, false), |b| (b.is_air(), b.is_liquid())); light_map[lm_idx(pos.x, pos.y, pos.z)] = if is_air { prop_que.push_back((pos.x as u8, pos.y as u8, pos.z as u16)); SUNLIGHT - } else if is_fluid { + } else if is_liquid { prop_que.push_back((pos.x as u8, pos.y as u8, pos.z as u16)); SUNLIGHT - 1 } else { @@ -268,7 +268,7 @@ impl<'a, V: RectRasterableVol + ReadVol + Debug> opaque_limits = opaque_limits .map(|l| l.including(z)) .or_else(|| Some(Limits::from_value(z))); - } else if block.is_fluid() { + } else if block.is_liquid() { fluid_limits = fluid_limits .map(|l| l.including(z)) .or_else(|| Some(Limits::from_value(z))); @@ -412,15 +412,15 @@ fn should_draw_greedy( let to = flat_get(pos); let from_opaque = from.is_opaque(); if from_opaque == to.is_opaque() { - // Check the interface of fluid and non-tangible non-fluids (e.g. air). - let from_fluid = from.is_fluid(); - if from_fluid == to.is_fluid() || from.is_tangible() || to.is_tangible() { + // Check the interface of liquid and non-tangible non-liquid (e.g. air). + let from_liquid = from.is_liquid(); + if from_liquid == to.is_liquid() || from.is_opaque() || to.is_opaque() { None } else { - // While fluid is not culled, we still try to keep a consistent orientation as - // we do for land; if going from fluid to non-fluid, + // While liquid is not culled, we still try to keep a consistent orientation as + // we do for land; if going from liquid to non-liquid, // forwards-facing; otherwise, backwards-facing. - Some((from_fluid, FaceKind::Fluid)) + Some((from_liquid, FaceKind::Fluid)) } } else { // If going from transparent to opaque, backward facing; otherwise, forward @@ -428,9 +428,9 @@ fn should_draw_greedy( Some(( from_opaque, FaceKind::Opaque(if from_opaque { - to.is_fluid() + to.is_liquid() } else { - from.is_fluid() + from.is_liquid() }), )) } diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs index 5cd4a26f1c..62d7282ea1 100644 --- a/voxygen/src/render/pipelines/mod.rs +++ b/voxygen/src/render/pipelines/mod.rs @@ -118,7 +118,7 @@ impl Globals { 0.0, 0.0, ], - medium: [if medium.is_fluid() { 1 } else { 0 }; 4], + medium: [if medium.is_liquid() { 1 } else { 0 }; 4], select_pos: select_pos .map(|sp| Vec4::from(sp) + Vec4::unit_w()) .unwrap_or(Vec4::zero()) diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index f838a4d8c8..22bfe683c2 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -17,7 +17,7 @@ use common::{ figure::Segment, span, spiral::Spiral2d, - terrain::{block, Block, BlockKind, TerrainChunk}, + terrain::{sprite, Block, SpriteKind, TerrainChunk}, vol::{BaseVol, ReadVol, RectRasterableVol, SampleVol, Vox}, volumes::vol_grid_2d::{VolGrid2d, VolGrid2dError}, }; @@ -49,7 +49,7 @@ pub struct TerrainChunkData { opaque_model: Model, fluid_model: Option>, col_lights: guillotiere::AllocId, - sprite_instances: HashMap<(BlockKind, usize), Instances>, + sprite_instances: HashMap<(SpriteKind, usize), Instances>, locals: Consts, pub blocks_of_interest: BlocksOfInterest, @@ -75,7 +75,7 @@ struct MeshWorkerResponse { opaque_mesh: Mesh, fluid_mesh: Mesh, col_lights_info: ColLightInfo, - sprite_instances: HashMap<(BlockKind, usize), Vec>, + sprite_instances: HashMap<(SpriteKind, usize), Vec>, started_tick: u64, blocks_of_interest: BlocksOfInterest, } @@ -94,7 +94,7 @@ struct SpriteModelConfig { #[derive(Deserialize)] /// Configuration data for a group of sprites (currently associated with a -/// particular BlockKind). +/// particular SpriteKind). struct SpriteConfig { /// All possible model variations for this sprite. // NOTE: Could make constant per sprite type, but eliminating this indirection and @@ -109,7 +109,7 @@ struct SpriteConfig { /// Configuration data for all sprite models. /// /// NOTE: Model is an asset path to the appropriate sprite .vox model. -type SpriteSpec = block::block_kind::PureCases>>; +type SpriteSpec = sprite::sprite_kind::PureCases>>; /// Function executed by worker threads dedicated to chunk meshing. #[allow(clippy::or_fun_call)] // TODO: Pending review in #587 @@ -122,7 +122,7 @@ fn mesh_worker + RectRasterableVol + ReadVol + Debug>( max_texture_size: u16, chunk: Arc, range: Aabb, - sprite_data: &HashMap<(BlockKind, usize), Vec>, + sprite_data: &HashMap<(SpriteKind, usize), Vec>, sprite_config: &SpriteSpec, ) -> MeshWorkerResponse { span!(_guard, "mesh_worker"); @@ -146,14 +146,19 @@ fn mesh_worker + RectRasterableVol + ReadVol + Debug>( 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 sprite = if let Some(sprite) = block.get_sprite() { + sprite + } else { + continue; + }; - if let Some(cfg) = block.kind().elim_case_pure(&sprite_config) { + if let Some(cfg) = sprite.elim_case_pure(&sprite_config) { let seed = wpos.x as u64 * 3 + wpos.y as u64 * 7 + wpos.x as u64 * wpos.y as u64; // Awful PRNG let ori = (block.get_ori().unwrap_or((seed % 4) as u8 * 2)) & 0b111; let variation = seed as usize % cfg.variations.len(); - let key = (block.kind(), variation); + let key = (sprite, variation); // NOTE: Safe because we called sprite_config_for already. // NOTE: Safe because 0 ≤ ori < 8 let sprite_data = &sprite_data[&key][0]; @@ -218,7 +223,7 @@ pub struct Terrain { mesh_todo: HashMap, ChunkMeshState>, // GPU data - sprite_data: Arc>>, + sprite_data: Arc>>, sprite_col_lights: Texture, col_lights: Texture, waves: Texture, @@ -254,7 +259,7 @@ impl Terrain { let sprite_config_ = &sprite_config; // NOTE: Tracks the start vertex of the next model to be meshed. - let sprite_data: HashMap<(BlockKind, usize), _> = BlockKind::into_enum_iter() + let sprite_data: HashMap<(SpriteKind, usize), _> = SpriteKind::into_enum_iter() .filter_map(|kind| Some((kind, kind.elim_case_pure(&sprite_config_).as_ref()?))) .flat_map(|(kind, sprite_config)| { let wind_sway = sprite_config.wind_sway; diff --git a/voxygen/src/scene/terrain/watcher.rs b/voxygen/src/scene/terrain/watcher.rs index 4e1fed21d9..b589e09742 100644 --- a/voxygen/src/scene/terrain/watcher.rs +++ b/voxygen/src/scene/terrain/watcher.rs @@ -1,6 +1,6 @@ use common::{ span, - terrain::{BlockKind, TerrainChunk}, + terrain::{BlockKind, SpriteKind, TerrainChunk}, vol::{IntoVolIterator, RectRasterableVol}, }; use rand::prelude::*; @@ -45,16 +45,18 @@ impl BlocksOfInterest { grass.push(pos) } }, - BlockKind::Ember => embers.push(pos), - BlockKind::Beehive => beehives.push(pos), - BlockKind::Reed => reeds.push(pos), - BlockKind::PinkFlower => flowers.push(pos), - BlockKind::PurpleFlower => flowers.push(pos), - BlockKind::RedFlower => flowers.push(pos), - BlockKind::WhiteFlower => flowers.push(pos), - BlockKind::YellowFlower => flowers.push(pos), - BlockKind::Sunflower => flowers.push(pos), - _ => {}, + _ => match block.get_sprite() { + Some(SpriteKind::Ember) => embers.push(pos), + Some(SpriteKind::Beehive) => beehives.push(pos), + Some(SpriteKind::Reed) => reeds.push(pos), + Some(SpriteKind::PinkFlower) => flowers.push(pos), + Some(SpriteKind::PurpleFlower) => flowers.push(pos), + Some(SpriteKind::RedFlower) => flowers.push(pos), + Some(SpriteKind::WhiteFlower) => flowers.push(pos), + Some(SpriteKind::YellowFlower) => flowers.push(pos), + Some(SpriteKind::Sunflower) => flowers.push(pos), + _ => {}, + }, }); Self { diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 729d7b4a54..29fa7ff3b6 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -86,7 +86,7 @@ impl SessionState { key_state: KeyState::default(), inputs: comp::ControllerInputs::default(), hud, - selected_block: Block::new(BlockKind::Normal, Rgb::broadcast(255)), + selected_block: Block::new(BlockKind::Misc, Rgb::broadcast(255)), voxygen_i18n, walk_forward_dir, walk_right_dir, @@ -1161,7 +1161,7 @@ fn under_cursor( let cam_ray = terrain .ray(cam_pos, cam_pos + cam_dir * 100.0) - .until(|block| block.is_tangible()) + .until(|block| block.is_filled() || block.is_collectible()) .cast(); let cam_dist = cam_ray.0; diff --git a/world/src/block/mod.rs b/world/src/block/mod.rs index a63db55b01..7d9873a4f4 100644 --- a/world/src/block/mod.rs +++ b/world/src/block/mod.rs @@ -8,7 +8,7 @@ use crate::{ use common::{ terrain::{ structure::{self, StructureBlock}, - Block, BlockKind, Structure, + Block, BlockKind, SpriteKind, Structure, }, vol::{ReadVol, Vox}, }; @@ -254,15 +254,19 @@ impl<'a> BlockGen<'a> { let grass_depth = (1.5 + 2.0 * chaos).min(height - basement_height); let block = if (wposf.z as f32) < height - grass_depth { + let stone_factor = (height - grass_depth - wposf.z as f32) * 0.15; let col = Lerp::lerp( sub_surface_color, stone_col.map(|e| e as f32 / 255.0), - (height - grass_depth - wposf.z as f32) * 0.15, + stone_factor, ) .map(|e| (e * 255.0) as u8); - // Underground - Some(Block::new(BlockKind::Normal, col)) + if stone_factor >= 0.5 { + Some(Block::new(BlockKind::Rock, col)) + } else { + Some(Block::new(BlockKind::Earth, col)) + } } else if (wposf.z as f32) < height { let grass_factor = (wposf.z as f32 - (height - grass_depth)) .div(grass_depth) @@ -273,81 +277,10 @@ impl<'a> BlockGen<'a> { if grass_factor > 0.7 { BlockKind::Grass } else { - BlockKind::Normal + BlockKind::Earth }, col.map(|e| (e * 255.0) as u8), )) - // } else if (wposf.z as f32) < height + 0.9 - // && temp < CONFIG.desert_temp - // && (wposf.z as f32 > water_height + 3.0) - // && marble > 0.6 - // && marble_small > 0.55 - // && (marble * 3173.7).fract() < 0.6 - // && humidity > CONFIG.desert_hum - // && false - // { - // let treasures = [BlockKind::Chest, BlockKind::Velorite]; - - // let flowers = [ - // BlockKind::BlueFlower, - // BlockKind::PinkFlower, - // BlockKind::PurpleFlower, - // BlockKind::RedFlower, - // BlockKind::WhiteFlower, - // BlockKind::YellowFlower, - // BlockKind::Sunflower, - // BlockKind::Mushroom, //TODO: Better spawnrules - // BlockKind::LeafyPlant, - // BlockKind::Blueberry, - // BlockKind::LingonBerry, - // BlockKind::Fern, - // /*BlockKind::Twigs, // TODO: Better spawnrules - // *BlockKind::Stones, // TODO: Better spawnrules - // *BlockKind::ShinyGem, // TODO: Better spawnrules */ - // ]; - // let grasses = [ - // BlockKind::LongGrass, - // BlockKind::MediumGrass, - // BlockKind::ShortGrass, - // ]; - - // Some(Block::new( - // if on_cliff && (height * 1271.0).fract() < 0.015 { - // treasures[(height * 731.3) as usize % - // treasures.len()] } else if (height * - // 1271.0).fract() < 0.1 { flowers[(height * - // 0.2) as usize % flowers.len()] } else { - // grasses[(height * 103.3) as usize % grasses.len()] - // }, - // Rgb::broadcast(0), - // )) - // } else if (wposf.z as f32) < height + 0.9 - // && temp > CONFIG.desert_temp - // && (marble * 4423.5).fract() < 0.0005 - // && false - // { - // let large_cacti = [ - // BlockKind::LargeCactus, - // BlockKind::MedFlatCactus, - // BlockKind::Welwitch, - // ]; - - // let small_cacti = [ - // BlockKind::BarrelCactus, - // BlockKind::RoundCactus, - // BlockKind::ShortCactus, - // BlockKind::ShortFlatCactus, - // BlockKind::DeadBush, - // ]; - - // Some(Block::new( - // if (height * 1271.0).fract() < 0.5 { - // large_cacti[(height * 0.2) as usize % - // large_cacti.len()] } else { - // small_cacti[(height * 0.3) as usize % - // small_cacti.len()] }, - // Rgb::broadcast(0), - // )) } else { None } @@ -360,7 +293,7 @@ impl<'a> BlockGen<'a> { let field2 = RandomField::new(world.seed + 2); Some(Block::new( - BlockKind::Rock, + BlockKind::WeakRock, stone_col.map2( Rgb::new( field0.get(wpos) as u8 % 16, @@ -510,7 +443,7 @@ impl StructureInfo { .reduce_max() { Some(Block::new( - BlockKind::Dense, + BlockKind::Rock, index.colors.block.pyramid.into(), )) } else { @@ -558,11 +491,11 @@ pub fn block_from_structure( StructureBlock::None => None, StructureBlock::Hollow => Some(Block::empty()), StructureBlock::Grass => Some(Block::new( - BlockKind::Normal, + BlockKind::Grass, sample.surface_color.map(|e| (e * 255.0) as u8), )), StructureBlock::Normal(color) => { - Some(Block::new(BlockKind::Normal, color)).filter(|block| !block.is_empty()) + 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())), @@ -573,23 +506,23 @@ pub fn block_from_structure( )), // None of these BlockKinds has an orientation, so we just use zero for the other color // bits. - StructureBlock::Liana => Some(Block::new(BlockKind::Liana, Rgb::zero())), + StructureBlock::Liana => Some(Block::empty().with_sprite(SpriteKind::Liana)), StructureBlock::Fruit => Some(if field.get(pos + structure_pos) % 24 == 0 { - Block::new(BlockKind::Beehive, Rgb::zero()) + Block::empty().with_sprite(SpriteKind::Beehive) } else if field.get(pos + structure_pos + 1) % 3 == 0 { - Block::new(BlockKind::Apple, Rgb::zero()) + Block::empty().with_sprite(SpriteKind::Apple) } else { Block::empty() }), StructureBlock::Coconut => Some(if field.get(pos + structure_pos) % 3 > 0 { Block::empty() } else { - Block::new(BlockKind::Coconut, Rgb::zero()) + Block::empty().with_sprite(SpriteKind::Coconut) }), StructureBlock::Chest => Some(if structure_seed % 10 < 7 { Block::empty() } else { - Block::new(BlockKind::Chest, Rgb::zero()) + Block::empty().with_sprite(SpriteKind::Chest) }), // We interpolate all these BlockKinds as needed. StructureBlock::TemperateLeaves diff --git a/world/src/layer/mod.rs b/world/src/layer/mod.rs index 1f56cb27b6..e132600faa 100644 --- a/world/src/layer/mod.rs +++ b/world/src/layer/mod.rs @@ -12,7 +12,7 @@ use common::{ comp, generation::{ChunkSupplement, EntityInfo}, lottery::Lottery, - terrain::{Block, BlockKind}, + terrain::{Block, BlockKind, SpriteKind}, vol::{BaseVol, ReadVol, RectSizedVol, Vox, WriteVol}, }; use noise::NoiseFn; @@ -98,14 +98,14 @@ pub fn apply_paths_to<'a>( Vec3::new(offs.x, offs.y, surface_z + z), if bridge_offset >= 2.0 && path_dist >= 3.0 || z < inset - 1 { Block::new( - BlockKind::Normal, + BlockKind::Rock, noisy_color(index.colors.layer.bridge.into(), 8), ) } else { let path_color = path.surface_color( col_sample.sub_surface_color.map(|e| (e * 255.0) as u8), ); - Block::new(BlockKind::Normal, noisy_color(path_color, 8)) + Block::new(BlockKind::Earth, noisy_color(path_color, 8)) }, ); } @@ -184,7 +184,7 @@ pub fn apply_caves_to<'a>( for z in cave_roof - stalagtites..cave_roof { let _ = vol.set( Vec3::new(offs.x, offs.y, z), - Block::new(BlockKind::Rock, index.colors.layer.stalagtite.into()), + Block::new(BlockKind::WeakRock, index.colors.layer.stalagtite.into()), ); } @@ -195,12 +195,11 @@ pub fn apply_caves_to<'a>( if RandomField::new(index.seed).chance(wpos2d.into(), 0.001 * difficulty.powf(1.5)) && cave_base < surface_z as i32 - 25 { - let kind = *Lottery::::load_expect("common.cave_scatter") + let kind = *Lottery::::load_expect("common.cave_scatter") .choose_seeded(RandomField::new(index.seed + 1).get(wpos2d.into())); - let _ = vol.set( - Vec3::new(offs.x, offs.y, cave_base), - Block::new(kind, Rgb::zero()), - ); + let _ = vol.map(Vec3::new(offs.x, offs.y, cave_base), |block| { + block.with_sprite(kind) + }); } } } diff --git a/world/src/layer/scatter.rs b/world/src/layer/scatter.rs index 55295b0af5..f93380ac70 100644 --- a/world/src/layer/scatter.rs +++ b/world/src/layer/scatter.rs @@ -1,6 +1,6 @@ use crate::{column::ColumnSample, sim::SimChunk, util::RandomField, IndexRef, CONFIG}; use common::{ - terrain::{Block, BlockKind}, + terrain::{Block, SpriteKind}, vol::{BaseVol, ReadVol, RectSizedVol, WriteVol}, }; use noise::NoiseFn; @@ -18,7 +18,7 @@ pub fn apply_scatter_to<'a>( index: IndexRef, chunk: &SimChunk, ) { - use BlockKind::*; + use SpriteKind::*; #[allow(clippy::type_complexity)] // TODO: Add back all sprites we had before let scatter: &[( @@ -282,6 +282,8 @@ pub fn apply_scatter_to<'a>( Some((128.0, 0.5)), ) }), + // Underwater chests + (Chest, true, |c, col| (MUSH_FACT * 0.25, None)), ]; for y in 0..vol.size_xy().y as i32 { @@ -299,10 +301,10 @@ pub fn apply_scatter_to<'a>( let underwater = col_sample.water_level > col_sample.alt; - let bk = scatter + let kind = scatter .iter() .enumerate() - .find_map(|(i, (bk, is_underwater, f))| { + .find_map(|(i, (kind, is_underwater, f))| { let (density, patch) = f(chunk, col_sample); let is_patch = patch .map(|(wavelen, threshold)| { @@ -324,13 +326,13 @@ pub fn apply_scatter_to<'a>( .chance(Vec3::new(wpos2d.x, wpos2d.y, 0), density) && underwater == *is_underwater { - Some(*bk) + Some(*kind) } else { None } }); - if let Some(bk) = bk { + if let Some(kind) = kind { let alt = col_sample.alt as i32; // Find the intersection between ground and air, if there is one near the @@ -349,10 +351,9 @@ pub fn apply_scatter_to<'a>( }) }) { - let _ = vol.set( - Vec3::new(offs.x, offs.y, alt + solid_end), - Block::new(bk, Rgb::broadcast(0)), - ); + let _ = vol.map(Vec3::new(offs.x, offs.y, alt + solid_end), |block| { + block.with_sprite(kind) + }); } } } diff --git a/world/src/lib.rs b/world/src/lib.rs index d7147bc736..30b2d720a2 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -116,7 +116,7 @@ impl World { let air = Block::empty(); let stone = Block::new( - BlockKind::Dense, + BlockKind::Rock, zcache_grid .get(grid_border + TerrainChunkSize::RECT_SIZE.map(|e| e as i32) / 2) .and_then(|zcache| zcache.as_ref()) diff --git a/world/src/site/castle/mod.rs b/world/src/site/castle/mod.rs index aba6553f33..2accbc7264 100644 --- a/world/src/site/castle/mod.rs +++ b/world/src/site/castle/mod.rs @@ -208,7 +208,7 @@ impl Castle { let _ = vol.set( pos, Block::new( - BlockKind::Normal, + BlockKind::Earth, col_sample.sub_surface_color.map(|e| (e * 255.0) as u8), ), ); diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index a3a2890166..cdaf42f5d7 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -14,7 +14,7 @@ use common::{ generation::{ChunkSupplement, EntityInfo}, lottery::Lottery, store::{Id, Store}, - terrain::{Block, BlockKind, Structure, TerrainChunkSize}, + terrain::{Block, BlockKind, SpriteKind, Structure, TerrainChunkSize}, vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol}, }; use core::{f32, hash::BuildHasherDefault}; @@ -564,13 +564,7 @@ impl Floor { let empty = BlockMask::new(Block::empty(), 1); let make_staircase = move |pos: Vec3, radius: f32, inner_radius: f32, stretch: f32| { - let stone = BlockMask::new( - Block::new( - BlockKind::Normal, - /* Rgb::new(150, 150, 175) */ colors.stone.into(), - ), - 5, - ); + let stone = BlockMask::new(Block::new(BlockKind::Rock, colors.stone.into()), 5); if (pos.xy().magnitude_squared() as f32) < inner_radius.powf(2.0) { stone @@ -599,15 +593,14 @@ impl Floor { let floor_sprite = if RandomField::new(7331).chance(Vec3::from(pos), 0.00005) { BlockMask::new( - Block::new( + Block::empty().with_sprite( match (RandomField::new(1337).get(Vec3::from(pos)) / 2) % 20 { - 0 => BlockKind::Apple, - 1 => BlockKind::VeloriteFrag, - 2 => BlockKind::Velorite, - 3..=8 => BlockKind::Mushroom, - _ => BlockKind::ShortGrass, + 0 => SpriteKind::Apple, + 1 => SpriteKind::VeloriteFrag, + 2 => SpriteKind::Velorite, + 3..=8 => SpriteKind::Mushroom, + _ => SpriteKind::ShortGrass, }, - Rgb::white(), ), 1, ) @@ -616,7 +609,7 @@ impl Floor { { let room = &self.rooms[*room]; if RandomField::new(room.seed).chance(Vec3::from(pos), room.loot_density * 0.5) { - BlockMask::new(Block::new(BlockKind::Chest, Rgb::white()), 1) + BlockMask::new(Block::empty().with_sprite(SpriteKind::Chest), 1) } else { empty } diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs index 182c7043d9..609fee44ed 100644 --- a/world/src/site/settlement/building/archetype/house.rs +++ b/world/src/site/settlement/building/archetype/house.rs @@ -8,7 +8,7 @@ use crate::{ }; use common::{ make_case_elim, - terrain::{Block, BlockKind}, + terrain::{Block, BlockKind, SpriteKind}, vol::Vox, }; use rand::prelude::*; @@ -145,7 +145,7 @@ pub struct Attr { pub mansard: i32, pub pillar: Pillar, pub levels: i32, - pub window: BlockKind, + pub window: SpriteKind, } impl Attr { @@ -169,10 +169,10 @@ impl Attr { }, levels: rng.gen_range(1, 3), window: match rng.gen_range(0, 4) { - 0 => BlockKind::Window1, - 1 => BlockKind::Window2, - 2 => BlockKind::Window3, - _ => BlockKind::Window4, + 0 => SpriteKind::Window1, + 1 => SpriteKind::Window2, + 2 => SpriteKind::Window3, + _ => SpriteKind::Window4, }, } } @@ -267,24 +267,13 @@ impl Archetype for House { let profile = Vec2::new(bound_offset.x, z); - let make_meta = |ori| { - Rgb::new( - match ori { - Ori::East => 0, - Ori::North => 2, - }, - 0, - 0, - ) - }; - 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, + BlockKind::Misc, // TODO: Clarify exactly how this affects the color. Rgb::new(r, g, b) .map(|e: u8| e.saturating_add((nz & 0x0F) as u8).saturating_sub(8)), @@ -307,10 +296,16 @@ impl Archetype for House { let empty = BlockMask::nothing(); let internal = BlockMask::new(Block::empty(), internal_layer); let end_window = BlockMask::new( - Block::new(attr.window, make_meta(ori.flip())), + Block::empty().with_sprite(attr.window).with_ori(match ori { + Ori::East => 2, + Ori::North => 0, + }), structural_layer, ); - let fire = BlockMask::new(Block::new(BlockKind::Ember, Rgb::white()), foundation_layer); + let fire = BlockMask::new( + Block::empty().with_sprite(SpriteKind::Ember), + foundation_layer, + ); let storey_height = 6; let storey = ((z - 1) / storey_height).min(attr.levels - 1); @@ -441,12 +436,14 @@ impl Archetype for House { // Doors on first floor only if profile.y == foundation_height + 1 { BlockMask::new( - Block::new( - BlockKind::Door, - if bound_offset.x == (width - 1) / 2 { - make_meta(ori.flip()) + Block::empty().with_sprite(SpriteKind::Door).with_ori( + match ori { + Ori::East => 2, + Ori::North => 0, + } + if bound_offset.x == (width - 1) / 2 { + 0 } else { - make_meta(ori.flip()) + Rgb::new(4, 0, 0) + 4 }, ), structural_layer, @@ -542,26 +539,26 @@ impl Archetype for House { z + 100, )) % 11 { - 0 => BlockKind::Planter, - 1 => BlockKind::ChairSingle, - 2 => BlockKind::ChairDouble, - 3 => BlockKind::CoatRack, + 0 => SpriteKind::Planter, + 1 => SpriteKind::ChairSingle, + 2 => SpriteKind::ChairDouble, + 3 => SpriteKind::CoatRack, 4 => { if rng.gen_range(0, 8) == 0 { - BlockKind::Chest + SpriteKind::Chest } else { - BlockKind::Crate + SpriteKind::Crate } }, - 6 => BlockKind::DrawerMedium, - 7 => BlockKind::DrawerSmall, - 8 => BlockKind::TableSide, - 9 => BlockKind::WardrobeSingle, - _ => BlockKind::Pot, + 6 => SpriteKind::DrawerMedium, + 7 => SpriteKind::DrawerSmall, + 8 => SpriteKind::TableSide, + 9 => SpriteKind::WardrobeSingle, + _ => SpriteKind::Pot, }; return Some(BlockMask::new( - Block::new(furniture, Rgb::new(edge_ori, 0, 0)), + Block::empty().with_sprite(furniture).with_ori(edge_ori), internal_layer, )); } else { @@ -584,13 +581,15 @@ impl Archetype for House { .get(Vec3::new(center_offset.x, center_offset.y, z + 100)) % 4 { - 0 => BlockKind::HangingSign, - 1 | 2 | 3 => BlockKind::HangingBasket, - _ => BlockKind::DungeonWallDecor, + 0 => SpriteKind::HangingSign, + 1 | 2 | 3 => SpriteKind::HangingBasket, + _ => SpriteKind::DungeonWallDecor, }; Some(BlockMask::new( - Block::new(ornament, Rgb::new((edge_ori + 4) % 8, 0, 0)), + Block::empty() + .with_sprite(ornament) + .with_ori((edge_ori + 4) % 8), internal_layer, )) } else { diff --git a/world/src/site/settlement/building/archetype/keep.rs b/world/src/site/settlement/building/archetype/keep.rs index 836c74030d..22f3e20db6 100644 --- a/world/src/site/settlement/building/archetype/keep.rs +++ b/world/src/site/settlement/building/archetype/keep.rs @@ -6,7 +6,7 @@ use crate::{ }; use common::{ make_case_elim, - terrain::{Block, BlockKind}, + terrain::{Block, BlockKind, SpriteKind}, vol::Vox, }; use rand::prelude::*; @@ -134,23 +134,8 @@ impl Archetype for Keep { let important_layer = normal_layer + 1; let internal_layer = important_layer + 1; - let make_meta = |ori| { - Rgb::new( - match ori { - Ori::East => 0, - Ori::North => 2, - }, - 0, - 0, - ) - }; - - let make_block = |r, g, b| { - BlockMask::new( - Block::new(BlockKind::Normal, Rgb::new(r, g, b)), - normal_layer, - ) - }; + let make_block = + |r, g, b| BlockMask::new(Block::new(BlockKind::Rock, Rgb::new(r, g, b)), normal_layer); let brick_tex_pos = (pos + Vec3::new(pos.z, pos.z, 0)) / Vec3::new(2, 2, 1); let brick_tex = RandomField::new(0).get(brick_tex_pos) as u8 % 24; @@ -165,7 +150,12 @@ impl Archetype for Keep { stone_color.2 + brick_tex, ); let window = BlockMask::new( - Block::new(BlockKind::Window1, make_meta(ori.flip())), + Block::empty() + .with_sprite(SpriteKind::Window1) + .with_ori(match ori { + Ori::East => 2, + Ori::North => 0, + }), normal_layer, ); let floor = make_block( @@ -182,7 +172,7 @@ impl Archetype for Keep { let empty = BlockMask::nothing(); let make_staircase = move |pos: Vec3, radius: f32, inner_radius: f32, stretch: f32| { - let stone = BlockMask::new(Block::new(BlockKind::Normal, dungeon_stone.into()), 5); + let stone = BlockMask::new(Block::new(BlockKind::Rock, dungeon_stone.into()), 5); if (pos.xy().magnitude_squared() as f32) < inner_radius.powf(2.0) { stone diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 66b84ca9b8..509f36f95d 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -19,7 +19,7 @@ use common::{ path::Path, spiral::Spiral2d, store::{Id, Store}, - terrain::{Block, BlockKind, TerrainChunkSize}, + terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize}, vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol}, }; use fxhash::FxHasher64; @@ -611,7 +611,7 @@ impl Settlement { } { - let mut surface_block = None; + let mut surface_sprite = None; let roll = |seed, n| self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, seed * 5)) % n; @@ -637,8 +637,7 @@ impl Settlement { if (col_sample.path.map(|(dist, _, _, _)| dist > 6.0 && dist < 7.0).unwrap_or(false) && is_lamp) //roll(0, 50) == 0) || (roll(0, 2000) == 0 && col_sample.path.map(|(dist, _, _, _)| dist > 20.0).unwrap_or(true)) { - surface_block = - Some(Block::new(BlockKind::StreetLamp, Rgb::white())); + surface_sprite = Some(SpriteKind::StreetLamp); } } @@ -676,41 +675,38 @@ impl Settlement { if in_furrow { if roll(0, 5) == 0 { - surface_block = match crop { - Crop::Corn => Some(BlockKind::Corn), + surface_sprite = match crop { + Crop::Corn => Some(SpriteKind::Corn), Crop::Wheat if roll(1, 2) == 0 => { - Some(BlockKind::WheatYellow) + Some(SpriteKind::WheatYellow) }, - Crop::Wheat => Some(BlockKind::WheatGreen), + Crop::Wheat => Some(SpriteKind::WheatGreen), Crop::Cabbage if roll(2, 2) == 0 => { - Some(BlockKind::Cabbage) + Some(SpriteKind::Cabbage) }, Crop::Pumpkin if roll(3, 2) == 0 => { - Some(BlockKind::Pumpkin) + Some(SpriteKind::Pumpkin) }, - Crop::Flax if roll(4, 2) == 0 => Some(BlockKind::Flax), - Crop::Carrot if roll(5, 2) == 0 => Some(BlockKind::Carrot), - Crop::Tomato if roll(6, 2) == 0 => Some(BlockKind::Tomato), - Crop::Radish if roll(7, 2) == 0 => Some(BlockKind::Radish), - Crop::Turnip if roll(8, 2) == 0 => Some(BlockKind::Turnip), - Crop::Sunflower => Some(BlockKind::Sunflower), - _ => None, + Crop::Flax if roll(4, 2) == 0 => Some(SpriteKind::Flax), + Crop::Carrot if roll(5, 2) == 0 => Some(SpriteKind::Carrot), + Crop::Tomato if roll(6, 2) == 0 => Some(SpriteKind::Tomato), + Crop::Radish if roll(7, 2) == 0 => Some(SpriteKind::Radish), + Crop::Turnip if roll(8, 2) == 0 => Some(SpriteKind::Turnip), + Crop::Sunflower => Some(SpriteKind::Sunflower), + _ => surface_sprite, } .or_else(|| { if roll(9, 400) == 0 { - Some(BlockKind::Scarecrow) + Some(SpriteKind::Scarecrow) } else { None } - }) - .map(|kind| Block::new(kind, Rgb::white())); + }); } } else if roll(0, 20) == 0 { - surface_block = - Some(Block::new(BlockKind::ShortGrass, Rgb::white())); + surface_sprite = Some(SpriteKind::ShortGrass); } else if roll(1, 30) == 0 { - surface_block = - Some(Block::new(BlockKind::MediumGrass, Rgb::white())); + surface_sprite = Some(SpriteKind::MediumGrass); } Some(if in_furrow { dirt } else { mound }) @@ -732,12 +728,20 @@ impl Settlement { 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.kind() == BlockKind::Air { + if block.is_empty() { break; } - if let (0, Some(block)) = (z, surface_block) { - let _ = vol.set(pos, block); + if let (0, Some(sprite)) = (z, surface_sprite) { + let _ = vol.set( + pos, + if block.is_fluid() { + block + } else { + Block::empty() + } + .with_sprite(sprite), + ); } else if z >= 0 { if block.kind() != BlockKind::Water { let _ = vol.set(pos, Block::empty()); @@ -745,7 +749,7 @@ impl Settlement { } else { let _ = vol.set( pos, - Block::new(BlockKind::Normal, noisy_color(color, 4)), + Block::new(BlockKind::Earth, noisy_color(color, 4)), ); } } @@ -773,7 +777,7 @@ impl Settlement { if dist / WayKind::Wall.width() < ((1.0 - z as f32 / 12.0) * 2.0).min(1.0) { let _ = vol.set( Vec3::new(offs.x, offs.y, surface_z + z), - Block::new(BlockKind::Normal, color), + Block::new(BlockKind::Wood, color), ); } } @@ -784,7 +788,7 @@ impl Settlement { for z in -2..16 { let _ = vol.set( Vec3::new(offs.x, offs.y, surface_z + z), - Block::new(BlockKind::Normal, colors.tower_color.into()), + Block::new(BlockKind::Rock, colors.tower_color.into()), ); } }