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
- Figure meshing no longer blocks the main thread.
- Overhauled persistence layer including no longer storing serialized JSON items in the database
- Overhauled representation of blocks to permit fluid and sprite coexistence
### Removed

13
Cargo.lock generated
View File

@ -2593,6 +2593,17 @@ dependencies = [
"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]]
name = "num-integer"
version = "0.1.43"
@ -4639,6 +4650,8 @@ dependencies = [
"indexmap",
"lazy_static",
"notify",
"num-derive",
"num-traits",
"parking_lot 0.9.0",
"rand 0.7.3",
"rayon",

View File

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

View File

@ -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 }

View File

@ -16,7 +16,7 @@ fn criterion_benchmark(c: &mut Criterion) {
// Setup: Create chunk and fill it (dense) for z in [140, 220).
let mut chunk = TerrainChunk::new(
MIN_Z,
Block::new(BlockKind::Dense, Default::default()),
Block::new(BlockKind::Rock, Rgb::zero()),
Block::empty(),
TerrainChunkMeta::void(),
);
@ -29,7 +29,7 @@ fn criterion_benchmark(c: &mut Criterion) {
),
) {
chunk
.set(pos, Block::new(BlockKind::Dense, Default::default()))
.set(pos, Block::new(BlockKind::Rock, Rgb::zero()))
.unwrap();
}
@ -118,7 +118,7 @@ fn criterion_benchmark(c: &mut Criterion) {
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,
Light,
MakeBlock,
MakeSprite,
Motd,
Object,
Players,
@ -105,6 +106,7 @@ pub static CHAT_COMMANDS: &[ChatCommand] = &[
ChatCommand::Lantern,
ChatCommand::Light,
ChatCommand::MakeBlock,
ChatCommand::MakeSprite,
ChatCommand::Motd,
ChatCommand::Object,
ChatCommand::Players,
@ -162,6 +164,11 @@ lazy_static! {
.cloned()
.collect();
static ref SPRITE_KINDS: Vec<String> = terrain::sprite::SPRITE_KINDS
.keys()
.cloned()
.collect();
/// List of item specifiers. Useful for tab completing
static ref ITEM_SPECS: Vec<String> = {
let path = assets::ASSETS_PATH.join("common").join("items");
@ -306,7 +313,12 @@ impl ChatCommand {
),
ChatCommand::MakeBlock => cmd(
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,
),
ChatCommand::Motd => cmd(
@ -422,6 +434,7 @@ impl ChatCommand {
ChatCommand::Lantern => "lantern",
ChatCommand::Light => "light",
ChatCommand::MakeBlock => "make_block",
ChatCommand::MakeSprite => "make_sprite",
ChatCommand::Motd => "motd",
ChatCommand::Object => "object",
ChatCommand::Players => "players",

View File

@ -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<Self> {
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::<String>::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::<String>::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,
}))
}

View File

@ -434,7 +434,7 @@ where
V: BaseVol<Vox = Block> + 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))

View File

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

View File

@ -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,
@ -619,7 +619,7 @@ impl<'a> System<'a> for Sys {
},
Collider::Point => {
let (dist, block) = terrain.ray(pos.0, pos.0 + pos_delta)
.until(|vox| !vox.is_air() && !vox.is_fluid())
.until(|vox| !vox.is_air() && !vox.is_liquid())
.ignore_error().cast();
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))
.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 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 = 0xFE,
}
);
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 {
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(()) }
}
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)]
#[repr(packed)]
pub struct Block {
kind: BlockKind,
color: [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 }
attr: [u8; 3],
}
impl Deref for Block {
@ -528,11 +105,141 @@ 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<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)]

View File

@ -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;

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(),
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,
})
}
}

View File

@ -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<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

View File

@ -12,9 +12,9 @@ use common::{
npc::{self, get_npc_name},
state::TimeOfDay,
sync::{Uid, WorldSyncExt},
terrain::{Block, BlockKind, TerrainChunkSize},
terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize},
util::Dir,
vol::RectVolSize,
vol::{RectVolSize, Vox},
LoadoutBuilder,
};
use rand::Rng;
@ -87,6 +87,7 @@ fn get_handler(cmd: &ChatCommand) -> CommandHandler {
ChatCommand::Lantern => handle_lantern,
ChatCommand::Light => handle_light,
ChatCommand::MakeBlock => handle_make_block,
ChatCommand::MakeSprite => handle_make_sprite,
ChatCommand::Motd => handle_motd,
ChatCommand::Object => handle_object,
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(
server: &mut Server,
client: EcsEntity,

View File

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

View File

@ -8,8 +8,7 @@ use common::{
msg::ServerMsg,
recipe::default_recipe_book,
sync::{Uid, WorldSyncExt},
terrain::block::Block,
vol::{ReadVol, Vox},
vol::ReadVol,
};
use comp::LightEmitter;
use rand::Rng;
@ -132,7 +131,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 {

View File

@ -97,7 +97,7 @@ fn calc_light<V: RectRasterableVol<Vox = Block> + 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<V: RectRasterableVol<Vox = Block> + 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<Vox = Block> + 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()
}),
))
}

