Pumpkin/crates/pumpkin-data/src/blocks.rs
2026-08-08 13:44:51 +02:00

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