2020-09-20 09:09:29 +00:00
|
|
|
use super::SpriteKind;
|
2020-08-28 07:21:22 +00:00
|
|
|
use crate::{make_case_elim, vol::Vox};
|
2020-06-27 18:39:16 +00:00
|
|
|
use enum_iterator::IntoEnumIterator;
|
2020-06-27 23:37:14 +00:00
|
|
|
use lazy_static::lazy_static;
|
2020-09-20 09:09:29 +00:00
|
|
|
use num_derive::FromPrimitive;
|
|
|
|
use num_traits::FromPrimitive;
|
2020-06-27 23:37:14 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use std::{collections::HashMap, convert::TryFrom, fmt, ops::Deref};
|
2019-01-23 20:01:58 +00:00
|
|
|
use vek::*;
|
|
|
|
|
2020-08-28 07:21:22 +00:00
|
|
|
make_case_elim!(
|
|
|
|
block_kind,
|
2020-09-20 09:09:29 +00:00
|
|
|
#[derive(
|
|
|
|
Copy,
|
|
|
|
Clone,
|
|
|
|
Debug,
|
|
|
|
Hash,
|
|
|
|
Eq,
|
|
|
|
PartialEq,
|
|
|
|
Serialize,
|
|
|
|
Deserialize,
|
|
|
|
IntoEnumIterator,
|
|
|
|
FromPrimitive,
|
|
|
|
)]
|
2020-08-28 07:21:22 +00:00
|
|
|
#[repr(u8)]
|
|
|
|
pub enum BlockKind {
|
2020-09-20 09:09:29 +00:00
|
|
|
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.
|
2020-09-21 20:10:32 +00:00
|
|
|
Misc = 0xFE,
|
2020-08-28 07:21:22 +00:00
|
|
|
}
|
|
|
|
);
|
2019-08-14 21:28:37 +00:00
|
|
|
|
2020-09-20 09:09:29 +00:00
|
|
|
impl BlockKind {
|
2020-09-21 20:10:32 +00:00
|
|
|
#[inline]
|
2020-09-20 10:07:13 +00:00
|
|
|
pub const fn is_air(&self) -> bool { matches!(self, BlockKind::Air) }
|
2020-09-20 09:09:29 +00:00
|
|
|
|
2020-09-21 20:10:32 +00:00
|
|
|
/// 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]
|
2020-09-20 09:09:29 +00:00
|
|
|
pub const fn is_fluid(&self) -> bool { *self as u8 & 0xF0 == 0x00 }
|
|
|
|
|
2020-09-21 20:10:32 +00:00
|
|
|
#[inline]
|
2020-09-20 10:07:13 +00:00
|
|
|
pub const fn is_liquid(&self) -> bool { self.is_fluid() && !self.is_air() }
|
2020-09-20 09:09:29 +00:00
|
|
|
|
2020-09-21 20:10:32 +00:00
|
|
|
/// Determine whether the block is filled (i.e: fully solid). Right now,
|
|
|
|
/// this is the opposite of being a fluid.
|
|
|
|
#[inline]
|
2020-09-20 09:09:29 +00:00
|
|
|
pub const fn is_filled(&self) -> bool { !self.is_fluid() }
|
2020-09-21 20:10:32 +00:00
|
|
|
|
|
|
|
/// Determine whether the block has an RGB color storaged in the attribute
|
|
|
|
/// fields.
|
|
|
|
#[inline]
|
|
|
|
pub const fn has_color(&self) -> bool { self.is_filled() }
|
2020-09-20 09:09:29 +00:00
|
|
|
}
|
|
|
|
|
2020-06-27 18:39:16 +00:00
|
|
|
impl fmt::Display for BlockKind {
|
2020-06-27 23:37:14 +00:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) }
|
2020-06-27 18:39:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
lazy_static! {
|
|
|
|
pub static ref BLOCK_KINDS: HashMap<String, BlockKind> = BlockKind::into_enum_iter()
|
|
|
|
.map(|bk| (bk.to_string(), bk))
|
|
|
|
.collect();
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> TryFrom<&'a str> for BlockKind {
|
|
|
|
type Error = ();
|
|
|
|
|
2020-06-27 23:37:14 +00:00
|
|
|
fn try_from(s: &'a str) -> Result<Self, Self::Error> { BLOCK_KINDS.get(s).copied().ok_or(()) }
|
2020-06-27 18:39:16 +00:00
|
|
|
}
|
|
|
|
|
2020-09-20 09:09:29 +00:00
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
|
|
pub struct Block {
|
|
|
|
kind: BlockKind,
|
|
|
|
attr: [u8; 3],
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Deref for Block {
|
|
|
|
type Target = BlockKind;
|
|
|
|
|
|
|
|
fn deref(&self) -> &Self::Target { &self.kind }
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Vox for Block {
|
|
|
|
fn empty() -> Self {
|
|
|
|
Self {
|
|
|
|
kind: BlockKind::Air,
|
|
|
|
attr: [0; 3],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_empty(&self) -> bool { *self == Block::empty() }
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Block {
|
2020-04-19 03:56:14 +00:00
|
|
|
pub const MAX_HEIGHT: f32 = 3.0;
|
|
|
|
|
2020-09-20 09:09:29 +00:00
|
|
|
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]
|
|
|
|
},
|
2019-09-25 14:56:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-21 20:10:32 +00:00
|
|
|
pub const fn air(sprite: SpriteKind) -> Self {
|
|
|
|
Self {
|
|
|
|
kind: BlockKind::Air,
|
|
|
|
attr: [sprite as u8, 0, 0],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2020-09-20 09:09:29 +00:00
|
|
|
pub fn get_color(&self) -> Option<Rgb<u8>> {
|
2020-09-21 20:10:32 +00:00
|
|
|
if self.has_color() {
|
2020-09-20 09:09:29 +00:00
|
|
|
Some(self.attr.into())
|
|
|
|
} else {
|
|
|
|
None
|
2019-08-14 21:28:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-21 20:10:32 +00:00
|
|
|
#[inline]
|
2020-09-20 09:09:29 +00:00
|
|
|
pub fn get_sprite(&self) -> Option<SpriteKind> {
|
|
|
|
if !self.is_filled() {
|
|
|
|
SpriteKind::from_u8(self.attr[0])
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-21 20:10:32 +00:00
|
|
|
#[inline]
|
2020-09-20 09:09:29 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2019-08-14 21:28:37 +00:00
|
|
|
|
2020-09-21 20:10:32 +00:00
|
|
|
#[inline]
|
2020-06-29 12:43:16 +00:00
|
|
|
pub fn get_glow(&self) -> Option<u8> {
|
2020-08-12 14:10:12 +00:00
|
|
|
// TODO: When we have proper volumetric lighting
|
2020-09-20 09:09:29 +00:00
|
|
|
// match self.get_sprite()? {
|
|
|
|
// SpriteKind::StreetLamp | SpriteKind::StreetLampTall => Some(20),
|
|
|
|
// SpriteKind::Velorite | SpriteKind::VeloriteFrag => Some(10),
|
2020-08-12 14:10:12 +00:00
|
|
|
// _ => None,
|
|
|
|
// }
|
|
|
|
None
|
2020-06-29 12:43:16 +00:00
|
|
|
}
|
|
|
|
|
2020-09-21 20:10:32 +00:00
|
|
|
#[inline]
|
2019-08-16 11:38:59 +00:00
|
|
|
pub fn is_solid(&self) -> bool {
|
2020-09-20 09:09:29 +00:00
|
|
|
self.get_sprite()
|
|
|
|
.map(|s| s.solid_height().is_some())
|
|
|
|
.unwrap_or(true)
|
2019-08-16 11:38:59 +00:00
|
|
|
}
|
2019-09-25 21:17:43 +00:00
|
|
|
|
2020-09-21 20:10:32 +00:00
|
|
|
#[inline]
|
2020-07-04 23:55:13 +00:00
|
|
|
pub fn is_explodable(&self) -> bool {
|
2020-09-20 09:09:29 +00:00
|
|
|
match self.kind() {
|
|
|
|
BlockKind::Leaves | BlockKind::Grass | BlockKind::WeakRock => true,
|
2020-09-21 20:10:32 +00:00
|
|
|
// 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
|
2020-09-20 09:09:29 +00:00
|
|
|
_ => true,
|
2020-04-18 20:26:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-21 20:10:32 +00:00
|
|
|
#[inline]
|
2019-09-25 21:17:43 +00:00
|
|
|
pub fn is_collectible(&self) -> bool {
|
2020-09-20 09:09:29 +00:00
|
|
|
self.get_sprite()
|
|
|
|
.map(|s| s.is_collectible())
|
|
|
|
.unwrap_or(false)
|
2019-09-25 21:17:43 +00:00
|
|
|
}
|
2019-08-14 21:28:37 +00:00
|
|
|
|
2020-09-21 20:10:32 +00:00
|
|
|
#[inline]
|
2020-09-20 09:09:29 +00:00
|
|
|
pub fn is_opaque(&self) -> bool { self.kind().is_filled() }
|
2019-01-14 18:49:53 +00:00
|
|
|
|
2020-09-21 20:10:32 +00:00
|
|
|
#[inline]
|
2020-09-20 09:09:29 +00:00
|
|
|
pub fn solid_height(&self) -> f32 {
|
|
|
|
self.get_sprite()
|
|
|
|
.map(|s| s.solid_height().unwrap_or(0.0))
|
|
|
|
.unwrap_or(1.0)
|
2019-01-23 20:01:58 +00:00
|
|
|
}
|
|
|
|
|
2020-09-21 20:10:32 +00:00
|
|
|
#[inline]
|
2020-09-20 09:09:29 +00:00
|
|
|
pub fn kind(&self) -> BlockKind { self.kind }
|
|
|
|
|
|
|
|
/// If this block is a fluid, replace its sprite.
|
2020-09-21 20:10:32 +00:00
|
|
|
#[inline]
|
2020-09-20 09:09:29 +00:00
|
|
|
pub fn with_sprite(mut self, sprite: SpriteKind) -> Self {
|
|
|
|
if !self.is_filled() {
|
|
|
|
self.attr[0] = sprite as u8;
|
2019-01-23 20:01:58 +00:00
|
|
|
}
|
2020-09-20 09:09:29 +00:00
|
|
|
self
|
2019-01-23 20:01:58 +00:00
|
|
|
}
|
2019-05-31 20:37:11 +00:00
|
|
|
|
2020-09-20 10:07:13 +00:00
|
|
|
/// If this block can have orientation, give it a new orientation.
|
2020-09-21 20:10:32 +00:00
|
|
|
#[inline]
|
|
|
|
pub fn with_ori(mut self, ori: u8) -> Option<Self> {
|
2020-09-20 09:09:29 +00:00
|
|
|
if self.get_sprite().map(|s| s.has_ori()).unwrap_or(false) {
|
|
|
|
self.attr[1] = (self.attr[1] & !0b111) | (ori & 0b111);
|
2020-09-21 20:10:32 +00:00
|
|
|
Some(self)
|
|
|
|
} else {
|
|
|
|
None
|
2020-04-20 14:50:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-20 09:09:29 +00:00
|
|
|
/// Remove the terrain sprite or solid aspects of a block
|
2020-09-21 20:10:32 +00:00
|
|
|
#[inline]
|
2020-09-20 10:07:13 +00:00
|
|
|
pub fn into_vacant(self) -> Self {
|
2020-09-20 09:09:29 +00:00
|
|
|
if self.is_fluid() {
|
|
|
|
Block::new(self.kind(), Rgb::zero())
|
|
|
|
} else {
|
|
|
|
Block::empty()
|
2019-01-14 18:49:53 +00:00
|
|
|
}
|
|
|
|
}
|
2019-08-14 21:28:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn block_size() {
|
|
|
|
assert_eq!(std::mem::size_of::<BlockKind>(), 1);
|
|
|
|
assert_eq!(std::mem::size_of::<Block>(), 4);
|
2019-01-14 18:49:53 +00:00
|
|
|
}
|
|
|
|
}
|