272 lines
7.5 KiB
Rust
272 lines
7.5 KiB
Rust
use crate::{
|
|
BlockState, BlockStateId,
|
|
tag::{RegistryKey, Tag, Taggable},
|
|
};
|
|
use pumpkin_util::{
|
|
loot_table::LootTable,
|
|
math::experience::Experience,
|
|
resource_location::{FromResourceLocation, ResourceLocation, ToResourceLocation},
|
|
};
|
|
use std::hash::{Hash, Hasher};
|
|
|
|
/// Represents the static definition of a Minecraft block type.
|
|
///
|
|
/// This struct contains the base properties shared by all instances of a block
|
|
/// Data-driven attributes like `hardness` and `blast_resistance` are defined here,
|
|
/// while specific orientations or variations are stored in the associated `BlockState`.
|
|
#[derive(Debug, Clone)]
|
|
pub struct Block {
|
|
/// The numeric ID used for internal registry mapping.
|
|
pub id: BlockId,
|
|
/// The unique namespaced ID (e.g., "`diamond_ore`").
|
|
pub name: &'static str,
|
|
/// How hard the block is to break. A value of -1.0 indicates an unbreakable block (e.g., Bedrock).
|
|
pub hardness: f32,
|
|
/// The block's resistance to explosions.
|
|
pub blast_resistance: f32,
|
|
pub map_color: u8,
|
|
/// The friction coefficient. Default is 0.6; Ice is 0.98.
|
|
pub slipperiness: f32,
|
|
/// How much this block affects the speed of an entity walking on it (e.g., Soul Sand).
|
|
pub velocity_multiplier: f32,
|
|
/// How much this block affects an entity's jump height (e.g., Honey Blocks).
|
|
pub jump_velocity_multiplier: f32,
|
|
/// The ID of the item form of this block, used for inventory and drops.
|
|
pub item_id: u16,
|
|
/// The initial state of the block when placed without extra data.
|
|
pub default_state: &'static BlockState,
|
|
/// A list of all possible valid states (properties like rotation, waterlogged, etc.) for this block.
|
|
pub states: &'static [BlockState],
|
|
/// Fire behavior settings. If `None`, the block is not flammable.
|
|
pub flammable: Option<Flammable>,
|
|
/// Defines the items dropped when this block is destroyed.
|
|
pub loot_table: Option<LootTable>,
|
|
/// Defines the amount of XP dropped when the block is mined (e.g., Coal or Diamond).
|
|
pub experience: Option<Experience>,
|
|
}
|
|
|
|
/// Helper struct to ensure the validity of BlockIds parsed from external sources.
|
|
/// Every [`BlockId`] is guaranteed to correspond to a valid [`Block`].
|
|
///
|
|
/// Also enables [`Block`]-type pattern matching, even in const contexts:
|
|
/// ```rs
|
|
/// const fn to_waxed(block: &'static Block) -> Option<&'static Block> {
|
|
/// match block.id {
|
|
/// BlockId::COPPER_BLOCK => Some(Block::WAXED_COPPER_BLOCK),
|
|
/// //...
|
|
/// _ => None
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
#[repr(transparent)]
|
|
pub struct BlockId(u16);
|
|
|
|
impl PartialEq<BlockId> for Block {
|
|
fn eq(&self, other: &BlockId) -> bool {
|
|
self.id == *other
|
|
}
|
|
}
|
|
|
|
impl PartialEq<Block> for BlockId {
|
|
fn eq(&self, other: &Block) -> bool {
|
|
*self == other.id
|
|
}
|
|
}
|
|
|
|
impl PartialEq for Block {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
self.id == other.id
|
|
}
|
|
}
|
|
|
|
impl Eq for Block {}
|
|
|
|
impl Hash for Block {
|
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
self.id.hash(state);
|
|
}
|
|
}
|
|
|
|
impl Taggable for Block {
|
|
#[inline]
|
|
fn tag_key() -> RegistryKey {
|
|
RegistryKey::Block
|
|
}
|
|
|
|
#[inline]
|
|
fn registry_key(&self) -> &str {
|
|
self.name
|
|
}
|
|
|
|
#[inline]
|
|
fn registry_id(&self) -> u16 {
|
|
self.id.as_u16()
|
|
}
|
|
}
|
|
|
|
impl ToResourceLocation for &'static Block {
|
|
fn to_resource_location(&self) -> ResourceLocation {
|
|
format!("minecraft:{}", self.name)
|
|
}
|
|
}
|
|
|
|
impl FromResourceLocation for &'static Block {
|
|
fn from_resource_location(resource_location: &ResourceLocation) -> Option<Self> {
|
|
Block::from_registry_key(
|
|
resource_location
|
|
.strip_prefix("minecraft:")
|
|
.unwrap_or(resource_location),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl Block {
|
|
#[must_use]
|
|
pub fn is_waterlogged(&self, id: BlockStateId) -> bool {
|
|
self.properties(id).is_some_and(|properties| {
|
|
properties
|
|
.to_props()
|
|
.into_iter()
|
|
.any(|(key, value)| key == "waterlogged" && value == "true")
|
|
})
|
|
}
|
|
|
|
/// Returns a new [`BlockState`] reference for the given [`BlockStateId`] with the
|
|
/// `waterlogged` property forced to `true` if the block supports that
|
|
/// property. If the state is already waterlogged or the block does not
|
|
/// expose a `waterlogged` property then `None` is returned.
|
|
#[must_use]
|
|
pub fn with_waterlogged(&self, id: BlockStateId) -> Option<&'static BlockState> {
|
|
// Check if already waterlogged
|
|
if self.is_waterlogged(id) {
|
|
return Some(BlockState::from_id(id));
|
|
}
|
|
|
|
// Modify the property list if available
|
|
if let Some(props_source) = self.properties(id) {
|
|
let mut props: Vec<(&str, &str)> = props_source
|
|
.to_props()
|
|
.iter()
|
|
.map(|(k, v)| (*k, *v))
|
|
.collect();
|
|
|
|
// Look for an existing waterlogged key or add one
|
|
if let Some(idx) = props.iter().position(|(k, _)| *k == "waterlogged") {
|
|
props[idx] = ("waterlogged", "true");
|
|
} else {
|
|
props.push(("waterlogged", "true"));
|
|
}
|
|
|
|
let new_state_id = self.from_properties(&props).to_state_id(self);
|
|
return Some(BlockState::from_id(new_state_id));
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
/// Returns whether this block is solid (based on default state)
|
|
#[must_use]
|
|
pub const fn is_solid(&self) -> bool {
|
|
self.default_state.is_solid()
|
|
}
|
|
|
|
/// Returns whether this block is air (based on default state)
|
|
#[must_use]
|
|
pub const fn is_air(&self) -> bool {
|
|
self.default_state.is_air()
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn mirror(
|
|
&self,
|
|
id: BlockStateId,
|
|
_mirror: crate::block_rotation::Mirror,
|
|
) -> &'static BlockState {
|
|
BlockState::from_id(id)
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn rotate(
|
|
&self,
|
|
id: BlockStateId,
|
|
_rotation: crate::block_rotation::Rotation,
|
|
) -> &'static BlockState {
|
|
BlockState::from_id(id)
|
|
}
|
|
}
|
|
|
|
impl BlockId {
|
|
// depends on generated impl:
|
|
// pub(crate) const BLOCK_COUNT: u16;
|
|
|
|
// SAFETY: There must never be a BlockId where self.0 >= BlockId::BLOCK_COUNT
|
|
|
|
#[inline]
|
|
#[must_use]
|
|
pub const fn new(inner: u16) -> Option<Self> {
|
|
if inner < Self::BLOCK_COUNT {
|
|
return Some(Self(inner));
|
|
}
|
|
None
|
|
}
|
|
|
|
#[inline]
|
|
#[must_use]
|
|
pub const fn new_or_air(inner: u16) -> Self {
|
|
if inner < Self::BLOCK_COUNT {
|
|
return Self(inner);
|
|
}
|
|
Self::AIR
|
|
}
|
|
|
|
#[inline]
|
|
#[must_use]
|
|
pub const fn to_block(self) -> &'static Block {
|
|
Block::from_id(self)
|
|
}
|
|
|
|
#[inline(always)]
|
|
#[must_use]
|
|
pub const fn as_u16(self) -> u16 {
|
|
self.0
|
|
}
|
|
|
|
#[inline]
|
|
#[must_use]
|
|
pub fn has_tag(self, tag: Tag) -> bool {
|
|
tag.1.contains(&self.0)
|
|
}
|
|
}
|
|
|
|
impl From<BlockId> for u16 {
|
|
#[inline]
|
|
fn from(value: BlockId) -> Self {
|
|
value.as_u16()
|
|
}
|
|
}
|
|
|
|
impl Default for BlockId {
|
|
#[inline]
|
|
fn default() -> Self {
|
|
Self::AIR
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Display for BlockId {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(
|
|
f,
|
|
"BlockId({} = \"{}\")",
|
|
self.0,
|
|
Block::from_id(*self).name
|
|
)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Flammable {
|
|
pub spread_chance: u8,
|
|
pub burn_chance: u8,
|
|
}
|