Merge branch 'zesterer/better-block-format' into 'master'

Improved representation of Block for better performance, more...

See merge request veloren/veloren!1395
This commit is contained in:
Joshua Barretto 2020-09-21 21:49:43 +00:00
commit b3117feea6
32 changed files with 733 additions and 796 deletions

View File

@ -37,6 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Improved first person aiming - Improved first person aiming
- Figure meshing no longer blocks the main thread. - Figure meshing no longer blocks the main thread.
- Overhauled persistence layer including no longer storing serialized JSON items in the database - Overhauled persistence layer including no longer storing serialized JSON items in the database
- Overhauled representation of blocks to permit fluid and sprite coexistence
### Removed ### Removed

13
Cargo.lock generated
View File

@ -2593,6 +2593,17 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "num-derive"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f09b9841adb6b5e1f89ef7087ea636e0fd94b2851f887c1e3eb5d5f8228fab3"
dependencies = [
"proc-macro2 1.0.18",
"quote 1.0.7",
"syn 1.0.33",
]
[[package]] [[package]]
name = "num-integer" name = "num-integer"
version = "0.1.43" version = "0.1.43"
@ -4639,6 +4650,8 @@ dependencies = [
"indexmap", "indexmap",
"lazy_static", "lazy_static",
"notify", "notify",
"num-derive",
"num-traits",
"parking_lot 0.9.0", "parking_lot 0.9.0",
"rand 0.7.3", "rand 0.7.3",
"rayon", "rayon",

View File

@ -1,13 +1,5 @@
#![enable(unwrap_newtypes)] #![enable(unwrap_newtypes)]
( (
// Non-sprites
Air: None,
Normal: None,
Dense: None,
Rock: None,
Leaves: None,
Water: None,
// Windows // Windows
Window1: Some(( Window1: Some((
variations: [ variations: [

View File

@ -34,6 +34,8 @@ sum_type = "0.2.0"
authc = { git = "https://gitlab.com/veloren/auth.git", rev = "b943c85e4a38f5ec60cd18c34c73097640162bfe" } authc = { git = "https://gitlab.com/veloren/auth.git", rev = "b943c85e4a38f5ec60cd18c34c73097640162bfe" }
slab = "0.4.2" slab = "0.4.2"
enum-iterator = "0.6" enum-iterator = "0.6"
num-traits = "0.2"
num-derive = "0.3"
# Tracy # Tracy
tracy-client = { version = "0.8.0", optional = true } tracy-client = { version = "0.8.0", optional = true }

View File

@ -16,7 +16,7 @@ fn criterion_benchmark(c: &mut Criterion) {
// Setup: Create chunk and fill it (dense) for z in [140, 220). // Setup: Create chunk and fill it (dense) for z in [140, 220).
let mut chunk = TerrainChunk::new( let mut chunk = TerrainChunk::new(
MIN_Z, MIN_Z,
Block::new(BlockKind::Dense, Default::default()), Block::new(BlockKind::Rock, Rgb::zero()),
Block::empty(), Block::empty(),
TerrainChunkMeta::void(), TerrainChunkMeta::void(),
); );
@ -29,7 +29,7 @@ fn criterion_benchmark(c: &mut Criterion) {
), ),
) { ) {
chunk chunk
.set(pos, Block::new(BlockKind::Dense, Default::default())) .set(pos, Block::new(BlockKind::Rock, Rgb::zero()))
.unwrap(); .unwrap();
} }
@ -118,7 +118,7 @@ fn criterion_benchmark(c: &mut Criterion) {
MAX_Z, MAX_Z,
), ),
) { ) {
let _ = chunk.set(pos, Block::new(BlockKind::Dense, Default::default())); let _ = chunk.set(pos, Block::new(BlockKind::Rock, Rgb::zero()));
} }
}) })
}); });

View File