View File

@ -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())

View File

@ -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<TerrainPipeline>,
fluid_model: Option<Model<FluidPipeline>>,
col_lights: guillotiere::AllocId,
sprite_instances: HashMap<(BlockKind, usize), Instances<SpriteInstance>>,
sprite_instances: HashMap<(SpriteKind, usize), Instances<SpriteInstance>>,
locals: Consts<TerrainLocals>,
pub blocks_of_interest: BlocksOfInterest,
@ -75,7 +75,7 @@ struct MeshWorkerResponse {
opaque_mesh: Mesh<TerrainPipeline>,
fluid_mesh: Mesh<FluidPipeline>,
col_lights_info: ColLightInfo,
sprite_instances: HashMap<(BlockKind, usize), Vec<SpriteInstance>>,
sprite_instances: HashMap<(SpriteKind, usize), Vec<SpriteInstance>>,
started_tick: u64,
blocks_of_interest: BlocksOfInterest,
}
@ -94,7 +94,7 @@ struct SpriteModelConfig<Model> {
#[derive(Deserialize)]
/// Configuration data for a group of sprites (currently associated with a
/// particular BlockKind).
/// particular SpriteKind).
struct SpriteConfig<Model> {
/// 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<Model> {
/// Configuration data for all sprite models.
///
/// 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.
#[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,
chunk: Arc<TerrainChunk>,
range: Aabb<i32>,
sprite_data: &HashMap<(BlockKind, usize), Vec<SpriteData>>,
sprite_data: &HashMap<(SpriteKind, usize), Vec<SpriteData>>,
sprite_config: &SpriteSpec,
) -> MeshWorkerResponse {
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 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<V: RectRasterableVol> {
mesh_todo: HashMap<Vec2<i32>, ChunkMeshState>,
// GPU data
sprite_data: Arc<HashMap<(BlockKind, usize), Vec<SpriteData>>>,
sprite_data: Arc<HashMap<(SpriteKind, usize), Vec<SpriteData>>>,
sprite_col_lights: Texture<ColLightFmt>,
col_lights: Texture<ColLightFmt>,
waves: Texture,
@ -254,7 +259,7 @@ impl<V: RectRasterableVol> Terrain<V> {
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;

View File

@ -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 {

View File

@ -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;

View File

@ -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::air(SpriteKind::Liana)),
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 {
Block::new(BlockKind::Apple, Rgb::zero())
Block::air(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::air(SpriteKind::Coconut)
}),
StructureBlock::Chest => Some(if structure_seed % 10 < 7 {
Block::empty()
} else {
Block::new(BlockKind::Chest, Rgb::zero())
Block::air(SpriteKind::Chest)
}),
// We interpolate all these BlockKinds as needed.
StructureBlock::TemperateLeaves

View File

@ -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::<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()));
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)
});
}
}
}

View File

@ -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, |_, _| (MUSH_FACT * 0.1, 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)
});
}
}
}

View File

@ -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())

View File

@ -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),
),
);

View File

@ -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<i32>, 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::air(
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::air(SpriteKind::Chest), 1)
} else {
empty
}

View File

@ -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,15 @@ 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::air(attr.window)
.with_ori(match ori {
Ori::East => 2,
Ori::North => 0,
})
.unwrap(),
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 = ((z - 1) / storey_height).min(attr.levels - 1);
@ -441,14 +435,18 @@ 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::air(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
},
),
)
.unwrap(),
structural_layer,
)
} else {
@ -542,26 +540,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::air(furniture).with_ori(edge_ori).unwrap(),
internal_layer,
));
} else {
@ -584,13 +582,13 @@ 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::air(ornament).with_ori((edge_ori + 4) % 8).unwrap(),
internal_layer,
))
} else {

View File

@ -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::air(SpriteKind::Window1)
.with_ori(match ori {
Ori::East => 2,
Ori::North => 0,
})
.unwrap(),
normal_layer,
);
let floor = make_block(
@ -182,7 +172,7 @@ impl Archetype for Keep {
let empty = BlockMask::nothing();
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) {
stone

View File

@ -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()),
);
}
}