diff --git a/pumpkin-world/src/block/mod.rs b/pumpkin-world/src/block/mod.rs index 82dc02c6c..8b6c611e3 100644 --- a/pumpkin-world/src/block/mod.rs +++ b/pumpkin-world/src/block/mod.rs @@ -7,7 +7,7 @@ use pumpkin_util::math::vector3::Vector3; pub use block_state::BlockState; #[derive(FromPrimitive, PartialEq, Clone, Copy)] -pub enum BlockFace { +pub enum BlockDirection { Bottom = 0, Top, North, @@ -18,7 +18,7 @@ pub enum BlockFace { pub struct InvalidBlockFace; -impl TryFrom for BlockFace { +impl TryFrom for BlockDirection { type Error = InvalidBlockFace; fn try_from(value: i32) -> Result { @@ -34,15 +34,15 @@ impl TryFrom for BlockFace { } } -impl BlockFace { +impl BlockDirection { pub fn to_offset(&self) -> Vector3 { match self { - BlockFace::Bottom => (0, -1, 0), - BlockFace::Top => (0, 1, 0), - BlockFace::North => (0, 0, -1), - BlockFace::South => (0, 0, 1), - BlockFace::West => (-1, 0, 0), - BlockFace::East => (1, 0, 0), + BlockDirection::Bottom => (0, -1, 0), + BlockDirection::Top => (0, 1, 0), + BlockDirection::North => (0, 0, -1), + BlockDirection::South => (0, 0, 1), + BlockDirection::West => (-1, 0, 0), + BlockDirection::East => (1, 0, 0), } .into() } diff --git a/pumpkin/src/block/block_properties_manager.rs b/pumpkin/src/block/block_properties_manager.rs deleted file mode 100644 index 0a6e76bb8..000000000 --- a/pumpkin/src/block/block_properties_manager.rs +++ /dev/null @@ -1,133 +0,0 @@ -use std::{collections::HashMap, sync::Arc}; - -use async_trait::async_trait; -use pumpkin_protocol::server::play::SUseItemOn; -use pumpkin_util::math::position::BlockPos; -use pumpkin_world::block::{ - block_registry::{Block, BLOCKS}, - BlockFace, -}; - -use crate::world::World; - -use super::properties::{slab::SlabBehavior, stair::StairBehavior}; - -#[async_trait] -pub trait BlockBehavior: Send + Sync { - async fn map_state_id( - &self, - world: &World, - block: &Block, - face: &BlockFace, - block_pos: &BlockPos, - use_item_on: &SUseItemOn, - player_direction: &Direction, - ) -> u16; - async fn is_updateable( - &self, - world: &World, - block: &Block, - face: &BlockFace, - block_pos: &BlockPos, - ) -> bool; -} - -#[derive(Clone, Debug)] -pub enum BlockProperty { - Waterlogged(bool), - Facing(Direction), - Powered(bool), - SlabType(SlabPosition), - StairShape(StairShape), - Half(BlockHalf), // Add other properties as needed -} - -#[derive(Clone, Debug)] -pub enum BlockHalf { - Top, - Bottom, -} - -#[derive(Clone, Debug)] -pub enum SlabPosition { - Top, - Bottom, - Double, -} - -#[derive(Clone, Debug)] -pub enum StairShape { - Straight, - InnerLeft, - InnerRight, - OuterLeft, - OuterRight, -} - -#[derive(Clone, Debug, PartialEq)] -pub enum Direction { - North, - South, - East, - West, -} - -#[must_use] -pub fn get_property_key(property_name: &str) -> Option { - match property_name { - "waterlogged" => Some(BlockProperty::Waterlogged(false)), - "facing" => Some(BlockProperty::Facing(Direction::North)), - "type" => Some(BlockProperty::SlabType(SlabPosition::Top)), - "shape" => Some(BlockProperty::StairShape(StairShape::Straight)), - "half" => Some(BlockProperty::Half(BlockHalf::Bottom)), - _ => None, - } -} - -#[derive(Default)] -pub struct BlockPropertiesManager { - properties_registry: HashMap>, -} - -impl BlockPropertiesManager { - pub fn build_properties_registry(&mut self) { - for block in &BLOCKS.blocks { - let behaviour: Arc = match block.name.as_str() { - name if name.ends_with("_slab") => SlabBehavior::get_or_init(&block.properties), - name if name.ends_with("_stairs") => StairBehavior::get_or_init(&block.properties), - _ => continue, - }; - self.properties_registry.insert(block.id, behaviour); - } - } - - pub async fn get_state_id( - &self, - world: &World, - block: &Block, - face: &BlockFace, - block_pos: &BlockPos, - use_item_on: &SUseItemOn, - player_direction: &Direction, - ) -> u16 { - if let Some(behaviour) = self.properties_registry.get(&block.id) { - return behaviour - .map_state_id(world, block, face, block_pos, use_item_on, player_direction) - .await; - } - block.default_state_id - } - - pub async fn is_updateable( - &self, - world: &World, - block: &Block, - face: &BlockFace, - block_pos: &BlockPos, - ) -> bool { - if let Some(behaviour) = self.properties_registry.get(&block.id) { - return behaviour.is_updateable(world, block, face, block_pos).await; - } - false - } -} diff --git a/pumpkin/src/block/mod.rs b/pumpkin/src/block/mod.rs index 46de1f4f6..d77894b24 100644 --- a/pumpkin/src/block/mod.rs +++ b/pumpkin/src/block/mod.rs @@ -1,6 +1,6 @@ -use block_properties_manager::BlockPropertiesManager; use blocks::chest::ChestBlock; use blocks::furnace::FurnaceBlock; +use properties::BlockPropertiesManager; use crate::block::block_manager::BlockManager; use crate::block::blocks::crafting_table::CraftingTableBlock; @@ -8,9 +8,8 @@ use crate::block::blocks::jukebox::JukeboxBlock; use std::sync::Arc; pub mod block_manager; -pub mod block_properties_manager; mod blocks; -mod properties; +pub mod properties; pub mod pumpkin_block; #[must_use] diff --git a/pumpkin/src/block/properties.rs b/pumpkin/src/block/properties.rs new file mode 100644 index 000000000..43de85c89 --- /dev/null +++ b/pumpkin/src/block/properties.rs @@ -0,0 +1,437 @@ +use std::collections::HashMap; + +use pumpkin_protocol::server::play::SUseItemOn; +use pumpkin_util::math::{position::BlockPos, vector3::Vector3}; +use pumpkin_world::block::{ + block_registry::{Block, BLOCKS}, + BlockDirection, +}; + +use crate::world::World; + +#[derive(Clone, Debug)] +pub enum BlockProperty { + Waterlogged(bool), + Facing(Direction), + Face(BlockFace), + Powered(bool), + SlabType(SlabPosition), + StairShape(StairShape), + Half(BlockHalf), // Add other properties as needed +} + +#[derive(Clone, Debug)] +pub enum BlockFace { + Floor, + Wall, + Ceiling, +} + +#[derive(Clone, Debug)] +pub enum BlockHalf { + Top, + Bottom, +} + +#[derive(Clone, Debug)] +pub enum SlabPosition { + Top, + Bottom, + Double, +} + +#[derive(Clone, Debug)] +pub enum StairShape { + Straight, + InnerLeft, + InnerRight, + OuterLeft, + OuterRight, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Direction { + North, + South, + East, + West, +} + +// TODO: We can automaticly parse them ig +#[must_use] +pub fn get_property_key(property_name: &str) -> Option { + match property_name { + "waterlogged" => Some(BlockProperty::Waterlogged(false)), + "facing" => Some(BlockProperty::Facing(Direction::North)), + "type" => Some(BlockProperty::SlabType(SlabPosition::Top)), + "shape" => Some(BlockProperty::StairShape(StairShape::Straight)), + "half" => Some(BlockProperty::Half(BlockHalf::Bottom)), + "powered" => Some(BlockProperty::Powered(false)), + "face" => Some(BlockProperty::Face(BlockFace::Wall)), + _ => None, + } +} + +#[must_use] +pub fn evaluate_property_type( + block: &Block, + clicked_block: &Block, + face: BlockDirection, + use_item_on: &SUseItemOn, +) -> String { + if block.id == clicked_block.id && face == BlockDirection::Top { + return format!("{}{}", "type", "double"); + } + + if face == BlockDirection::Top { + return format!("{}{}", "type", "bottom"); + } + + if face == BlockDirection::North + || face == BlockDirection::South + || face == BlockDirection::West + || face == BlockDirection::East + { + let y_pos = use_item_on.cursor_pos.y; + if y_pos > 0.5 { + return format!("{}{}", "type", "top"); + } + + return format!("{}{}", "type", "bottom"); + } + + format!("{}{}", "type", "bottom") +} + +#[must_use] +pub fn evaluate_property_waterlogged(block: &Block) -> String { + if block.name == "water" { + return format!("{}{}", "waterlogged", "true"); + } + format!("{}{}", "waterlogged", "false") +} + +fn calculate_positions(player_direction: &Direction, block_pos: &BlockPos) -> (BlockPos, BlockPos) { + match player_direction { + Direction::North => ( + BlockPos(Vector3::new( + block_pos.0.x, + block_pos.0.y, + block_pos.0.z - 1, + )), + BlockPos(Vector3::new( + block_pos.0.x, + block_pos.0.y, + block_pos.0.z + 1, + )), + ), + Direction::South => ( + BlockPos(Vector3::new( + block_pos.0.x, + block_pos.0.y, + block_pos.0.z + 1, + )), + BlockPos(Vector3::new( + block_pos.0.x, + block_pos.0.y, + block_pos.0.z - 1, + )), + ), + Direction::East => ( + BlockPos(Vector3::new( + block_pos.0.x + 1, + block_pos.0.y, + block_pos.0.z, + )), + BlockPos(Vector3::new( + block_pos.0.x - 1, + block_pos.0.y, + block_pos.0.z, + )), + ), + Direction::West => ( + BlockPos(Vector3::new( + block_pos.0.x - 1, + block_pos.0.y, + block_pos.0.z, + )), + BlockPos(Vector3::new( + block_pos.0.x + 1, + block_pos.0.y, + block_pos.0.z, + )), + ), + } +} + +#[expect(clippy::implicit_hasher)] +pub async fn evaluate_property_shape( + world: &World, + block_pos: &BlockPos, + face: &BlockDirection, + use_item_on: &SUseItemOn, + player_direction: &Direction, + property_mappings: &HashMap>, +) -> String { + let block_half = evaluate_property_half(*face, use_item_on); + let (front_block_pos, back_block_pos) = calculate_positions(player_direction, block_pos); + + let front_block_and_state = world.get_block_and_block_state(&front_block_pos).await; + let back_block_and_state = world.get_block_and_block_state(&back_block_pos).await; + + match front_block_and_state { + Ok((block, state)) => { + if block.name.ends_with("stairs") { + log::debug!("Block in front is a stair block"); + + let key = state.id - block.states[0].id; + if let Some(properties) = property_mappings.get(&key) { + if properties.contains(&"shapestraight".to_owned()) + && properties.contains(&block_half) + { + let is_facing_north = properties.contains(&"facingnorth".to_owned()); + let is_facing_west = properties.contains(&"facingwest".to_owned()); + let is_facing_south = properties.contains(&"facingsouth".to_owned()); + let is_facing_east = properties.contains(&"facingeast".to_owned()); + + if (is_facing_north && *player_direction == Direction::West) + || (is_facing_west && *player_direction == Direction::South) + || (is_facing_south && *player_direction == Direction::East) + || (is_facing_east && *player_direction == Direction::North) + { + return "shapeouter_right".to_owned(); + } + + if (is_facing_north && *player_direction == Direction::East) + || (is_facing_west && *player_direction == Direction::North) + || (is_facing_south && *player_direction == Direction::West) + || (is_facing_east && *player_direction == Direction::South) + { + return "shapeouter_left".to_owned(); + } + } + } + } else { + log::debug!("Block to the left is not a stair block"); + } + } + Err(_) => { + log::debug!("There is no block to the left"); + } + } + + match back_block_and_state { + Ok((block, state)) => { + if block.name.ends_with("stairs") { + log::debug!("Block in back is a stair block"); + + let key = state.id - block.states[0].id; + if let Some(properties) = property_mappings.get(&key) { + if properties.contains(&"shapestraight".to_owned()) + && properties.contains(&block_half) + { + let is_facing_north = properties.contains(&"facingnorth".to_owned()); + let is_facing_west = properties.contains(&"facingwest".to_owned()); + let is_facing_south = properties.contains(&"facingsouth".to_owned()); + let is_facing_east = properties.contains(&"facingeast".to_owned()); + + if (is_facing_north && *player_direction == Direction::West) + || (is_facing_west && *player_direction == Direction::South) + || (is_facing_south && *player_direction == Direction::East) + || (is_facing_east && *player_direction == Direction::North) + { + return "shapeinner_right".to_owned(); + } + + if (is_facing_north && *player_direction == Direction::East) + || (is_facing_west && *player_direction == Direction::North) + || (is_facing_south && *player_direction == Direction::West) + || (is_facing_east && *player_direction == Direction::South) + { + return "shapeinner_left".to_owned(); + } + } + } + } else { + log::debug!("Block to the right is not a stair block"); + } + } + Err(_) => { + log::debug!("There is no block to the right"); + } + } + + // TODO: We currently don't notify adjacent stair blocks to update their shape after placement. + // We should implement a block update mechanism (e.g., tracking state changes and triggering + // a server-wide or chunk-level update) so that neighbors properly recalculate their shape. + + format!("{}{}", "shape", "straight") +} + +#[must_use] +pub fn evaluate_property_facing(face: BlockDirection, player_direction: &Direction) -> String { + let facing = match face { + BlockDirection::North => "south", + BlockDirection::South => "north", + BlockDirection::East => "west", + BlockDirection::West => "east", + BlockDirection::Top | BlockDirection::Bottom => match player_direction { + Direction::North => "north", + Direction::South => "south", + Direction::East => "east", + Direction::West => "west", + }, + }; + + format!("facing{facing}") +} + +#[must_use] +pub fn evaluate_property_block_face(dir: BlockDirection) -> String { + let block_face = if dir == BlockDirection::Bottom || dir == BlockDirection::Top { + if dir == BlockDirection::Top { + BlockFace::Ceiling + } else { + BlockFace::Floor + } + } else { + BlockFace::Wall + }; + + let facing = match block_face { + BlockFace::Floor => "floor", + BlockFace::Wall => "wall", + BlockFace::Ceiling => "ceiling", + }; + + format!("face{facing}") +} + +#[must_use] +pub fn evaluate_property_half(face: BlockDirection, use_item_on: &SUseItemOn) -> String { + match face { + BlockDirection::Top => format!("{}{}", "half", "bottom"), + BlockDirection::Bottom => format!("{}{}", "half", "top"), + _ => { + if use_item_on.cursor_pos.y > 0.5 { + format!("{}{}", "half", "top") + } else { + format!("{}{}", "half", "bottom") + } + } + } +} + +#[derive(Default)] +pub struct BlockPropertiesManager { + properties_registry: HashMap, +} + +pub struct BlockProperties { + // Mappings from property state strings -> offset + state_mappings: HashMap, u16>, + // Mappings from offset -> property state strings + property_mappings: HashMap>, +} + +impl BlockPropertiesManager { + pub fn build_properties_registry(&mut self) { + for block in &BLOCKS.blocks { + let properties = &block.properties; + if properties.is_empty() { + continue; + } + let total_combinations: usize = properties.iter().map(|p| p.values.len()).product(); + + let mut forward_map = HashMap::with_capacity(total_combinations); + let mut reverse_map = HashMap::with_capacity(total_combinations); + + for i in 0..total_combinations { + let mut current = i; + let mut combination = Vec::with_capacity(properties.len()); + + for property in properties.iter().rev() { + let property_size = property.values.len(); + combination.push(current % property_size); + current /= property_size; + } + + combination.reverse(); + + let key: Vec = combination + .iter() + .enumerate() + .map(|(prop_idx, &state_idx)| { + // Build "namevalue" strings, e.g. "facingnorth", "halfbottom", etc. + format!( + "{}{}", + properties[prop_idx].name, properties[prop_idx].values[state_idx] + ) + }) + .collect(); + + forward_map.insert(key.clone(), i as u16); + reverse_map.insert(i as u16, key); + } + self.properties_registry.insert( + block.id, + BlockProperties { + state_mappings: forward_map, + property_mappings: reverse_map, + }, + ); + } + } + + pub async fn get_state_id( + &self, + world: &World, + block: &Block, + face: &BlockDirection, + block_pos: &BlockPos, + use_item_on: &SUseItemOn, + player_direction: &Direction, + ) -> u16 { + if let Some(properties) = self.properties_registry.get(&block.id) { + let mut hmap_key: Vec = Vec::with_capacity(block.properties.len()); + + for raw_property in &block.properties { + let property = get_property_key(raw_property.name.as_str()); + if let Some(property) = property { + let state = match property { + BlockProperty::SlabType(_) => { + let clicked_block = world.get_block(block_pos).await.unwrap(); + evaluate_property_type(block, clicked_block, *face, use_item_on) + } + BlockProperty::Waterlogged(_) => evaluate_property_waterlogged(block), + BlockProperty::Facing(_) => { + evaluate_property_facing(*face, player_direction) + } + BlockProperty::Half(_) => evaluate_property_half(*face, use_item_on), + BlockProperty::StairShape(_) => { + evaluate_property_shape( + world, + block_pos, + face, + use_item_on, + player_direction, + &properties.property_mappings, + ) + .await + } + BlockProperty::Powered(_) => "poweredfalse".to_string(), // todo + BlockProperty::Face(_) => evaluate_property_block_face(*face), + }; + hmap_key.push(state.to_string()); + } else { + log::warn!("Unknown Block Property: {}", &raw_property.name); + // if one property is not found everything will not work + return block.default_state_id; + } + } + // Base state id plus offset + return block.states[0].id + properties.state_mappings[&hmap_key]; + } + block.default_state_id + } +} diff --git a/pumpkin/src/block/properties/mod.rs b/pumpkin/src/block/properties/mod.rs deleted file mode 100644 index c27038e7c..000000000 --- a/pumpkin/src/block/properties/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub(crate) mod slab; -pub(crate) mod stair; diff --git a/pumpkin/src/block/properties/slab.rs b/pumpkin/src/block/properties/slab.rs deleted file mode 100644 index 2b6cefaac..000000000 --- a/pumpkin/src/block/properties/slab.rs +++ /dev/null @@ -1,167 +0,0 @@ -use std::{ - collections::HashMap, - sync::{Arc, OnceLock}, -}; - -use pumpkin_protocol::server::play::SUseItemOn; -use pumpkin_util::math::position::BlockPos; -use pumpkin_world::block::block_registry::Block; -use pumpkin_world::block::{block_registry::Property, BlockFace}; - -use crate::{ - block::block_properties_manager::{get_property_key, BlockBehavior, BlockProperty, Direction}, - world::World, -}; - -pub static SLAB_BEHAVIOR: OnceLock> = OnceLock::new(); - -// Example of a behavior with shared static data -pub struct SlabBehavior { - // Shared static data for all slabs - state_mappings: HashMap, u16>, - property_mappings: HashMap>, -} - -impl SlabBehavior { - pub fn get_or_init(properties: &[Property]) -> Arc { - SLAB_BEHAVIOR - .get_or_init(|| Arc::new(Self::new(properties))) - .clone() - } - - pub fn get() -> Arc { - SLAB_BEHAVIOR.get().expect("Slab Uninitialized").clone() - } - - pub fn new(properties: &[Property]) -> Self { - let total_combinations: usize = properties.iter().map(|p| p.values.len()).product(); - - let mut forward_map = HashMap::with_capacity(total_combinations); - let mut reverse_map = HashMap::with_capacity(total_combinations); - - for i in 0..total_combinations { - let mut current = i; - let mut combination = Vec::with_capacity(properties.len()); - - for property in properties.iter().rev() { - let property_size = property.values.len(); - combination.push(current % property_size); - current /= property_size; - } - - combination.reverse(); - - let key: Vec = combination - .iter() - .enumerate() - .map(|(prop_idx, &state_idx)| { - format!( - "{}{}", - properties[prop_idx].name, properties[prop_idx].values[state_idx] - ) - }) - .collect(); - - forward_map.insert(key.clone(), i as u16); - reverse_map.insert(i as u16, key); - } - - Self { - state_mappings: forward_map, - property_mappings: reverse_map, - } - } - - pub fn evaluate_property_type( - block: &Block, - clicked_block: &Block, - face: BlockFace, - use_item_on: &SUseItemOn, - ) -> String { - if block.id == clicked_block.id && face == BlockFace::Top { - return format!("{}{}", "type", "double"); - } - - if face == BlockFace::Top { - return format!("{}{}", "type", "bottom"); - } - - if face == BlockFace::North - || face == BlockFace::South - || face == BlockFace::West - || face == BlockFace::East - { - let y_pos = use_item_on.cursor_pos.y; - if y_pos > 0.5 { - return format!("{}{}", "type", "top"); - } - - return format!("{}{}", "type", "bottom"); - } - - format!("{}{}", "type", "bottom") - } - - pub fn evaluate_property_waterlogged(block: &Block) -> String { - if block.name == "water" { - return format!("{}{}", "waterlogged", "true"); - } - format!("{}{}", "waterlogged", "false") - } -} - -#[async_trait::async_trait] -impl BlockBehavior for SlabBehavior { - async fn map_state_id( - &self, - world: &World, - block: &Block, - face: &BlockFace, - block_pos: &BlockPos, - use_item_on: &SUseItemOn, - _player_direction: &Direction, - ) -> u16 { - let clicked_block = world.get_block(block_pos).await.unwrap(); - let mut hmap_key: Vec = Vec::with_capacity(block.properties.len()); - let slab_behaviour = Self::get(); - - for property in &block.properties { - let state = match get_property_key(property.name.as_str()).expect("Property not found") - { - BlockProperty::SlabType(_) => { - Self::evaluate_property_type(block, clicked_block, *face, use_item_on) - } - BlockProperty::Waterlogged(false) => Self::evaluate_property_waterlogged(block), - _ => panic!("Property not found"), - }; - hmap_key.push(state.to_string()); - } - - // Base state id plus offset - block.states[0].id + slab_behaviour.state_mappings[&hmap_key] - } - - async fn is_updateable( - &self, - world: &World, - block: &Block, - _face: &BlockFace, - block_pos: &BlockPos, - ) -> bool { - let clicked_block = world.get_block(block_pos).await.unwrap(); - if block.id != clicked_block.id { - return false; // Ensure the block being interacted with matches the target block. - } - - let clicked_block_state_id = world.get_block_state_id(block_pos).await.unwrap(); - - let key = clicked_block_state_id - clicked_block.states[0].id; - if let Some(properties) = Self::get().property_mappings.get(&key) { - log::debug!("Properties: {:?}", properties); - if properties.contains(&"typebottom".to_string()) { - return true; - } - } - false - } -} diff --git a/pumpkin/src/block/properties/stair.rs b/pumpkin/src/block/properties/stair.rs deleted file mode 100644 index 3cb4b000c..000000000 --- a/pumpkin/src/block/properties/stair.rs +++ /dev/null @@ -1,330 +0,0 @@ -use std::{ - collections::HashMap, - sync::{Arc, OnceLock}, -}; - -use pumpkin_protocol::server::play::SUseItemOn; -use pumpkin_util::math::{position::BlockPos, vector3::Vector3}; -use pumpkin_world::block::block_registry::Block; -use pumpkin_world::block::{block_registry::Property, BlockFace}; - -use crate::{ - block::block_properties_manager::{get_property_key, BlockBehavior, BlockProperty, Direction}, - world::World, -}; - -/// Global static for `StairBehavior` -pub static STAIRS_BEHAVIOR: OnceLock> = OnceLock::new(); - -/// Behavior for Stairs -pub struct StairBehavior { - // Mappings from property state strings -> offset - state_mappings: HashMap, u16>, - // Mappings from offset -> property state strings - property_mappings: HashMap>, -} - -impl StairBehavior { - /// Initialize or return the existing `StairBehavior` - pub fn get_or_init(properties: &[Property]) -> Arc { - STAIRS_BEHAVIOR - .get_or_init(|| Arc::new(Self::new(properties))) - .clone() - } - - /// Returns the global `StairBehavior` (must have been init once) - pub fn get() -> Arc { - STAIRS_BEHAVIOR - .get() - .expect("StairsBehavior not initialized") - .clone() - } - - /// Build up our forward/reverse property state maps - pub fn new(properties: &[Property]) -> Self { - let total_combinations: usize = properties.iter().map(|p| p.values.len()).product(); - - let mut forward_map = HashMap::with_capacity(total_combinations); - let mut reverse_map = HashMap::with_capacity(total_combinations); - - for i in 0..total_combinations { - let mut current = i; - let mut combination = Vec::with_capacity(properties.len()); - - for property in properties.iter().rev() { - let property_size = property.values.len(); - combination.push(current % property_size); - current /= property_size; - } - - combination.reverse(); - - let key: Vec = combination - .iter() - .enumerate() - .map(|(prop_idx, &state_idx)| { - // Build "namevalue" strings, e.g. "facingnorth", "halfbottom", etc. - format!( - "{}{}", - properties[prop_idx].name, properties[prop_idx].values[state_idx] - ) - }) - .collect(); - - forward_map.insert(key.clone(), i as u16); - reverse_map.insert(i as u16, key); - } - - Self { - state_mappings: forward_map, - property_mappings: reverse_map, - } - } - - fn calculate_positions( - player_direction: &Direction, - block_pos: &BlockPos, - ) -> (BlockPos, BlockPos) { - match player_direction { - Direction::North => ( - BlockPos(Vector3::new( - block_pos.0.x, - block_pos.0.y, - block_pos.0.z - 1, - )), - BlockPos(Vector3::new( - block_pos.0.x, - block_pos.0.y, - block_pos.0.z + 1, - )), - ), - Direction::South => ( - BlockPos(Vector3::new( - block_pos.0.x, - block_pos.0.y, - block_pos.0.z + 1, - )), - BlockPos(Vector3::new( - block_pos.0.x, - block_pos.0.y, - block_pos.0.z - 1, - )), - ), - Direction::East => ( - BlockPos(Vector3::new( - block_pos.0.x + 1, - block_pos.0.y, - block_pos.0.z, - )), - BlockPos(Vector3::new( - block_pos.0.x - 1, - block_pos.0.y, - block_pos.0.z, - )), - ), - Direction::West => ( - BlockPos(Vector3::new( - block_pos.0.x - 1, - block_pos.0.y, - block_pos.0.z, - )), - BlockPos(Vector3::new( - block_pos.0.x + 1, - block_pos.0.y, - block_pos.0.z, - )), - ), - } - } - - pub async fn evaluate_property_shape( - world: &World, - block_pos: &BlockPos, - face: &BlockFace, - use_item_on: &SUseItemOn, - player_direction: &Direction, - ) -> String { - let block_half = Self::evaluate_property_half(*face, use_item_on); - let (front_block_pos, back_block_pos) = - Self::calculate_positions(player_direction, block_pos); - - let front_block_and_state = world.get_block_and_block_state(&front_block_pos).await; - let back_block_and_state = world.get_block_and_block_state(&back_block_pos).await; - - match front_block_and_state { - Ok((block, state)) => { - if block.name.ends_with("stairs") { - log::debug!("Block in front is a stair block"); - - let key = state.id - block.states[0].id; - if let Some(properties) = Self::get().property_mappings.get(&key) { - if properties.contains(&"shapestraight".to_owned()) - && properties.contains(&block_half) - { - let is_facing_north = properties.contains(&"facingnorth".to_owned()); - let is_facing_west = properties.contains(&"facingwest".to_owned()); - let is_facing_south = properties.contains(&"facingsouth".to_owned()); - let is_facing_east = properties.contains(&"facingeast".to_owned()); - - if (is_facing_north && *player_direction == Direction::West) - || (is_facing_west && *player_direction == Direction::South) - || (is_facing_south && *player_direction == Direction::East) - || (is_facing_east && *player_direction == Direction::North) - { - return "shapeouter_right".to_owned(); - } - - if (is_facing_north && *player_direction == Direction::East) - || (is_facing_west && *player_direction == Direction::North) - || (is_facing_south && *player_direction == Direction::West) - || (is_facing_east && *player_direction == Direction::South) - { - return "shapeouter_left".to_owned(); - } - } - } - } else { - log::debug!("Block to the left is not a stair block"); - } - } - Err(_) => { - log::debug!("There is no block to the left"); - } - } - - match back_block_and_state { - Ok((block, state)) => { - if block.name.ends_with("stairs") { - log::debug!("Block in back is a stair block"); - - let key = state.id - block.states[0].id; - if let Some(properties) = Self::get().property_mappings.get(&key) { - if properties.contains(&"shapestraight".to_owned()) - && properties.contains(&block_half) - { - let is_facing_north = properties.contains(&"facingnorth".to_owned()); - let is_facing_west = properties.contains(&"facingwest".to_owned()); - let is_facing_south = properties.contains(&"facingsouth".to_owned()); - let is_facing_east = properties.contains(&"facingeast".to_owned()); - - if (is_facing_north && *player_direction == Direction::West) - || (is_facing_west && *player_direction == Direction::South) - || (is_facing_south && *player_direction == Direction::East) - || (is_facing_east && *player_direction == Direction::North) - { - return "shapeinner_right".to_owned(); - } - - if (is_facing_north && *player_direction == Direction::East) - || (is_facing_west && *player_direction == Direction::North) - || (is_facing_south && *player_direction == Direction::West) - || (is_facing_east && *player_direction == Direction::South) - { - return "shapeinner_left".to_owned(); - } - } - } - } else { - log::debug!("Block to the right is not a stair block"); - } - } - Err(_) => { - log::debug!("There is no block to the right"); - } - } - - // TODO: We currently don't notify adjacent stair blocks to update their shape after placement. - // We should implement a block update mechanism (e.g., tracking state changes and triggering - // a server-wide or chunk-level update) so that neighbors properly recalculate their shape. - - format!("{}{}", "shape", "straight") - } - - pub fn evaluate_property_waterlogged(block: &Block) -> String { - if block.name == "water" { - return format!("{}{}", "waterlogged", "true"); - } - format!("{}{}", "waterlogged", "false") - } - - pub fn evaluate_property_facing(face: BlockFace, player_direction: &Direction) -> String { - let facing = match face { - BlockFace::North => "south", - BlockFace::South => "north", - BlockFace::East => "west", - BlockFace::West => "east", - BlockFace::Top | BlockFace::Bottom => match player_direction { - Direction::North => "north", - Direction::South => "south", - Direction::East => "east", - Direction::West => "west", - }, - }; - - format!("facing{facing}") - } - - pub fn evaluate_property_half(face: BlockFace, use_item_on: &SUseItemOn) -> String { - match face { - BlockFace::Top => format!("{}{}", "half", "bottom"), - BlockFace::Bottom => format!("{}{}", "half", "top"), - _ => { - if use_item_on.cursor_pos.y > 0.5 { - format!("{}{}", "half", "top") - } else { - format!("{}{}", "half", "bottom") - } - } - } - } -} - -#[async_trait::async_trait] -impl BlockBehavior for StairBehavior { - /// Given the block and environment, compute the correct state ID. - async fn map_state_id( - &self, - world: &World, - block: &Block, - face: &BlockFace, - block_pos: &BlockPos, - use_item_on: &SUseItemOn, - player_direction: &Direction, - ) -> u16 { - let mut hmap_key: Vec = Vec::with_capacity(block.properties.len()); - let stair_behaviour = Self::get(); - - for property in &block.properties { - let state = match get_property_key(property.name.as_str()).expect("Property not found") - { - BlockProperty::Facing(_) => Self::evaluate_property_facing(*face, player_direction), - BlockProperty::Half(_) => Self::evaluate_property_half(*face, use_item_on), - BlockProperty::StairShape(_) => { - Self::evaluate_property_shape( - world, - block_pos, - face, - use_item_on, - player_direction, - ) - .await - } - BlockProperty::Waterlogged(_) => Self::evaluate_property_waterlogged(block), - _ => panic!("BlockProperty invalid for Stairs"), - }; - hmap_key.push(state); - } - - block.states[0].id + stair_behaviour.state_mappings[&hmap_key] - } - - async fn is_updateable( - &self, - _world: &World, - _block: &Block, - _face: &BlockFace, - _block_pos: &BlockPos, - ) -> bool { - false - } -} diff --git a/pumpkin/src/net/packet/play.rs b/pumpkin/src/net/packet/play.rs index d2f535523..192fbe734 100644 --- a/pumpkin/src/net/packet/play.rs +++ b/pumpkin/src/net/packet/play.rs @@ -2,7 +2,7 @@ use std::num::NonZeroU8; use std::sync::Arc; use crate::block::block_manager::BlockActionResult; -use crate::block::block_properties_manager::Direction; +use crate::block::properties::Direction; use crate::entity::mob; use crate::net::PlayerConfig; use crate::{ @@ -47,7 +47,7 @@ use pumpkin_world::block::block_registry::Block; use pumpkin_world::item::item_registry::get_item_by_id; use pumpkin_world::item::ItemStack; use pumpkin_world::{ - block::{block_registry::get_block_by_item, BlockFace}, + block::{block_registry::get_block_by_item, BlockDirection}, entity::entity_registry::get_entity_id, item::item_registry::get_spawn_egg, }; @@ -900,7 +900,7 @@ impl Player { return Err(BlockPlacingError::BlockOutOfReach.into()); } - let Ok(face) = BlockFace::try_from(use_item_on.face.0) else { + let Ok(face) = BlockDirection::try_from(use_item_on.face.0) else { return Err(BlockPlacingError::InvalidBlockFace.into()); }; @@ -1104,7 +1104,7 @@ impl Player { item_t: String, server: &Server, location: BlockPos, - face: &BlockFace, + face: &BlockDirection, ) -> Result> { // checks if spawn egg has a corresponding entity name if let Some(spawn_item_id) = get_entity_id(&item_t) { @@ -1163,7 +1163,7 @@ impl Player { server: &Server, use_item_on: SUseItemOn, location: BlockPos, - face: &BlockFace, + face: &BlockDirection, ) -> Result> { let entity = &self.living_entity.entity; let world = &entity.world; @@ -1206,10 +1206,7 @@ impl Player { _ => {} } - let clicked_block_updated_able = server - .block_properties_manager - .is_updateable(world, &block, face, &clicked_block_pos) - .await; + let clicked_block_updated_able = false; let final_block_pos = if clicked_block_state.replaceable || clicked_block_updated_able { clicked_block_pos diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index b13a28a9b..fde800538 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -32,7 +32,7 @@ use tokio::sync::{Mutex, RwLock}; use uuid::Uuid; use crate::block::block_manager::BlockManager; -use crate::block::block_properties_manager::BlockPropertiesManager; +use crate::block::properties::BlockPropertiesManager; use crate::block::{default_block_manager, default_block_properties_manager}; use crate::entity::ai::path::Navigator; use crate::entity::living::LivingEntity;