@ -59,6 +59,7 @@ pub enum ChatCommand {
Lantern, Lantern,
Light, Light,
MakeBlock, MakeBlock,
MakeSprite,
Motd, Motd,
Object, Object,
Players, Players,
@ -105,6 +106,7 @@ pub static CHAT_COMMANDS: &[ChatCommand] = &[
ChatCommand::Lantern, ChatCommand::Lantern,
ChatCommand::Light, ChatCommand::Light,
ChatCommand::MakeBlock, ChatCommand::MakeBlock,
ChatCommand::MakeSprite,
ChatCommand::Motd, ChatCommand::Motd,
ChatCommand::Object, ChatCommand::Object,
ChatCommand::Players, ChatCommand::Players,
@ -162,6 +164,11 @@ lazy_static! {
.cloned() .cloned()
.collect(); .collect();
static ref SPRITE_KINDS: Vec<String> = terrain::sprite::SPRITE_KINDS
.keys()
.cloned()
.collect();
/// List of item specifiers. Useful for tab completing /// List of item specifiers. Useful for tab completing
static ref ITEM_SPECS: Vec<String> = { static ref ITEM_SPECS: Vec<String> = {
let path = assets::ASSETS_PATH.join("common").join("items"); let path = assets::ASSETS_PATH.join("common").join("items");
@ -306,7 +313,12 @@ impl ChatCommand {
), ),
ChatCommand::MakeBlock => cmd( ChatCommand::MakeBlock => cmd(
vec![Enum("block", BLOCK_KINDS.clone(), Required)], vec![Enum("block", BLOCK_KINDS.clone(), Required)],
"Make a block", "Make a block at your location",
Admin,
),
ChatCommand::MakeSprite => cmd(
vec![Enum("sprite", SPRITE_KINDS.clone(), Required)],
"Make a sprite at your location",
Admin, Admin,
), ),
ChatCommand::Motd => cmd( ChatCommand::Motd => cmd(
@ -422,6 +434,7 @@ impl ChatCommand {
ChatCommand::Lantern => "lantern", ChatCommand::Lantern => "lantern",
ChatCommand::Light => "light", ChatCommand::Light => "light",
ChatCommand::MakeBlock => "make_block", ChatCommand::MakeBlock => "make_block",
ChatCommand::MakeSprite => "make_sprite",
ChatCommand::Motd => "motd", ChatCommand::Motd => "motd",
ChatCommand::Object => "object", ChatCommand::Object => "object",
ChatCommand::Players => "players", ChatCommand::Players => "players",

View File

@ -8,7 +8,7 @@ use crate::{
assets::{self, Asset, Error}, assets::{self, Asset, Error},
effect::Effect, effect::Effect,
lottery::Lottery, lottery::Lottery,
terrain::{Block, BlockKind}, terrain::{Block, SpriteKind},
}; };
use crossbeam::atomic::AtomicCell; use crossbeam::atomic::AtomicCell;
use rand::prelude::*; use rand::prelude::*;
@ -277,23 +277,23 @@ impl Item {
pub fn try_reclaim_from_block(block: Block) -> Option<Self> { pub fn try_reclaim_from_block(block: Block) -> Option<Self> {
let chosen; let chosen;
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
Some(Item::new_from_asset_expect(match block.kind() { Some(Item::new_from_asset_expect(match block.get_sprite()? {
BlockKind::Apple => "common.items.food.apple", SpriteKind::Apple => "common.items.food.apple",
BlockKind::Mushroom => "common.items.food.mushroom", SpriteKind::Mushroom => "common.items.food.mushroom",
BlockKind::Velorite => "common.items.ore.velorite", SpriteKind::Velorite => "common.items.ore.velorite",
BlockKind::VeloriteFrag => "common.items.ore.veloritefrag", SpriteKind::VeloriteFrag => "common.items.ore.veloritefrag",
BlockKind::BlueFlower => "common.items.flowers.blue", SpriteKind::BlueFlower => "common.items.flowers.blue",
BlockKind::PinkFlower => "common.items.flowers.pink", SpriteKind::PinkFlower => "common.items.flowers.pink",
BlockKind::PurpleFlower => "common.items.flowers.purple", SpriteKind::PurpleFlower => "common.items.flowers.purple",
BlockKind::RedFlower => "common.items.flowers.red", SpriteKind::RedFlower => "common.items.flowers.red",
BlockKind::WhiteFlower => "common.items.flowers.white", SpriteKind::WhiteFlower => "common.items.flowers.white",
BlockKind::YellowFlower => "common.items.flowers.yellow", SpriteKind::YellowFlower => "common.items.flowers.yellow",
BlockKind::Sunflower => "common.items.flowers.sun", SpriteKind::Sunflower => "common.items.flowers.sun",
BlockKind::LongGrass => "common.items.grasses.long", SpriteKind::LongGrass => "common.items.grasses.long",
BlockKind::MediumGrass => "common.items.grasses.medium", SpriteKind::MediumGrass => "common.items.grasses.medium",
BlockKind::ShortGrass => "common.items.grasses.short", SpriteKind::ShortGrass => "common.items.grasses.short",
BlockKind::Coconut => "common.items.food.coconut", SpriteKind::Coconut => "common.items.food.coconut",
BlockKind::Chest => { SpriteKind::Chest => {
chosen = Lottery::<String>::load_expect(match rng.gen_range(0, 7) { chosen = Lottery::<String>::load_expect(match rng.gen_range(0, 7) {
0 => "common.loot_tables.loot_table_weapon_uncommon", 0 => "common.loot_tables.loot_table_weapon_uncommon",
1 => "common.loot_tables.loot_table_weapon_common", 1 => "common.loot_tables.loot_table_weapon_common",
@ -304,13 +304,13 @@ impl Item {
}); });
chosen.choose() chosen.choose()
}, },
BlockKind::Crate => { SpriteKind::Crate => {
chosen = Lottery::<String>::load_expect("common.loot_tables.loot_table_food"); chosen = Lottery::<String>::load_expect("common.loot_tables.loot_table_food");
chosen.choose() chosen.choose()
}, },
BlockKind::Stones => "common.items.crafting_ing.stones", SpriteKind::Stones => "common.items.crafting_ing.stones",
BlockKind::Twigs => "common.items.crafting_ing.twigs", SpriteKind::Twigs => "common.items.crafting_ing.twigs",
BlockKind::ShinyGem => "common.items.crafting_ing.shiny_gem", SpriteKind::ShinyGem => "common.items.crafting_ing.shiny_gem",
_ => return None, _ => return None,
})) }))
} }

View File

@ -434,7 +434,7 @@ where
V: BaseVol<Vox = Block> + ReadVol, V: BaseVol<Vox = Block> + ReadVol,
{ {
vol.get(pos - Vec3::new(0, 0, 1)) 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) .unwrap_or(false)
&& vol && vol
.get(pos + Vec3::new(0, 0, 0)) .get(pos + Vec3::new(0, 0, 0))

View File

@ -7,7 +7,7 @@ use crate::{
sys, sys,
terrain::{Block, TerrainChunk, TerrainGrid}, terrain::{Block, TerrainChunk, TerrainGrid},
time::DayPeriod, time::DayPeriod,
vol::WriteVol, vol::{ReadVol, WriteVol},
}; };
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
use rayon::{ThreadPool, ThreadPoolBuilder}; use rayon::{ThreadPool, ThreadPoolBuilder};
@ -264,6 +264,11 @@ impl State {
/// Get a writable reference to this state's terrain. /// Get a writable reference to this state's terrain.
pub fn terrain_mut(&self) -> FetchMut<TerrainGrid> { self.ecs.write_resource() } pub fn terrain_mut(&self) -> FetchMut<TerrainGrid> { self.ecs.write_resource() }
/// Get a block in this state's terrain.
pub fn get_block(&self, pos: Vec3<i32>) -> Option<Block> {
self.terrain().get(pos).ok().copied()
}
/// Set a block in this state's terrain. /// Set a block in this state's terrain.
pub fn set_block(&mut self, pos: Vec3<i32>, block: Block) { pub fn set_block(&mut self, pos: Vec3<i32>, block: Block) {
self.ecs.write_resource::<BlockChange>().set(pos, block); self.ecs.write_resource::<BlockChange>().set(pos, block);

View File

@ -8,7 +8,7 @@ use crate::{
span, span,
state::DeltaTime, state::DeltaTime,
sync::{Uid, UidAllocator}, sync::{Uid, UidAllocator},
terrain::{Block, BlockKind, TerrainGrid}, terrain::{Block, TerrainGrid},
vol::ReadVol, vol::ReadVol,
}; };
use rayon::iter::ParallelIterator; use rayon::iter::ParallelIterator;
@ -341,7 +341,7 @@ impl<'a> System<'a> for Sys {
let near_iter = (-hdist..hdist + 1) let near_iter = (-hdist..hdist + 1)
.map(move |i| { .map(move |i| {
(-hdist..hdist + 1).map(move |j| { (-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) ..z_max.ceil() as i32 + 1)
.map(move |k| (i, j, k)) .map(move |k| (i, j, k))
}) })
@ -370,7 +370,7 @@ impl<'a> System<'a> for Sys {
let block_aabb = Aabb { let block_aabb = Aabb {
min: block_pos.map(|e| e as f32), min: block_pos.map(|e| e as f32),
max: 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) { if player_aabb.collides_with_aabb(block_aabb) {
@ -442,9 +442,9 @@ impl<'a> System<'a> for Sys {
block_pos, block_pos,
Aabb { Aabb {
min: block_pos.map(|e| e as f32), 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 { } else {
None None
@ -557,7 +557,7 @@ impl<'a> System<'a> for Sys {
&terrain, &terrain,
&|block| { &|block| {
block.is_solid() 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(), near_iter.clone(),
radius, radius,
@ -571,7 +571,7 @@ impl<'a> System<'a> for Sys {
) )
.ok() .ok()
.filter(|block| block.is_solid()) .filter(|block| block.is_solid())
.map(|block| block.get_height()) .map(|block| block.solid_height())
.unwrap_or(0.0); .unwrap_or(0.0);
pos.0.z = (pos.0.z - 0.05).floor() + snap_height; pos.0.z = (pos.0.z - 0.05).floor() + snap_height;
physics_state.on_ground = true; physics_state.on_ground = true;
@ -609,7 +609,7 @@ impl<'a> System<'a> for Sys {
physics_state.in_fluid = collision_iter( physics_state.in_fluid = collision_iter(
pos.0, pos.0,
&terrain, &terrain,
&|block| block.is_fluid(), &|block| block.is_liquid(),
near_iter.clone(), near_iter.clone(),
radius, radius,
z_min..z_max, z_min..z_max,
@ -619,7 +619,7 @@ impl<'a> System<'a> for Sys {
}, },
Collider::Point => { Collider::Point => {
let (dist, block) = terrain.ray(pos.0, pos.0 + pos_delta) let (dist, block) = terrain.ray(pos.0, pos.0 + pos_delta)
.until(|vox| !vox.is_air() && !vox.is_fluid()) .until(|vox| !vox.is_air() && !vox.is_liquid())
.ignore_error().cast(); .ignore_error().cast();
pos.0 += pos_delta.try_normalized().unwrap_or(Vec3::zero()) * dist; pos.0 += pos_delta.try_normalized().unwrap_or(Vec3::zero()) * dist;
@ -655,7 +655,7 @@ impl<'a> System<'a> for Sys {
physics_state.in_fluid = terrain.get(pos.0.map(|e| e.floor() as i32)) physics_state.in_fluid = terrain.get(pos.0.map(|e| e.floor() as i32))
.ok() .ok()
.and_then(|vox| vox.is_fluid().then_some(1.0)); .and_then(|vox| vox.is_liquid().then_some(1.0));
}, },
} }

View File

@ -1,103 +1,78 @@
use super::SpriteKind;
use crate::{make_case_elim, vol::Vox}; use crate::{make_case_elim, vol::Vox};
use enum_iterator::IntoEnumIterator; use enum_iterator::IntoEnumIterator;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{collections::HashMap, convert::TryFrom, fmt, ops::Deref}; use std::{collections::HashMap, convert::TryFrom, fmt, ops::Deref};
use vek::*; use vek::*;
make_case_elim!( make_case_elim!(
block_kind, 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)] #[repr(u8)]
pub enum BlockKind { pub enum BlockKind {
Air = 0x00, Air = 0x00, // Air counts as a fluid
Normal = 0x01, Water = 0x01,
Dense = 0x02, // 0x02 <= x < 0x10 are reserved for other fluids. These are 2^n aligned to allow bitwise
Rock = 0x03, // checking of common conditions. For example, `is_fluid` is just `block_kind &
Grass = 0x04, // 0x0F == 0` (this is a very common operation used in meshing that could do with
Leaves = 0x05, // being *very* fast).
Water = 0x06, Rock = 0x10,
LargeCactus = 0x07, WeakRock = 0x11, // Explodable
BarrelCactus = 0x08, // 0x12 <= x < 0x20 is reserved for future rocks
RoundCactus = 0x09, Grass = 0x20, // Note: *not* the same as grass sprites
ShortCactus = 0x0A, // 0x21 <= x < 0x30 is reserved for future grasses
MedFlatCactus = 0x0B, Earth = 0x30,
ShortFlatCactus = 0x0C, Sand = 0x31,
BlueFlower = 0x0D, // 0x32 <= x < 0x40 is reserved for future earths/muds/gravels/sands/etc.
PinkFlower = 0x0E, Wood = 0x40,
PurpleFlower = 0x0F, Leaves = 0x41,
RedFlower = 0x10, // 0x42 <= x < 0x50 is reserved for future tree parts
WhiteFlower = 0x11,
YellowFlower = 0x12, // Covers all other cases (we sometimes have bizarrely coloured misc blocks, and also we
Sunflower = 0x13, // often want to experiment with new kinds of block without allocating them a
LongGrass = 0x14, // dedicated block kind.
MediumGrass = 0x15, Misc = 0xFE,
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,
} }
); );
impl BlockKind {
#[inline]
pub const fn is_air(&self) -> bool { matches!(self, BlockKind::Air) }
/// Determine whether the block kind is a gas or a liquid. This does not
/// consider any sprites that may occupy the block (the definition of
/// fluid is 'a substance that deforms to fit containers')
#[inline]
pub const fn is_fluid(&self) -> bool { *self as u8 & 0xF0 == 0x00 }
#[inline]
pub const fn is_liquid(&self) -> bool { self.is_fluid() && !self.is_air() }
/// Determine whether the block is filled (i.e: fully solid). Right now,
/// this is the opposite of being a fluid.
#[inline]
pub const fn is_filled(&self) -> bool { !self.is_fluid() }
/// Determine whether the block has an RGB color storaged in the attribute
/// fields.
#[inline]
pub const fn has_color(&self) -> bool { self.is_filled() }
}
impl fmt::Display for BlockKind { impl fmt::Display for BlockKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) } fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) }
} }
@ -114,408 +89,10 @@ impl<'a> TryFrom<&'a str> for BlockKind {
fn try_from(s: &'a str) -> Result<Self, Self::Error> { BLOCK_KINDS.get(s).copied().ok_or(()) } fn try_from(s: &'a str) -> Result<Self, Self::Error> { 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<u8> {
// 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<f32>`
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)] #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[repr(packed)]
pub struct Block { pub struct Block {
kind: BlockKind, kind: BlockKind,
color: [u8; 3], attr: [u8; 3],
}
impl Block {
pub const fn new(kind: BlockKind, color: Rgb<u8>) -> Self {
Self {
kind,
color: [color.r, color.g, color.b],
}
}
pub fn get_color(&self) -> Option<Rgb<u8>> {
if !self.is_air() {
Some(self.color.into())
} else {
None
}
}
pub fn get_ori(&self) -> Option<u8> {
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 }
} }
impl Deref for Block { impl Deref for Block {
@ -528,11 +105,141 @@ impl Vox for Block {
fn empty() -> Self { fn empty() -> Self {
Self { Self {
kind: BlockKind::Air, 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<u8>) -> 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 const fn air(sprite: SpriteKind) -> Self {
Self {
kind: BlockKind::Air,
attr: [sprite as u8, 0, 0],
}
}
#[inline]
pub fn get_color(&self) -> Option<Rgb<u8>> {
if self.has_color() {
Some(self.attr.into())
} else {
None
}
}
#[inline]
pub fn get_sprite(&self) -> Option<SpriteKind> {
if !self.is_filled() {
SpriteKind::from_u8(self.attr[0])
} else {
None
}
}
#[inline]
pub fn get_ori(&self) -> Option<u8> {
if self.get_sprite()?.has_ori() {
// TODO: Formalise this a bit better
Some(self.attr[1] & 0b111)
} else {
None
}
}
#[inline]
pub fn get_glow(&self) -> Option<u8> {
// 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
}
#[inline]
pub fn is_solid(&self) -> bool {
self.get_sprite()
.map(|s| s.solid_height().is_some())
.unwrap_or(true)
}
#[inline]
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 all is good for
// empty fluids.
// TODO: Handle the case of terrain sprites we don't want to have explode
_ => true,
}
}
#[inline]
pub fn is_collectible(&self) -> bool {
self.get_sprite()
.map(|s| s.is_collectible())
.unwrap_or(false)
}
#[inline]
pub fn is_opaque(&self) -> bool { self.kind().is_filled() }
#[inline]
pub fn solid_height(&self) -> f32 {
self.get_sprite()
.map(|s| s.solid_height().unwrap_or(0.0))
.unwrap_or(1.0)
}
#[inline]
pub fn kind(&self) -> BlockKind { self.kind }
/// If this block is a fluid, replace its sprite.
#[inline]
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, give it a new orientation.
#[inline]
pub fn with_ori(mut self, ori: u8) -> Option<Self> {
if self.get_sprite().map(|s| s.has_ori()).unwrap_or(false) {
self.attr[1] = (self.attr[1] & !0b111) | (ori & 0b111);
Some(self)
} else {
None
}
}
/// Remove the terrain sprite or solid aspects of a block
#[inline]
pub fn into_vacant(self) -> Self {
if self.is_fluid() {
Block::new(self.kind(), Rgb::zero())
} else {
Block::empty()
}
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -2,6 +2,7 @@ pub mod biome;
pub mod block; pub mod block;
pub mod chonk; pub mod chonk;
pub mod map; pub mod map;
pub mod sprite;
pub mod structure; pub mod structure;
// Reexports // Reexports
@ -9,6 +10,7 @@ pub use self::{
biome::BiomeKind, biome::BiomeKind,
block::{Block, BlockKind}, block::{Block, BlockKind},
map::MapSizeLg, map::MapSizeLg,
sprite::SpriteKind,
structure::Structure, structure::Structure,
}; };
use roots::find_roots_cubic; use roots::find_roots_cubic;

View File

@ -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<f32> {
// 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 {
matches!(
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
)
}
}
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<String, SpriteKind> = 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<Self, Self::Error> { SPRITE_KINDS.get(s).copied().ok_or(()) }
}

View File

@ -148,14 +148,14 @@ impl Asset for Structure {
center: Vec3::zero(), center: Vec3::zero(),
vol, vol,
empty: StructureBlock::empty(), empty: StructureBlock::empty(),
default_kind: BlockKind::Normal, default_kind: BlockKind::Misc,
}) })
} else { } else {
Ok(Self { Ok(Self {
center: Vec3::zero(), center: Vec3::zero(),
vol: Dyna::filled(Vec3::zero(), StructureBlock::empty(), ()), vol: Dyna::filled(Vec3::zero(), StructureBlock::empty(), ()),
empty: StructureBlock::empty(), empty: StructureBlock::empty(),
default_kind: BlockKind::Normal, default_kind: BlockKind::Misc,
}) })
} }
} }

View File

@ -131,6 +131,22 @@ pub trait WriteVol: BaseVol {
/// Set the voxel at the provided position in the volume to the provided /// Set the voxel at the provided position in the volume to the provided
/// value. /// value.
fn set(&mut self, pos: Vec3<i32>, vox: Self::Vox) -> Result<(), Self::Error>; fn set(&mut self, pos: Vec3<i32>, 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<F: FnOnce(Self::Vox) -> Self::Vox>(
&mut self,
pos: Vec3<i32>,
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 /// A volume (usually rather a reference to a volume) that is convertible into

View File

@ -12,9 +12,9 @@ use common::{
npc::{self, get_npc_name}, npc::{self, get_npc_name},
state::TimeOfDay, state::TimeOfDay,
sync::{Uid, WorldSyncExt}, sync::{Uid, WorldSyncExt},
terrain::{Block, BlockKind, TerrainChunkSize}, terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize},
util::Dir, util::Dir,
vol::RectVolSize, vol::{RectVolSize, Vox},
LoadoutBuilder, LoadoutBuilder,
}; };
use rand::Rng; use rand::Rng;
@ -87,6 +87,7 @@ fn get_handler(cmd: &ChatCommand) -> CommandHandler {
ChatCommand::Lantern => handle_lantern, ChatCommand::Lantern => handle_lantern,
ChatCommand::Light => handle_light, ChatCommand::Light => handle_light,
ChatCommand::MakeBlock => handle_make_block, ChatCommand::MakeBlock => handle_make_block,
ChatCommand::MakeSprite => handle_make_sprite,
ChatCommand::Motd => handle_motd, ChatCommand::Motd => handle_motd,
ChatCommand::Object => handle_object, ChatCommand::Object => handle_object,
ChatCommand::Players => handle_players, ChatCommand::Players => handle_players,
@ -217,6 +218,44 @@ fn handle_make_block(
} }
} }
fn handle_make_sprite(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: String,
action: &ChatCommand,
) {
if let Some(sprite_name) = scan_fmt_some!(&args, &action.arg_fmt(), String) {
if let Ok(sk) = SpriteKind::try_from(sprite_name.as_str()) {
match server.state.read_component_copied::<comp::Pos>(target) {
Some(pos) => {
let pos = pos.0.map(|e| e.floor() as i32);
let new_block = server
.state
.get_block(pos)
.unwrap_or_else(Block::empty)
.with_sprite(sk);
server.state.set_block(pos, new_block);
},
None => server.notify_client(
client,
ChatType::CommandError.server_msg(String::from("You have no position.")),
),
}
} else {
server.notify_client(
client,
ChatType::CommandError.server_msg(format!("Invalid sprite kind: {}", sprite_name)),
);
}
} else {
server.notify_client(
client,
ChatType::CommandError.server_msg(action.help_string()),
);
}
}
fn handle_motd( fn handle_motd(
server: &mut Server, server: &mut Server,
client: EcsEntity, client: EcsEntity,

View File

@ -14,7 +14,7 @@ use common::{
sync::{Uid, UidAllocator, WorldSyncExt}, sync::{Uid, UidAllocator, WorldSyncExt},
sys::combat::BLOCK_ANGLE, sys::combat::BLOCK_ANGLE,
terrain::{Block, TerrainGrid}, terrain::{Block, TerrainGrid},
vol::{ReadVol, Vox}, vol::ReadVol,
}; };
use comp::item::Reagent; use comp::item::Reagent;
use rand::prelude::*; use rand::prelude::*;
@ -569,10 +569,10 @@ pub fn handle_explosion(
let terrain = ecs.read_resource::<TerrainGrid>(); let terrain = ecs.read_resource::<TerrainGrid>();
let _ = terrain let _ = terrain
.ray(pos, pos + dir * power) .ray(pos, pos + dir * power)
.until(|block| block.is_fluid() || rand::random::<f32>() < 0.05) .until(|block| block.is_liquid() || rand::random::<f32>() < 0.05)
.for_each(|block: &Block, pos| { .for_each(|block: &Block, pos| {
if block.is_explodable() { if block.is_explodable() {
block_change.set(pos, Block::empty()); block_change.set(pos, block.into_vacant());
} }
}) })
.cast(); .cast();

View File

@ -8,8 +8,7 @@ use common::{
msg::ServerMsg, msg::ServerMsg,
recipe::default_recipe_book, recipe::default_recipe_book,
sync::{Uid, WorldSyncExt}, sync::{Uid, WorldSyncExt},
terrain::block::Block, vol::ReadVol,
vol::{ReadVol, Vox},
}; };
use comp::LightEmitter; use comp::LightEmitter;
use rand::Rng; use rand::Rng;
@ -132,7 +131,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
state.write_component(entity, event); state.write_component(entity, event);
if item_was_added { if item_was_added {
// we made sure earlier the block was not already modified this tick // 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 { } else {

View File

@ -97,7 +97,7 @@ fn calc_light<V: RectRasterableVol<Vox = Block> + ReadVol + Debug>(
if vol if vol
.get(outer.min + pos) .get(outer.min + pos)
.ok() .ok()
.map_or(false, |b| b.is_air() || b.is_fluid()) .map_or(false, |b| b.is_fluid())
{ {
*dest = src - 1; *dest = src - 1;
// Can't propagate further // Can't propagate further
@ -127,14 +127,14 @@ fn calc_light<V: RectRasterableVol<Vox = Block> + ReadVol + Debug>(
// Down is special cased and we know up is a ray // Down is special cased and we know up is a ray
// Special cased ray propagation // Special cased ray propagation
let pos = Vec3::new(pos.x, pos.y, pos.z - 1); 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) .get(outer.min + pos)
.ok() .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 { 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)); prop_que.push_back((pos.x as u8, pos.y as u8, pos.z as u16));
SUNLIGHT 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)); prop_que.push_back((pos.x as u8, pos.y as u8, pos.z as u16));
SUNLIGHT - 1 SUNLIGHT - 1
} else { } else {
@ -268,7 +268,7 @@ impl<'a, V: RectRasterableVol<Vox = Block> + ReadVol + Debug>
opaque_limits = opaque_limits opaque_limits = opaque_limits
.map(|l| l.including(z)) .map(|l| l.including(z))
.or_else(|| Some(Limits::from_value(z))); .or_else(|| Some(Limits::from_value(z)));
} else if block.is_fluid() { } else if block.is_liquid() {
fluid_limits = fluid_limits fluid_limits = fluid_limits
.map(|l| l.including(z)) .map(|l| l.including(z))
.or_else(|| Some(Limits::from_value(z))); .or_else(|| Some(Limits::from_value(z)));
@ -412,15 +412,15 @@ fn should_draw_greedy(
let to = flat_get(pos); let to = flat_get(pos);
let from_opaque = from.is_opaque(); let from_opaque = from.is_opaque();
if from_opaque == to.is_opaque() { if from_opaque == to.is_opaque() {
// Check the interface of fluid and non-tangible non-fluids (e.g. air). // Check the interface of liquid and non-tangible non-liquid (e.g. air).
let from_fluid = from.is_fluid(); let from_liquid = from.is_liquid();
if from_fluid == to.is_fluid() || from.is_tangible() || to.is_tangible() { if from_liquid == to.is_liquid() || from.is_opaque() || to.is_opaque() {
None None
} else { } else {
// While fluid is not culled, we still try to keep a consistent orientation as // While liquid is not culled, we still try to keep a consistent orientation as
// we do for land; if going from fluid to non-fluid, // we do for land; if going from liquid to non-liquid,
// forwards-facing; otherwise, backwards-facing. // forwards-facing; otherwise, backwards-facing.
Some((from_fluid, FaceKind::Fluid)) Some((from_liquid, FaceKind::Fluid))
} }
} else { } else {
// If going from transparent to opaque, backward facing; otherwise, forward // If going from transparent to opaque, backward facing; otherwise, forward
@ -428,9 +428,9 @@ fn should_draw_greedy(
Some(( Some((
from_opaque, from_opaque,
FaceKind::Opaque(if from_opaque { FaceKind::Opaque(if from_opaque {
to.is_fluid() to.is_liquid()
} else { } else {
from.is_fluid() from.is_liquid()
}), }),
)) ))
} }

View File

@ -118,7 +118,7 @@ impl Globals {
0.0, 0.0,
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 select_pos: select_pos
.map(|sp| Vec4::from(sp) + Vec4::unit_w()) .map(|sp| Vec4::from(sp) + Vec4::unit_w())
.unwrap_or(Vec4::zero()) .unwrap_or(Vec4::zero())

View File

@ -17,7 +17,7 @@ use common::{
figure::Segment, figure::Segment,
span, span,
spiral::Spiral2d, spiral::Spiral2d,
terrain::{block, Block, BlockKind, TerrainChunk}, terrain::{sprite, Block, SpriteKind, TerrainChunk},
vol::{BaseVol, ReadVol, RectRasterableVol, SampleVol, Vox}, vol::{BaseVol, ReadVol, RectRasterableVol, SampleVol, Vox},
volumes::vol_grid_2d::{VolGrid2d, VolGrid2dError}, volumes::vol_grid_2d::{VolGrid2d, VolGrid2dError},
}; };
@ -49,7 +49,7 @@ pub struct TerrainChunkData {
opaque_model: Model<TerrainPipeline>, opaque_model: Model<TerrainPipeline>,
fluid_model: Option<Model<FluidPipeline>>, fluid_model: Option<Model<FluidPipeline>>,
col_lights: guillotiere::AllocId, col_lights: guillotiere::AllocId,
sprite_instances: HashMap<(BlockKind, usize), Instances<SpriteInstance>>, sprite_instances: HashMap<(SpriteKind, usize), Instances<SpriteInstance>>,
locals: Consts<TerrainLocals>, locals: Consts<TerrainLocals>,
pub blocks_of_interest: BlocksOfInterest, pub blocks_of_interest: BlocksOfInterest,
@ -75,7 +75,7 @@ struct MeshWorkerResponse {
opaque_mesh: Mesh<TerrainPipeline>, opaque_mesh: Mesh<TerrainPipeline>,
fluid_mesh: Mesh<FluidPipeline>, fluid_mesh: Mesh<FluidPipeline>,
col_lights_info: ColLightInfo, col_lights_info: ColLightInfo,
sprite_instances: HashMap<(BlockKind, usize), Vec<SpriteInstance>>, sprite_instances: HashMap<(SpriteKind, usize), Vec<SpriteInstance>>,
started_tick: u64, started_tick: u64,
blocks_of_interest: BlocksOfInterest, blocks_of_interest: BlocksOfInterest,
} }
@ -94,7 +94,7 @@ struct SpriteModelConfig<Model> {
#[derive(Deserialize)] #[derive(Deserialize)]
/// Configuration data for a group of sprites (currently associated with a /// Configuration data for a group of sprites (currently associated with a
/// particular BlockKind). /// particular SpriteKind).
struct SpriteConfig<Model> { struct SpriteConfig<Model> {
/// All possible model variations for this sprite. /// All possible model variations for this sprite.
// NOTE: Could make constant per sprite type, but eliminating this indirection and // NOTE: Could make constant per sprite type, but eliminating this indirection and
@ -109,7 +109,7 @@ struct SpriteConfig<Model> {
/// Configuration data for all sprite models. /// Configuration data for all sprite models.
/// ///
/// NOTE: Model is an asset path to the appropriate sprite .vox model. /// NOTE: Model is an asset path to the appropriate sprite .vox model.
type SpriteSpec = block::block_kind::PureCases<Option<SpriteConfig<String>>>; type SpriteSpec = sprite::sprite_kind::PureCases<Option<SpriteConfig<String>>>;
/// Function executed by worker threads dedicated to chunk meshing. /// Function executed by worker threads dedicated to chunk meshing.
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587 #[allow(clippy::or_fun_call)] // TODO: Pending review in #587
@ -122,7 +122,7 @@ fn mesh_worker<V: BaseVol<Vox = Block> + RectRasterableVol + ReadVol + Debug>(
max_texture_size: u16, max_texture_size: u16,
chunk: Arc<TerrainChunk>, chunk: Arc<TerrainChunk>,
range: Aabb<i32>, range: Aabb<i32>,
sprite_data: &HashMap<(BlockKind, usize), Vec<SpriteData>>, sprite_data: &HashMap<(SpriteKind, usize), Vec<SpriteData>>,
sprite_config: &SpriteSpec, sprite_config: &SpriteSpec,
) -> MeshWorkerResponse { ) -> MeshWorkerResponse {
span!(_guard, "mesh_worker"); span!(_guard, "mesh_worker");
@ -146,14 +146,19 @@ fn mesh_worker<V: BaseVol<Vox = Block> + RectRasterableVol + ReadVol + Debug>(
let wpos = Vec3::from(pos * V::RECT_SIZE.map(|e: u32| e as i32)) + rel_pos; 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 = 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 let seed = wpos.x as u64 * 3
+ wpos.y as u64 * 7 + wpos.y as u64 * 7
+ wpos.x as u64 * wpos.y as u64; // Awful PRNG + wpos.x as u64 * wpos.y as u64; // Awful PRNG
let ori = (block.get_ori().unwrap_or((seed % 4) as u8 * 2)) & 0b111; let ori = (block.get_ori().unwrap_or((seed % 4) as u8 * 2)) & 0b111;
let variation = seed as usize % cfg.variations.len(); 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 we called sprite_config_for already.
// NOTE: Safe because 0 ≤ ori < 8 // NOTE: Safe because 0 ≤ ori < 8
let sprite_data = &sprite_data[&key][0]; let sprite_data = &sprite_data[&key][0];
@ -218,7 +223,7 @@ pub struct Terrain<V: RectRasterableVol> {
mesh_todo: HashMap<Vec2<i32>, ChunkMeshState>, mesh_todo: HashMap<Vec2<i32>, ChunkMeshState>,
// GPU data // GPU data
sprite_data: Arc<HashMap<(BlockKind, usize), Vec<SpriteData>>>, sprite_data: Arc<HashMap<(SpriteKind, usize), Vec<SpriteData>>>,
sprite_col_lights: Texture<ColLightFmt>, sprite_col_lights: Texture<ColLightFmt>,
col_lights: Texture<ColLightFmt>, col_lights: Texture<ColLightFmt>,
waves: Texture, waves: Texture,
@ -254,7 +259,7 @@ impl<V: RectRasterableVol> Terrain<V> {
let sprite_config_ = &sprite_config; let sprite_config_ = &sprite_config;
// NOTE: Tracks the start vertex of the next model to be meshed. // 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()?))) .filter_map(|kind| Some((kind, kind.elim_case_pure(&sprite_config_).as_ref()?)))
.flat_map(|(kind, sprite_config)| { .flat_map(|(kind, sprite_config)| {
let wind_sway = sprite_config.wind_sway; let wind_sway = sprite_config.wind_sway;

View File

@ -1,6 +1,6 @@
use common::{ use common::{
span, span,
terrain::{BlockKind, TerrainChunk}, terrain::{BlockKind, SpriteKind, TerrainChunk},
vol::{IntoVolIterator, RectRasterableVol}, vol::{IntoVolIterator, RectRasterableVol},
}; };
use rand::prelude::*; use rand::prelude::*;
@ -45,16 +45,18 @@ impl BlocksOfInterest {
grass.push(pos) grass.push(pos)
} }
}, },
BlockKind::Ember => embers.push(pos), _ => match block.get_sprite() {
BlockKind::Beehive => beehives.push(pos), Some(SpriteKind::Ember) => embers.push(pos),
BlockKind::Reed => reeds.push(pos), Some(SpriteKind::Beehive) => beehives.push(pos),
BlockKind::PinkFlower => flowers.push(pos), Some(SpriteKind::Reed) => reeds.push(pos),
BlockKind::PurpleFlower => flowers.push(pos), Some(SpriteKind::PinkFlower) => flowers.push(pos),
BlockKind::RedFlower => flowers.push(pos), Some(SpriteKind::PurpleFlower) => flowers.push(pos),
BlockKind::WhiteFlower => flowers.push(pos), Some(SpriteKind::RedFlower) => flowers.push(pos),
BlockKind::YellowFlower => flowers.push(pos), Some(SpriteKind::WhiteFlower) => flowers.push(pos),
BlockKind::Sunflower => flowers.push(pos), Some(SpriteKind::YellowFlower) => flowers.push(pos),
Some(SpriteKind::Sunflower) => flowers.push(pos),
_ => {}, _ => {},
},
}); });
Self { Self {

View File

@ -86,7 +86,7 @@ impl SessionState {
key_state: KeyState::default(), key_state: KeyState::default(),
inputs: comp::ControllerInputs::default(), inputs: comp::ControllerInputs::default(),
hud, hud,
selected_block: Block::new(BlockKind::Normal, Rgb::broadcast(255)), selected_block: Block::new(BlockKind::Misc, Rgb::broadcast(255)),
voxygen_i18n, voxygen_i18n,
walk_forward_dir, walk_forward_dir,
walk_right_dir, walk_right_dir,
@ -1161,7 +1161,7 @@ fn under_cursor(
let cam_ray = terrain let cam_ray = terrain
.ray(cam_pos, cam_pos + cam_dir * 100.0) .ray(cam_pos, cam_pos + cam_dir * 100.0)
.until(|block| block.is_tangible()) .until(|block| block.is_filled() || block.is_collectible())
.cast(); .cast();
let cam_dist = cam_ray.0; let cam_dist = cam_ray.0;

View File

@ -8,7 +8,7 @@ use crate::{
use common::{ use common::{
terrain::{ terrain::{
structure::{self, StructureBlock}, structure::{self, StructureBlock},
Block, BlockKind, Structure, Block, BlockKind, SpriteKind, Structure,
}, },
vol::{ReadVol, Vox}, vol::{ReadVol, Vox},
}; };
@ -254,15 +254,19 @@ impl<'a> BlockGen<'a> {
let grass_depth = (1.5 + 2.0 * chaos).min(height - basement_height); let grass_depth = (1.5 + 2.0 * chaos).min(height - basement_height);
let block = if (wposf.z as f32) < height - grass_depth { 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( let col = Lerp::lerp(
sub_surface_color, sub_surface_color,
stone_col.map(|e| e as f32 / 255.0), 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); .map(|e| (e * 255.0) as u8);
// Underground if stone_factor >= 0.5 {
Some(Block::new(BlockKind::Normal, col)) Some(Block::new(BlockKind::Rock, col))
} else {
Some(Block::new(BlockKind::Earth, col))
}
} else if (wposf.z as f32) < height { } else if (wposf.z as f32) < height {
let grass_factor = (wposf.z as f32 - (height - grass_depth)) let grass_factor = (wposf.z as f32 - (height - grass_depth))
.div(grass_depth) .div(grass_depth)
@ -273,81 +277,10 @@ impl<'a> BlockGen<'a> {
if grass_factor > 0.7 { if grass_factor > 0.7 {
BlockKind::Grass BlockKind::Grass
} else { } else {
BlockKind::Normal BlockKind::Earth
}, },
col.map(|e| (e * 255.0) as u8), 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 { } else {
None None
} }
@ -360,7 +293,7 @@ impl<'a> BlockGen<'a> {
let field2 = RandomField::new(world.seed + 2); let field2 = RandomField::new(world.seed + 2);
Some(Block::new( Some(Block::new(
BlockKind::Rock, BlockKind::WeakRock,
stone_col.map2( stone_col.map2(
Rgb::new( Rgb::new(
field0.get(wpos) as u8 % 16, field0.get(wpos) as u8 % 16,
@ -510,7 +443,7 @@ impl StructureInfo {
.reduce_max() .reduce_max()
{ {
Some(Block::new( Some(Block::new(
BlockKind::Dense, BlockKind::Rock,
index.colors.block.pyramid.into(), index.colors.block.pyramid.into(),
)) ))
} else { } else {
@ -558,11 +491,11 @@ pub fn block_from_structure(
StructureBlock::None => None, StructureBlock::None => None,
StructureBlock::Hollow => Some(Block::empty()), StructureBlock::Hollow => Some(Block::empty()),
StructureBlock::Grass => Some(Block::new( StructureBlock::Grass => Some(Block::new(
BlockKind::Normal, BlockKind::Grass,
sample.surface_color.map(|e| (e * 255.0) as u8), sample.surface_color.map(|e| (e * 255.0) as u8),
)), )),
StructureBlock::Normal(color) => { 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. // Water / sludge throw away their color bits currently, so we don't set anyway.
StructureBlock::Water => Some(Block::new(BlockKind::Water, Rgb::zero())), 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 // None of these BlockKinds has an orientation, so we just use zero for the other color
// bits. // bits.
StructureBlock::Liana => Some(Block::new(BlockKind::Liana, Rgb::zero())), StructureBlock::Liana => Some(Block::air(SpriteKind::Liana)),
StructureBlock::Fruit => Some(if field.get(pos + structure_pos) % 24 == 0 { StructureBlock::Fruit => Some(if field.get(pos + structure_pos) % 24 == 0 {
Block::new(BlockKind::Beehive, Rgb::zero()) Block::air(SpriteKind::Beehive)
} else if field.get(pos + structure_pos + 1) % 3 == 0 { } else if field.get(pos + structure_pos + 1) % 3 == 0 {
Block::new(BlockKind::Apple, Rgb::zero()) Block::air(SpriteKind::Apple)
} else { } else {
Block::empty() Block::empty()
}), }),
StructureBlock::Coconut => Some(if field.get(pos + structure_pos) % 3 > 0 { StructureBlock::Coconut => Some(if field.get(pos + structure_pos) % 3 > 0 {
Block::empty() Block::empty()
} else { } else {
Block::new(BlockKind::Coconut, Rgb::zero()) Block::air(SpriteKind::Coconut)
}), }),
StructureBlock::Chest => Some(if structure_seed % 10 < 7 { StructureBlock::Chest => Some(if structure_seed % 10 < 7 {
Block::empty() Block::empty()
} else { } else {
Block::new(BlockKind::Chest, Rgb::zero()) Block::air(SpriteKind::Chest)
}), }),
// We interpolate all these BlockKinds as needed. // We interpolate all these BlockKinds as needed.
StructureBlock::TemperateLeaves StructureBlock::TemperateLeaves

View File

@ -12,7 +12,7 @@ use common::{
comp, comp,
generation::{ChunkSupplement, EntityInfo}, generation::{ChunkSupplement, EntityInfo},
lottery::Lottery, lottery::Lottery,
terrain::{Block, BlockKind}, terrain::{Block, BlockKind, SpriteKind},
vol::{BaseVol, ReadVol, RectSizedVol, Vox, WriteVol}, vol::{BaseVol, ReadVol, RectSizedVol, Vox, WriteVol},
}; };
use noise::NoiseFn; use noise::NoiseFn;
@ -98,14 +98,14 @@ pub fn apply_paths_to<'a>(
Vec3::new(offs.x, offs.y, surface_z + z), Vec3::new(offs.x, offs.y, surface_z + z),
if bridge_offset >= 2.0 && path_dist >= 3.0 || z < inset - 1 { if bridge_offset >= 2.0 && path_dist >= 3.0 || z < inset - 1 {
Block::new( Block::new(
BlockKind::Normal, BlockKind::Rock,
noisy_color(index.colors.layer.bridge.into(), 8), noisy_color(index.colors.layer.bridge.into(), 8),
) )
} else { } else {
let path_color = path.surface_color( let path_color = path.surface_color(
col_sample.sub_surface_color.map(|e| (e * 255.0) as u8), 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 { for z in cave_roof - stalagtites..cave_roof {
let _ = vol.set( let _ = vol.set(
Vec3::new(offs.x, offs.y, z), 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)) if RandomField::new(index.seed).chance(wpos2d.into(), 0.001 * difficulty.powf(1.5))
&& cave_base < surface_z as i32 - 25 && cave_base < surface_z as i32 - 25
{ {
let kind = *Lottery::<BlockKind>::load_expect("common.cave_scatter") let kind = *Lottery::<SpriteKind>::load_expect("common.cave_scatter")
.choose_seeded(RandomField::new(index.seed + 1).get(wpos2d.into())); .choose_seeded(RandomField::new(index.seed + 1).get(wpos2d.into()));
let _ = vol.set( let _ = vol.map(Vec3::new(offs.x, offs.y, cave_base), |block| {
Vec3::new(offs.x, offs.y, cave_base), block.with_sprite(kind)
Block::new(kind, Rgb::zero()), });
);
} }
} }
} }

View File

@ -1,6 +1,6 @@
use crate::{column::ColumnSample, sim::SimChunk, util::RandomField, IndexRef, CONFIG}; use crate::{column::ColumnSample, sim::SimChunk, util::RandomField, IndexRef, CONFIG};
use common::{ use common::{
terrain::{Block, BlockKind}, terrain::{Block, SpriteKind},
vol::{BaseVol, ReadVol, RectSizedVol, WriteVol}, vol::{BaseVol, ReadVol, RectSizedVol, WriteVol},
}; };
use noise::NoiseFn; use noise::NoiseFn;
@ -18,7 +18,7 @@ pub fn apply_scatter_to<'a>(
index: IndexRef, index: IndexRef,
chunk: &SimChunk, chunk: &SimChunk,
) { ) {
use BlockKind::*; use SpriteKind::*;
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
// TODO: Add back all sprites we had before // TODO: Add back all sprites we had before
let scatter: &[( let scatter: &[(
@ -282,6 +282,8 @@ pub fn apply_scatter_to<'a>(
Some((128.0, 0.5)), Some((128.0, 0.5)),
) )
}), }),
// Underwater chests
(Chest, true, |_, _| (MUSH_FACT * 0.1, None)),
]; ];
for y in 0..vol.size_xy().y as i32 { 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 underwater = col_sample.water_level > col_sample.alt;
let bk = scatter let kind = scatter
.iter() .iter()
.enumerate() .enumerate()
.find_map(|(i, (bk, is_underwater, f))| { .find_map(|(i, (kind, is_underwater, f))| {
let (density, patch) = f(chunk, col_sample); let (density, patch) = f(chunk, col_sample);
let is_patch = patch let is_patch = patch
.map(|(wavelen, threshold)| { .map(|(wavelen, threshold)| {
@ -324,13 +326,13 @@ pub fn apply_scatter_to<'a>(
.chance(Vec3::new(wpos2d.x, wpos2d.y, 0), density) .chance(Vec3::new(wpos2d.x, wpos2d.y, 0), density)
&& underwater == *is_underwater && underwater == *is_underwater
{ {
Some(*bk) Some(*kind)
} else { } else {
None None
} }
}); });
if let Some(bk) = bk { if let Some(kind) = kind {
let alt = col_sample.alt as i32; let alt = col_sample.alt as i32;
// Find the intersection between ground and air, if there is one near the // 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( let _ = vol.map(Vec3::new(offs.x, offs.y, alt + solid_end), |block| {
Vec3::new(offs.x, offs.y, alt + solid_end), block.with_sprite(kind)
Block::new(bk, Rgb::broadcast(0)), });
);
} }
} }
} }

View File

@ -116,7 +116,7 @@ impl World {
let air = Block::empty(); let air = Block::empty();
let stone = Block::new( let stone = Block::new(
BlockKind::Dense, BlockKind::Rock,
zcache_grid zcache_grid
.get(grid_border + TerrainChunkSize::RECT_SIZE.map(|e| e as i32) / 2) .get(grid_border + TerrainChunkSize::RECT_SIZE.map(|e| e as i32) / 2)
.and_then(|zcache| zcache.as_ref()) .and_then(|zcache| zcache.as_ref())

View File

@ -208,7 +208,7 @@ impl Castle {
let _ = vol.set( let _ = vol.set(
pos, pos,
Block::new( Block::new(
BlockKind::Normal, BlockKind::Earth,
col_sample.sub_surface_color.map(|e| (e * 255.0) as u8), col_sample.sub_surface_color.map(|e| (e * 255.0) as u8),
), ),
); );

View File

@ -14,7 +14,7 @@ use common::{
generation::{ChunkSupplement, EntityInfo}, generation::{ChunkSupplement, EntityInfo},
lottery::Lottery, lottery::Lottery,
store::{Id, Store}, store::{Id, Store},
terrain::{Block, BlockKind, Structure, TerrainChunkSize}, terrain::{Block, BlockKind, SpriteKind, Structure, TerrainChunkSize},
vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol}, vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol},
}; };
use core::{f32, hash::BuildHasherDefault}; use core::{f32, hash::BuildHasherDefault};
@ -564,13 +564,7 @@ impl Floor {
let empty = BlockMask::new(Block::empty(), 1); let empty = BlockMask::new(Block::empty(), 1);
let make_staircase = move |pos: Vec3<i32>, radius: f32, inner_radius: f32, stretch: f32| { let make_staircase = move |pos: Vec3<i32>, radius: f32, inner_radius: f32, stretch: f32| {
let stone = BlockMask::new( let stone = BlockMask::new(Block::new(BlockKind::Rock, colors.stone.into()), 5);
Block::new(
BlockKind::Normal,
/* Rgb::new(150, 150, 175) */ colors.stone.into(),
),
5,
);
if (pos.xy().magnitude_squared() as f32) < inner_radius.powf(2.0) { if (pos.xy().magnitude_squared() as f32) < inner_radius.powf(2.0) {
stone stone
@ -599,15 +593,14 @@ impl Floor {
let floor_sprite = if RandomField::new(7331).chance(Vec3::from(pos), 0.00005) { let floor_sprite = if RandomField::new(7331).chance(Vec3::from(pos), 0.00005) {
BlockMask::new( BlockMask::new(
Block::new( Block::air(
match (RandomField::new(1337).get(Vec3::from(pos)) / 2) % 20 { match (RandomField::new(1337).get(Vec3::from(pos)) / 2) % 20 {
0 => BlockKind::Apple, 0 => SpriteKind::Apple,
1 => BlockKind::VeloriteFrag, 1 => SpriteKind::VeloriteFrag,
2 => BlockKind::Velorite, 2 => SpriteKind::Velorite,
3..=8 => BlockKind::Mushroom, 3..=8 => SpriteKind::Mushroom,
_ => BlockKind::ShortGrass, _ => SpriteKind::ShortGrass,
}, },
Rgb::white(),
), ),
1, 1,
) )
@ -616,7 +609,7 @@ impl Floor {
{ {
let room = &self.rooms[*room]; let room = &self.rooms[*room];
if RandomField::new(room.seed).chance(Vec3::from(pos), room.loot_density * 0.5) { 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::air(SpriteKind::Chest), 1)
} else { } else {
empty empty
} }

View File

@ -8,7 +8,7 @@ use crate::{
}; };
use common::{ use common::{
make_case_elim, make_case_elim,
terrain::{Block, BlockKind}, terrain::{Block, BlockKind, SpriteKind},
vol::Vox, vol::Vox,
}; };
use rand::prelude::*; use rand::prelude::*;
@ -145,7 +145,7 @@ pub struct Attr {
pub mansard: i32, pub mansard: i32,
pub pillar: Pillar, pub pillar: Pillar,
pub levels: i32, pub levels: i32,
pub window: BlockKind, pub window: SpriteKind,
} }
impl Attr { impl Attr {
@ -169,10 +169,10 @@ impl Attr {
}, },
levels: rng.gen_range(1, 3), levels: rng.gen_range(1, 3),
window: match rng.gen_range(0, 4) { window: match rng.gen_range(0, 4) {
0 => BlockKind::Window1, 0 => SpriteKind::Window1,
1 => BlockKind::Window2, 1 => SpriteKind::Window2,
2 => BlockKind::Window3, 2 => SpriteKind::Window3,
_ => BlockKind::Window4, _ => SpriteKind::Window4,
}, },
} }
} }
@ -267,24 +267,13 @@ impl Archetype for House {
let profile = Vec2::new(bound_offset.x, z); 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 make_block = |(r, g, b)| {
let nz = self let nz = self
.noise .noise
.get(Vec3::new(center_offset.x, center_offset.y, z * 8)); .get(Vec3::new(center_offset.x, center_offset.y, z * 8));
BlockMask::new( BlockMask::new(
Block::new( Block::new(
BlockKind::Normal, BlockKind::Misc,
// TODO: Clarify exactly how this affects the color. // TODO: Clarify exactly how this affects the color.
Rgb::new(r, g, b) Rgb::new(r, g, b)
.map(|e: u8| e.saturating_add((nz & 0x0F) as u8).saturating_sub(8)), .map(|e: u8| e.saturating_add((nz & 0x0F) as u8).saturating_sub(8)),
@ -307,10 +296,15 @@ impl Archetype for House {
let empty = BlockMask::nothing(); let empty = BlockMask::nothing();
let internal = BlockMask::new(Block::empty(), internal_layer); let internal = BlockMask::new(Block::empty(), internal_layer);
let end_window = BlockMask::new( let end_window = BlockMask::new(
Block::new(attr.window, make_meta(ori.flip())), Block::air(attr.window)
.with_ori(match ori {
Ori::East => 2,
Ori::North => 0,
})
.unwrap(),
structural_layer, structural_layer,
); );
let fire = BlockMask::new(Block::new(BlockKind::Ember, Rgb::white()), foundation_layer); let fire = BlockMask::new(Block::air(SpriteKind::Ember), foundation_layer);
let storey_height = 6; let storey_height = 6;
let storey = ((z - 1) / storey_height).min(attr.levels - 1); let storey = ((z - 1) / storey_height).min(attr.levels - 1);
@ -441,14 +435,18 @@ impl Archetype for House {
// Doors on first floor only // Doors on first floor only
if profile.y == foundation_height + 1 { if profile.y == foundation_height + 1 {
BlockMask::new( BlockMask::new(
Block::new( Block::air(SpriteKind::Door)
BlockKind::Door, .with_ori(
if bound_offset.x == (width - 1) / 2 { match ori {
make_meta(ori.flip()) Ori::East => 2,
Ori::North => 0,
} + if bound_offset.x == (width - 1) / 2 {
0
} else { } else {
make_meta(ori.flip()) + Rgb::new(4, 0, 0) 4
}, },
), )
.unwrap(),
structural_layer, structural_layer,
) )
} else { } else {
@ -542,26 +540,26 @@ impl Archetype for House {
z + 100, z + 100,
)) % 11 )) % 11
{ {
0 => BlockKind::Planter, 0 => SpriteKind::Planter,
1 => BlockKind::ChairSingle, 1 => SpriteKind::ChairSingle,
2 => BlockKind::ChairDouble, 2 => SpriteKind::ChairDouble,
3 => BlockKind::CoatRack, 3 => SpriteKind::CoatRack,
4 => { 4 => {
if rng.gen_range(0, 8) == 0 { if rng.gen_range(0, 8) == 0 {
BlockKind::Chest SpriteKind::Chest
} else { } else {
BlockKind::Crate SpriteKind::Crate
} }
}, },
6 => BlockKind::DrawerMedium, 6 => SpriteKind::DrawerMedium,
7 => BlockKind::DrawerSmall, 7 => SpriteKind::DrawerSmall,
8 => BlockKind::TableSide, 8 => SpriteKind::TableSide,
9 => BlockKind::WardrobeSingle, 9 => SpriteKind::WardrobeSingle,
_ => BlockKind::Pot, _ => SpriteKind::Pot,
}; };
return Some(BlockMask::new( return Some(BlockMask::new(
Block::new(furniture, Rgb::new(edge_ori, 0, 0)), Block::air(furniture).with_ori(edge_ori).unwrap(),
internal_layer, internal_layer,
)); ));
} else { } else {
@ -584,13 +582,13 @@ impl Archetype for House {
.get(Vec3::new(center_offset.x, center_offset.y, z + 100)) .get(Vec3::new(center_offset.x, center_offset.y, z + 100))
% 4 % 4
{ {
0 => BlockKind::HangingSign, 0 => SpriteKind::HangingSign,
1 | 2 | 3 => BlockKind::HangingBasket, 1 | 2 | 3 => SpriteKind::HangingBasket,
_ => BlockKind::DungeonWallDecor, _ => SpriteKind::DungeonWallDecor,
}; };
Some(BlockMask::new( Some(BlockMask::new(
Block::new(ornament, Rgb::new((edge_ori + 4) % 8, 0, 0)), Block::air(ornament).with_ori((edge_ori + 4) % 8).unwrap(),
internal_layer, internal_layer,
)) ))
} else { } else {

View File

@ -6,7 +6,7 @@ use crate::{
}; };
use common::{ use common::{
make_case_elim, make_case_elim,
terrain::{Block, BlockKind}, terrain::{Block, BlockKind, SpriteKind},
vol::Vox, vol::Vox,
}; };
use rand::prelude::*; use rand::prelude::*;
@ -134,23 +134,8 @@ impl Archetype for Keep {
let important_layer = normal_layer + 1; let important_layer = normal_layer + 1;
let internal_layer = important_layer + 1; let internal_layer = important_layer + 1;
let make_meta = |ori| { let make_block =
Rgb::new( |r, g, b| BlockMask::new(Block::new(BlockKind::Rock, Rgb::new(r, g, b)), normal_layer);
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 brick_tex_pos = (pos + Vec3::new(pos.z, pos.z, 0)) / Vec3::new(2, 2, 1); 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; 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, stone_color.2 + brick_tex,
); );
let window = BlockMask::new( let window = BlockMask::new(
Block::new(BlockKind::Window1, make_meta(ori.flip())), Block::air(SpriteKind::Window1)
.with_ori(match ori {
Ori::East => 2,
Ori::North => 0,
})
.unwrap(),
normal_layer, normal_layer,
); );
let floor = make_block( let floor = make_block(
@ -182,7 +172,7 @@ impl Archetype for Keep {
let empty = BlockMask::nothing(); let empty = BlockMask::nothing();
let make_staircase = move |pos: Vec3<i32>, radius: f32, inner_radius: f32, stretch: f32| { let make_staircase = move |pos: Vec3<i32>, 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) { if (pos.xy().magnitude_squared() as f32) < inner_radius.powf(2.0) {
stone stone

View File

@ -19,7 +19,7 @@ use common::{
path::Path, path::Path,
spiral::Spiral2d, spiral::Spiral2d,
store::{Id, Store}, store::{Id, Store},
terrain::{Block, BlockKind, TerrainChunkSize}, terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize},
vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol}, vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol},
}; };
use fxhash::FxHasher64; use fxhash::FxHasher64;
@ -611,7 +611,7 @@ impl Settlement {
} }
{ {
let mut surface_block = None; let mut surface_sprite = None;
let roll = let roll =
|seed, n| self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, seed * 5)) % n; |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) 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)) || (roll(0, 2000) == 0 && col_sample.path.map(|(dist, _, _, _)| dist > 20.0).unwrap_or(true))
{ {
surface_block = surface_sprite = Some(SpriteKind::StreetLamp);
Some(Block::new(BlockKind::StreetLamp, Rgb::white()));
} }
} }
@ -676,41 +675,38 @@ impl Settlement {
if in_furrow { if in_furrow {
if roll(0, 5) == 0 { if roll(0, 5) == 0 {
surface_block = match crop { surface_sprite = match crop {
Crop::Corn => Some(BlockKind::Corn), Crop::Corn => Some(SpriteKind::Corn),
Crop::Wheat if roll(1, 2) == 0 => { 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 => { Crop::Cabbage if roll(2, 2) == 0 => {
Some(BlockKind::Cabbage) Some(SpriteKind::Cabbage)
}, },
Crop::Pumpkin if roll(3, 2) == 0 => { Crop::Pumpkin if roll(3, 2) == 0 => {
Some(BlockKind::Pumpkin) Some(SpriteKind::Pumpkin)
}, },
Crop::Flax if roll(4, 2) == 0 => Some(BlockKind::Flax), Crop::Flax if roll(4, 2) == 0 => Some(SpriteKind::Flax),
Crop::Carrot if roll(5, 2) == 0 => Some(BlockKind::Carrot), Crop::Carrot if roll(5, 2) == 0 => Some(SpriteKind::Carrot),
Crop::Tomato if roll(6, 2) == 0 => Some(BlockKind::Tomato), Crop::Tomato if roll(6, 2) == 0 => Some(SpriteKind::Tomato),
Crop::Radish if roll(7, 2) == 0 => Some(BlockKind::Radish), Crop::Radish if roll(7, 2) == 0 => Some(SpriteKind::Radish),
Crop::Turnip if roll(8, 2) == 0 => Some(BlockKind::Turnip), Crop::Turnip if roll(8, 2) == 0 => Some(SpriteKind::Turnip),
Crop::Sunflower => Some(BlockKind::Sunflower), Crop::Sunflower => Some(SpriteKind::Sunflower),
_ => None, _ => surface_sprite,
} }
.or_else(|| { .or_else(|| {
if roll(9, 400) == 0 { if roll(9, 400) == 0 {
Some(BlockKind::Scarecrow) Some(SpriteKind::Scarecrow)
} else { } else {
None None
} }
}) });
.map(|kind| Block::new(kind, Rgb::white()));
} }
} else if roll(0, 20) == 0 { } else if roll(0, 20) == 0 {
surface_block = surface_sprite = Some(SpriteKind::ShortGrass);
Some(Block::new(BlockKind::ShortGrass, Rgb::white()));
} else if roll(1, 30) == 0 { } else if roll(1, 30) == 0 {
surface_block = surface_sprite = Some(SpriteKind::MediumGrass);
Some(Block::new(BlockKind::MediumGrass, Rgb::white()));
} }
Some(if in_furrow { dirt } else { mound }) 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 pos = Vec3::new(offs.x, offs.y, surface_z + z);
let block = vol.get(pos).ok().copied().unwrap_or_else(Block::empty); let block = vol.get(pos).ok().copied().unwrap_or_else(Block::empty);
if block.kind() == BlockKind::Air { if block.is_empty() {
break; break;
} }
if let (0, Some(block)) = (z, surface_block) { if let (0, Some(sprite)) = (z, surface_sprite) {
let _ = vol.set(pos, block); let _ = vol.set(
pos,
if block.is_fluid() {
block
} else {
Block::empty()
}
.with_sprite(sprite),
);
} else if z >= 0 { } else if z >= 0 {
if block.kind() != BlockKind::Water { if block.kind() != BlockKind::Water {
let _ = vol.set(pos, Block::empty()); let _ = vol.set(pos, Block::empty());
@ -745,7 +749,7 @@ impl Settlement {
} else { } else {
let _ = vol.set( let _ = vol.set(
pos, 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) { if dist / WayKind::Wall.width() < ((1.0 - z as f32 / 12.0) * 2.0).min(1.0) {
let _ = vol.set( let _ = vol.set(
Vec3::new(offs.x, offs.y, surface_z + z), 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 { for z in -2..16 {
let _ = vol.set( let _ = vol.set(
Vec3::new(offs.x, offs.y, surface_z + z), 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()),
); );
} }
} }