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, /// Defines the items dropped when this block is destroyed. pub loot_table: Option, /// Defines the amount of XP dropped when the block is mined (e.g., Coal or Diamond). pub experience: Option, } /// 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 for Block { fn eq(&self, other: &BlockId) -> bool { self.id == *other } } impl PartialEq 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(&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 { 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 { 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 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, }