From 6c4eb88dd3bae7c4989bdbd77b676fadb632a110 Mon Sep 17 00:00:00 2001 From: Kyle Davis Date: Tue, 28 Jan 2025 05:15:55 -0500 Subject: [PATCH] Adds support for Block properties (#485) * Something * Double slabs working * Revert changes to blocks.json * Fixed merge and clippy issues * Fixed Bad merge for play.rs * fix slabs and adds stairs * Fix issue with fmt --------- Co-authored-by: Bafran Co-authored-by: Alexander Medvedev --- pumpkin-world/Cargo.toml | 2 + pumpkin-world/src/block/block_registry.rs | 5 +- pumpkin-world/src/block/mod.rs | 2 + pumpkin-world/src/entity/mod.rs | 8 + pumpkin/src/block/block_properties_manager.rs | 135 +++++++ pumpkin/src/block/mod.rs | 12 + pumpkin/src/block/properties/mod.rs | 2 + pumpkin/src/block/properties/slab.rs | 167 +++++++++ pumpkin/src/block/properties/stair.rs | 335 ++++++++++++++++++ pumpkin/src/net/packet/play.rs | 55 ++- pumpkin/src/server/mod.rs | 6 +- 11 files changed, 713 insertions(+), 16 deletions(-) create mode 100644 pumpkin/src/block/block_properties_manager.rs create mode 100644 pumpkin/src/block/properties/mod.rs create mode 100644 pumpkin/src/block/properties/slab.rs create mode 100644 pumpkin/src/block/properties/stair.rs diff --git a/pumpkin-world/Cargo.toml b/pumpkin-world/Cargo.toml index 235771f98..36750d444 100644 --- a/pumpkin-world/Cargo.toml +++ b/pumpkin-world/Cargo.toml @@ -21,6 +21,8 @@ serde.workspace = true serde_json.workspace = true log.workspace = true +num-derive = "0.4.2" + dashmap = "6.1.0" num-traits = "0.2" diff --git a/pumpkin-world/src/block/block_registry.rs b/pumpkin-world/src/block/block_registry.rs index 561333c4a..c620d36ab 100644 --- a/pumpkin-world/src/block/block_registry.rs +++ b/pumpkin-world/src/block/block_registry.rs @@ -126,11 +126,10 @@ pub struct Block { pub default_state_id: u16, pub states: Vec, } -#[expect(dead_code)] #[derive(Deserialize, Clone, Debug)] pub struct Property { - name: String, - values: Vec, + pub name: String, + pub values: Vec, } #[derive(Deserialize, Clone, Debug)] pub struct State { diff --git a/pumpkin-world/src/block/mod.rs b/pumpkin-world/src/block/mod.rs index 2a40622d0..82dc02c6c 100644 --- a/pumpkin-world/src/block/mod.rs +++ b/pumpkin-world/src/block/mod.rs @@ -1,10 +1,12 @@ pub mod block_registry; pub mod block_state; +use num_derive::FromPrimitive; use pumpkin_util::math::vector3::Vector3; pub use block_state::BlockState; +#[derive(FromPrimitive, PartialEq, Clone, Copy)] pub enum BlockFace { Bottom = 0, Top, diff --git a/pumpkin-world/src/entity/mod.rs b/pumpkin-world/src/entity/mod.rs index 30282ee57..344618837 100644 --- a/pumpkin-world/src/entity/mod.rs +++ b/pumpkin-world/src/entity/mod.rs @@ -1 +1,9 @@ pub mod entity_registry; + +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum FacingDirection { + North, + South, + East, + West, +} diff --git a/pumpkin/src/block/block_properties_manager.rs b/pumpkin/src/block/block_properties_manager.rs new file mode 100644 index 000000000..1a10a90b2 --- /dev/null +++ b/pumpkin/src/block/block_properties_manager.rs @@ -0,0 +1,135 @@ +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, + }, + entity::FacingDirection, +}; + +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: &FacingDirection, + ) -> 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), + 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)] +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: &FacingDirection, + ) -> 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 b07790339..46de1f4f6 100644 --- a/pumpkin/src/block/mod.rs +++ b/pumpkin/src/block/mod.rs @@ -1,3 +1,4 @@ +use block_properties_manager::BlockPropertiesManager; use blocks::chest::ChestBlock; use blocks::furnace::FurnaceBlock; @@ -7,7 +8,9 @@ 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 pumpkin_block; #[must_use] @@ -21,3 +24,12 @@ pub fn default_block_manager() -> Arc { Arc::new(manager) } + +#[must_use] +pub fn default_block_properties_manager() -> Arc { + let mut manager = BlockPropertiesManager::default(); + + manager.build_properties_registry(); + + Arc::new(manager) +} diff --git a/pumpkin/src/block/properties/mod.rs b/pumpkin/src/block/properties/mod.rs new file mode 100644 index 000000000..c27038e7c --- /dev/null +++ b/pumpkin/src/block/properties/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod slab; +pub(crate) mod stair; diff --git a/pumpkin/src/block/properties/slab.rs b/pumpkin/src/block/properties/slab.rs new file mode 100644 index 000000000..5858703b8 --- /dev/null +++ b/pumpkin/src/block/properties/slab.rs @@ -0,0 +1,167 @@ +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::Property, BlockFace}; +use pumpkin_world::{block::block_registry::Block, entity::FacingDirection}; + +use crate::{ + block::block_properties_manager::{get_property_key, BlockBehavior, BlockProperty}, + 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 evalute_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 evalute_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: &FacingDirection, + ) -> 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::evalute_property_type(block, clicked_block, *face, use_item_on) + } + BlockProperty::Waterlogged(false) => Self::evalute_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 new file mode 100644 index 000000000..7614004e5 --- /dev/null +++ b/pumpkin/src/block/properties/stair.rs @@ -0,0 +1,335 @@ +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}, + entity::FacingDirection, +}; + +use crate::{ + block::block_properties_manager::{get_property_key, BlockBehavior, BlockProperty}, + 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: FacingDirection, + block_pos: BlockPos, + ) -> (BlockPos, BlockPos) { + match player_direction { + FacingDirection::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, + )), + ), + FacingDirection::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, + )), + ), + FacingDirection::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, + )), + ), + FacingDirection::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: &FacingDirection, + ) -> 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 == FacingDirection::West) + || (is_facing_west && *player_direction == FacingDirection::South) + || (is_facing_south && *player_direction == FacingDirection::East) + || (is_facing_east && *player_direction == FacingDirection::North) + { + return "shapeouter_right".to_owned(); + } + + if (is_facing_north && *player_direction == FacingDirection::East) + || (is_facing_west && *player_direction == FacingDirection::North) + || (is_facing_south && *player_direction == FacingDirection::West) + || (is_facing_east && *player_direction == FacingDirection::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 == FacingDirection::West) + || (is_facing_west && *player_direction == FacingDirection::South) + || (is_facing_south && *player_direction == FacingDirection::East) + || (is_facing_east && *player_direction == FacingDirection::North) + { + return "shapeinner_right".to_owned(); + } + + if (is_facing_north && *player_direction == FacingDirection::East) + || (is_facing_west && *player_direction == FacingDirection::North) + || (is_facing_south && *player_direction == FacingDirection::West) + || (is_facing_east && *player_direction == FacingDirection::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: FacingDirection) -> String { + let facing = match face { + BlockFace::North => "south", + BlockFace::South => "north", + BlockFace::East => "west", + BlockFace::West => "east", + BlockFace::Top | BlockFace::Bottom => match player_direction { + FacingDirection::North => "north", + FacingDirection::South => "south", + FacingDirection::East => "east", + FacingDirection::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: &FacingDirection, + ) -> 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), + BlockProperty::SlabType(_) => panic!("SlabType 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 01c0ecfe2..950ea9c79 100644 --- a/pumpkin/src/net/packet/play.rs +++ b/pumpkin/src/net/packet/play.rs @@ -41,7 +41,9 @@ use pumpkin_util::{ text::TextComponent, GameMode, }; -use pumpkin_world::block::block_registry::{get_block_collision_shapes, Block}; +use pumpkin_world::block::block_registry::get_block_collision_shapes; +use pumpkin_world::block::block_registry::Block; +use pumpkin_world::entity::FacingDirection; use pumpkin_world::item::item_registry::get_item_by_id; use pumpkin_world::item::ItemStack; use pumpkin_world::{ @@ -49,6 +51,7 @@ use pumpkin_world::{ entity::entity_registry::get_entity_id, item::item_registry::get_spawn_egg, }; + use pumpkin_world::{WORLD_LOWEST_Y, WORLD_MAX_Y}; use thiserror::Error; @@ -1142,6 +1145,18 @@ impl Player { Ok(true) } + fn get_player_direction(&self) -> FacingDirection { + let adjusted_yaw = (self.living_entity.entity.yaw.load() % 360.0 + 360.0) % 360.0; // Normalize yaw to [0, 360) + + match adjusted_yaw { + 0.0..=45.0 | 315.0..=360.0 => FacingDirection::South, + 45.0..=135.0 => FacingDirection::West, + 135.0..=225.0 => FacingDirection::North, + 225.0..=315.0 => FacingDirection::East, + _ => FacingDirection::South, // Default case, should not occur + } + } + async fn run_is_block_place( &self, block: Block, @@ -1153,6 +1168,9 @@ impl Player { let entity = &self.living_entity.entity; let world = &entity.world; + let clicked_block_pos = BlockPos(location.0); + let clicked_block_state = world.get_block_state(&clicked_block_pos).await?; + // check block under the world if location.0.y + face.to_offset().y < WORLD_LOWEST_Y.into() { self.client @@ -1188,20 +1206,22 @@ impl Player { _ => {} } - let clicked_world_pos = BlockPos(location.0); - let clicked_block_state = world.get_block_state(&clicked_world_pos).await?; + let clicked_block_updated_able = server + .block_properties_manager + .is_updateable(world, &block, face, &clicked_block_pos) + .await; - let world_pos = if clicked_block_state.replaceable { - clicked_world_pos + let final_block_pos = if clicked_block_state.replaceable || clicked_block_updated_able { + clicked_block_pos } else { - let world_pos = BlockPos(location.0 + face.to_offset()); - let previous_block_state = world.get_block_state(&world_pos).await?; + let block_pos = BlockPos(location.0 + face.to_offset()); + let previous_block_state = world.get_block_state(&block_pos).await?; if !previous_block_state.replaceable { return Ok(true); } - world_pos + block_pos }; // To this point we must have the new block state @@ -1210,17 +1230,28 @@ impl Player { let mut intersects = false; for player in world.get_nearby_players(entity.pos.load(), 20.0).await { let bounding_box = player.1.living_entity.entity.bounding_box.load(); - if bounding_box.intersects_block(&world_pos, &block_bounding_box) { + if bounding_box.intersects_block(&final_block_pos, &block_bounding_box) { intersects = true; } } if !intersects { - world - .set_block_state(&world_pos, block.default_state_id) + let mapped_block_id = server + .block_properties_manager + .get_state_id( + world, + &block, + face, + &final_block_pos, + &use_item_on, + &self.get_player_direction(), + ) + .await; + let _replaced_id = world + .set_block_state(&final_block_pos, mapped_block_id) .await; server .block_manager - .on_placed(&block, self, world_pos, server) + .on_placed(&block, self, final_block_pos, server) .await; } self.client diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 479569aaf..b13a28a9b 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -32,7 +32,8 @@ use tokio::sync::{Mutex, RwLock}; use uuid::Uuid; use crate::block::block_manager::BlockManager; -use crate::block::default_block_manager; +use crate::block::block_properties_manager::BlockPropertiesManager; +use crate::block::{default_block_manager, default_block_properties_manager}; use crate::entity::ai::path::Navigator; use crate::entity::living::LivingEntity; use crate::entity::mob::MobEntity; @@ -64,6 +65,8 @@ pub struct Server { pub command_dispatcher: RwLock, /// Saves and calls blocks blocks pub block_manager: Arc, + /// Creates and stores block property registry and managed behaviours. + pub block_properties_manager: Arc, /// Manages multiple worlds within the server. pub worlds: RwLock>>, // All the dimensions that exists on the server, @@ -134,6 +137,7 @@ impl Server { ], command_dispatcher, block_manager: default_block_manager(), + block_properties_manager: default_block_properties_manager(), auth_client, key_store: KeyStore::new(), server_listing: Mutex::new(CachedStatus::new()),