veloren/common/src/terrain/block.rs

294 lines
8.5 KiB
Rust
Raw Normal View History

use super::SpriteKind;
use crate::make_case_elim;
2020-06-27 18:39:16 +00:00
use enum_iterator::IntoEnumIterator;
2020-12-12 01:45:46 +00:00
use hashbrown::HashMap;
2020-06-27 23:37:14 +00:00
use lazy_static::lazy_static;
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
2020-06-27 23:37:14 +00:00
use serde::{Deserialize, Serialize};
2020-12-12 01:45:46 +00:00
use std::{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,
#[derive(
Copy,
Clone,
Debug,
Hash,
Eq,
PartialEq,
Serialize,
Deserialize,
IntoEnumIterator,
FromPrimitive,
)]
2020-08-28 07:21:22 +00:00
#[repr(u8)]
pub enum BlockKind {
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
2020-11-09 15:06:37 +00:00
Snow = 0x21,
// 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
}
);
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-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]
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-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]
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-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
}
#[derive(Copy, Clone, Debug, Eq, Hash, 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 Block {
pub const MAX_HEIGHT: f32 = 3.0;
#[inline]
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
}
}
#[inline]
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],
}
}
2020-11-10 15:27:52 +00:00
#[inline]
pub const fn empty() -> Self { Self::air(SpriteKind::Empty) }
2020-09-27 15:06:46 +00:00
/// TODO: See if we can generalize this somehow.
#[inline]
pub const fn water(sprite: SpriteKind) -> Self {
Self {
kind: BlockKind::Water,
attr: [sprite as u8, 0, 0],
}
}
2020-09-21 20:10:32 +00:00
#[inline]
pub fn get_color(&self) -> Option<Rgb<u8>> {
2020-09-21 20:10:32 +00:00
if self.has_color() {
Some(self.attr.into())
} else {
None
}
}
2020-09-21 20:10:32 +00:00
#[inline]
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]
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
}
}
2020-09-21 20:10:32 +00:00
#[inline]
pub fn get_glow(&self) -> Option<u8> {
2020-11-21 03:05:09 +00:00
match self.get_sprite()? {
2020-11-22 02:00:22 +00:00
SpriteKind::StreetLamp | SpriteKind::StreetLampTall => Some(24),
SpriteKind::Ember => Some(20),
SpriteKind::WallLamp => Some(16),
2021-02-07 00:45:30 +00:00
SpriteKind::WallLampSmall => Some(16),
SpriteKind::WallSconce => Some(16),
SpriteKind::FireBowlGround => Some(16),
2020-11-22 02:00:22 +00:00
SpriteKind::Velorite | SpriteKind::VeloriteFrag => Some(6),
2020-12-05 18:00:29 +00:00
SpriteKind::CaveMushroom => Some(12),
SpriteKind::Amethyst
| SpriteKind::Ruby
| SpriteKind::Sapphire
| SpriteKind::Diamond
| SpriteKind::Emerald
| SpriteKind::Topaz
| SpriteKind::AmethystSmall
| SpriteKind::TopazSmall
| SpriteKind::DiamondSmall
| SpriteKind::RubySmall
| SpriteKind::EmeraldSmall
| SpriteKind::SapphireSmall => Some(3),
2021-02-08 11:10:47 +00:00
SpriteKind::Lantern => Some(24),
2020-11-21 03:05:09 +00:00
_ => None,
}
}
2021-01-06 20:57:33 +00:00
#[inline]
pub fn get_max_sunlight(&self) -> Option<u8> {
match self.kind() {
2021-02-07 23:50:05 +00:00
BlockKind::Water => Some(4),
BlockKind::Leaves => Some(10),
2021-01-06 20:57:33 +00:00
_ if self.is_opaque() => Some(0),
_ => None,
}
}
2020-09-21 20:10:32 +00:00
#[inline]
pub fn is_solid(&self) -> bool {
self.get_sprite()
.map(|s| s.solid_height().is_some())
.unwrap_or(true)
}
2020-11-21 21:31:49 +00:00
/// Can this block be exploded? If so, what 'power' is required to do so?
/// Note that we don't really define what 'power' is. Consider the units
/// arbitrary and only important when compared to one-another.
2020-09-21 20:10:32 +00:00
#[inline]
pub fn explode_power(&self) -> Option<f32> {
match self.kind() {
BlockKind::Leaves => Some(0.25),
BlockKind::Grass => Some(0.5),
BlockKind::WeakRock => Some(0.75),
BlockKind::Snow => Some(0.1),
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
_ => self.get_sprite().map(|_| 0.25),
}
}
2020-09-21 20:10:32 +00:00
#[inline]
pub fn is_collectible(&self) -> bool {
self.get_sprite()
.map(|s| s.is_collectible())
.unwrap_or(false)
}
2020-09-21 20:10:32 +00:00
#[inline]
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]
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]
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]
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
}
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> {
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
}
}
/// 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 {
if self.is_fluid() {
Block::new(self.kind(), Rgb::zero())
} else {
// FIXME: Figure out if there's some sensible way to determine what medium to
// replace a filled block with if it's removed.
Block::air(SpriteKind::Empty)
2019-01-14 18:49:53 +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
}
}