use crate::common::{
Aabb, BiomeInfo, BlockInfo, BlockPhysics, Collision, DigMaterial, EffectInfo, EnchantmentInfo,
EntityInfo, EntityKind, Fluid, FluidKind, FoodInfo, ItemInfo, ShapeDependency, lookup_id,
lookup_name,
};
use crate::generated::v1_16_5 as g;
use core::fmt;
pub use g::blocks::{Material, Property};
pub const BLOCK_COUNT: usize = g::blocks::BLOCKS.len();
pub const STATE_COUNT: usize = g::blocks::STATE_BLOCK.len();
impl Property {
#[must_use]
pub fn from_name(name: &str) -> Option<Property> {
lookup_name(&g::blocks::PROPERTY_NAMES, name)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PropertyDef {
pub property: Property,
pub values: &'static [&'static str],
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct StateLayout {
pub min_state: u16,
pub state_count: u16,
pub default_state: u16,
pub(crate) properties: &'static [u8],
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum OffsetType {
None,
Xz,
Xyz,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct VanillaBlock {
pub collidable: bool,
pub offset: OffsetType,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ScaffoldingShapes {
pub stable: &'static [Aabb],
pub bottom: &'static [Aabb],
pub stable_voxel: &'static VoxelShapeRef,
pub bottom_voxel: &'static VoxelShapeRef,
pub has_bottom: bool,
}
impl ScaffoldingShapes {
#[must_use]
pub fn select(
&self,
feet_min_y: f64,
block_y: i32,
descending: bool,
) -> (&'static [Aabb], &'static VoxelShapeRef) {
let eps = f64::from(1.0e-5_f32);
if feet_min_y > f64::from(block_y) + 1.0 - eps && !descending {
(self.stable, self.stable_voxel)
} else if self.has_bottom && feet_min_y > f64::from(block_y) + 0.0 - eps {
(self.bottom, self.bottom_voxel)
} else {
(&[], &g::blocks::VOXEL_SHAPES[0])
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum VoxelClass {
Simple,
Array,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct VoxelShapeRef {
pub class: VoxelClass,
pub sizes: [u8; 3],
pub xs: &'static [f64],
pub ys: &'static [f64],
pub zs: &'static [f64],
pub cells: &'static [u64],
pub empty: bool,
}
impl VoxelShapeRef {
#[must_use]
pub fn filled(&self, x: usize, y: usize, z: usize) -> bool {
let [nx, ny, nz] = self.sizes.map(usize::from);
if x >= nx || y >= ny || z >= nz {
return false;
}
let i = (x * ny + y) * nz + z;
self.cells[i / 64] & (1 << (i % 64)) != 0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Side {
North = 0,
East = 1,
South = 2,
West = 3,
Up = 4,
Down = 5,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Block(pub(crate) u16);
impl Block {
#[must_use]
pub const fn from_id(id: u16) -> Option<Block> {
if (id as usize) < BLOCK_COUNT {
Some(Block(id))
} else {
None
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<Block> {
lookup_name(&g::blocks::BLOCK_NAMES, name).map(Block)
}
#[must_use]
pub const fn id(self) -> u16 {
self.0
}
#[must_use]
pub fn info(self) -> &'static BlockInfo {
&g::blocks::BLOCKS[self.0 as usize]
}
#[must_use]
pub fn name(self) -> &'static str {
self.info().name
}
#[must_use]
pub fn physics(self) -> &'static BlockPhysics {
&g::blocks::BLOCKS[self.0 as usize].physics
}
#[must_use]
pub fn shape_dependency(self) -> ShapeDependency {
g::blocks::BLOCK_SHAPE_DEPENDENCY[self.0 as usize]
}
#[must_use]
pub const fn is_air(self) -> bool {
matches!(self, Block::AIR | Block::CAVE_AIR | Block::VOID_AIR)
}
#[must_use]
pub const fn is_bed(self) -> bool {
self.0 >= Block::WHITE_BED.0 && self.0 <= Block::BLACK_BED.0
}
#[must_use]
pub fn material(self) -> Material {
g::blocks::BLOCK_MATERIAL[self.0 as usize]
}
#[must_use]
pub fn vanilla(self) -> &'static VanillaBlock {
&g::blocks::VANILLA_BLOCKS[self.0 as usize]
}
#[must_use]
pub fn layout(self) -> &'static StateLayout {
&g::blocks::LAYOUTS[self.0 as usize]
}
#[must_use]
pub fn default_state(self) -> BlockState {
BlockState(self.layout().default_state)
}
pub fn states(self) -> impl ExactSizeIterator<Item = BlockState> {
let l = self.layout();
(l.min_state..l.min_state + l.state_count).map(BlockState)
}
pub fn properties(self) -> impl ExactSizeIterator<Item = &'static PropertyDef> {
self.layout()
.properties
.iter()
.map(|&i| &g::blocks::PROPERTY_DEFS[i as usize])
}
#[must_use]
pub fn state_with(self, props: &[(Property, &str)]) -> Option<BlockState> {
props
.iter()
.try_fold(self.default_state(), |s, &(p, v)| s.with(p, v))
}
pub fn all() -> impl ExactSizeIterator<Item = Block> {
(0..BLOCK_COUNT as u16).map(Block)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct BlockState(u16);
impl BlockState {
pub const AIR: BlockState = BlockState(0);
#[must_use]
pub const fn from_id(id: u16) -> Option<BlockState> {
if (id as usize) < STATE_COUNT {
Some(BlockState(id))
} else {
None
}
}
#[must_use]
pub const fn id(self) -> u16 {
self.0
}
#[must_use]
pub fn block(self) -> Block {
Block(g::blocks::STATE_BLOCK[self.0 as usize])
}
#[must_use]
pub fn is_air(self) -> bool {
self.block().is_air()
}
#[must_use]
pub fn collision(self) -> Collision {
Collision {
boxes: self.shape(),
dependency: g::blocks::BLOCK_SHAPE_DEPENDENCY
[g::blocks::STATE_BLOCK[self.0 as usize] as usize],
world_coords: None,
}
}
#[must_use]
pub fn full_cube(self) -> bool {
g::blocks::STATE_VANILLA_FLAGS[self.0 as usize] & 1 != 0
}
#[must_use]
pub fn solid_block(self) -> bool {
g::blocks::STATE_VANILLA_FLAGS[self.0 as usize] & 2 != 0
}
#[must_use]
pub fn should_suffocate(self) -> bool {
g::blocks::STATE_VANILLA_FLAGS[self.0 as usize] & 4 != 0
}
#[must_use]
pub fn voxel_shape(self) -> &'static VoxelShapeRef {
&g::blocks::VOXEL_SHAPES[g::blocks::STATE_VOXEL[self.0 as usize] as usize]
}
fn voxel_flag(self, bit: u16) -> bool {
g::blocks::STATE_VOXEL_FLAGS[self.0 as usize] & bit != 0
}
#[must_use]
pub fn full_cube_identity(self) -> bool {
self.voxel_flag(1)
}
#[must_use]
pub fn empty_identity(self) -> bool {
self.voxel_flag(1 << 12)
}
#[must_use]
pub fn material_liquid(self) -> bool {
self.voxel_flag(1 << 13)
}
#[must_use]
pub fn material_solid(self) -> bool {
self.voxel_flag(1 << 14)
}
#[must_use]
pub fn material_replaceable(self) -> bool {
self.voxel_flag(1 << 15)
}
#[must_use]
pub fn exceeds_cube(self) -> bool {
self.voxel_flag(2)
}
#[must_use]
pub fn has_dynamic_bounds(self) -> bool {
self.voxel_flag(16)
}
#[must_use]
pub fn material_blocks_movement(self) -> bool {
self.voxel_flag(32)
}
#[must_use]
pub fn side_solid_full_square(self, side: Side) -> bool {
self.voxel_flag(64 << side as u16)
}
#[must_use]
pub fn scaffolding_shapes(self) -> Option<ScaffoldingShapes> {
if self.block() != Block::SCAFFOLDING {
return None;
}
let has_bottom =
self.get(Property::Bottom) == Some("true") && self.get(Property::Distance) != Some("0");
Some(ScaffoldingShapes {
stable: self.shape(),
bottom: if has_bottom {
g::blocks::SCAFFOLDING_BOTTOM
} else {
&[]
},
stable_voxel: self.voxel_shape(),
has_bottom,
bottom_voxel: &g::blocks::VOXEL_SHAPES[if has_bottom {
g::blocks::SCAFFOLDING_BOTTOM_VOXEL as usize
} else {
0
}],
})
}
#[must_use]
pub fn shape(self) -> &'static [Aabb] {
g::blocks::SHAPES[g::blocks::STATE_SHAPE[self.0 as usize] as usize]
}
#[must_use]
pub fn fluid(self) -> Option<Fluid> {
let f = g::blocks::STATE_FLUID[self.0 as usize];
let kind = match f >> 4 {
1 => FluidKind::Water,
2 => FluidKind::Lava,
_ => return None,
};
Some(Fluid {
kind,
level: f & 15,
})
}
#[must_use]
pub fn get_index(self, prop: Property) -> Option<usize> {
let layout = self.block().layout();
let mut rem = usize::from(self.0 - layout.min_state);
for &d in layout.properties.iter().rev() {
let def = &g::blocks::PROPERTY_DEFS[d as usize];
let n = def.values.len();
if def.property == prop {
return Some(rem % n);
}
rem /= n;
}
None
}
#[must_use]
pub fn get(self, prop: Property) -> Option<&'static str> {
let def = self.property_def(prop)?;
self.get_index(prop).map(|i| def.values[i])
}
#[must_use]
pub fn with(self, prop: Property, value: &str) -> Option<BlockState> {
let layout = self.block().layout();
let mut stride = 1usize;
for &d in layout.properties.iter().rev() {
let def = &g::blocks::PROPERTY_DEFS[d as usize];
let n = def.values.len();
if def.property == prop {
let new = def.values.iter().position(|v| *v == value)?;
let base = usize::from(self.0 - layout.min_state);
let old = base / stride % n;
let id = base - old * stride + new * stride + usize::from(layout.min_state);
return Some(BlockState(id as u16));
}
stride *= n;
}
None
}
pub fn properties(self) -> impl Iterator<Item = (Property, &'static str)> {
let block = self.block();
block
.properties()
.filter_map(move |d| self.get(d.property).map(|v| (d.property, v)))
}
fn property_def(self, prop: Property) -> Option<&'static PropertyDef> {
self.block().properties().find(|d| d.property == prop)
}
}
impl fmt::Display for BlockState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "minecraft:{}", self.block().name())?;
let mut first = true;
for (p, v) in self.properties() {
f.write_str(if first { "[" } else { "," })?;
write!(f, "{}={v}", p.name())?;
first = false;
}
if !first {
f.write_str("]")?;
}
Ok(())
}
}
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn slime_dimensions(n: i32) -> crate::common::EntityDimensions {
let ratio = 0.255_f32 * n as f32;
let s = 2.04_f32 * ratio;
crate::common::EntityDimensions {
width: s,
height: s,
eye_height: 0.625 * s,
}
}
crate::macros::common_api!();