From 7ef2770ee0d7c1fcafb20458a01ff950320255db Mon Sep 17 00:00:00 2001 From: MrMelther <73891959+DarkMrMelther@users.noreply.github.com> Date: Fri, 31 Jan 2025 10:32:58 +0100 Subject: [PATCH] Fixed slabs properties (#515) * Fixed slabs properties Changed a little bit the properties system to make the slabs work properly and the collision calculation when placing them. * Added Layers Used for Snow * Prevent some crashes Fixes https://github.com/Pumpkin-MC/Pumpkin/issues/514 * fix clippy * Improve bounding box * Added margin on intersection Added a margin on intersections calculation to prevent the player to pass through blocks. * Rolling back intersection margin --------- Co-authored-by: Alexander Medvedev --- pumpkin-util/src/math/boundingbox.rs | 90 +++++++------- pumpkin-util/src/math/vector3.rs | 13 ++ pumpkin-world/src/block/block_registry.rs | 17 +-- pumpkin-world/src/block/mod.rs | 10 ++ pumpkin/src/block/properties.rs | 138 +++++++++++++++++----- pumpkin/src/net/packet/play.rs | 61 ++++++---- 6 files changed, 216 insertions(+), 113 deletions(-) diff --git a/pumpkin-util/src/math/boundingbox.rs b/pumpkin-util/src/math/boundingbox.rs index 848b4689f..3bceadf29 100644 --- a/pumpkin-util/src/math/boundingbox.rs +++ b/pumpkin-util/src/math/boundingbox.rs @@ -2,12 +2,8 @@ use super::{position::BlockPos, vector3::Vector3}; #[derive(Clone, Copy)] pub struct BoundingBox { - pub min_x: f64, - pub min_y: f64, - pub min_z: f64, - pub max_x: f64, - pub max_y: f64, - pub max_z: f64, + pub min: Vector3, + pub max: Vector3, } impl BoundingBox { @@ -18,68 +14,62 @@ impl BoundingBox { pub fn new_from_pos(x: f64, y: f64, z: f64, size: &BoundingBoxSize) -> Self { let f = size.width / 2.; Self { - min_x: x - f, - min_y: y, - min_z: z - f, - max_x: x + f, - max_y: y + size.height, - max_z: z + f, + min: Vector3::new(x - f, y, z - f), + max: Vector3::new(x + f, y + size.height, z + f), + } + } + + pub fn offset(&self, other: Self) -> Self { + Self { + min: self.min.add(&other.min), + max: self.max.add(&other.max), } } pub fn new(min: Vector3, max: Vector3) -> Self { + Self { min, max } + } + + pub fn new_array(min: [f64; 3], max: [f64; 3]) -> Self { Self { - min_x: min.x, - min_y: min.y, - min_z: min.z, - max_x: max.x, - max_y: max.y, - max_z: max.z, + min: Vector3::new(min[0], min[1], min[2]), + max: Vector3::new(max[0], max[1], max[2]), } } pub fn from_block(position: &BlockPos) -> Self { let position = position.0; Self { - min_x: position.x as f64, - min_y: position.y as f64, - min_z: position.z as f64, - max_x: (position.x as f64) + 1.0, - max_y: (position.y as f64) + 1.0, - max_z: (position.z as f64) + 1.0, + min: Vector3::new(position.x as f64, position.y as f64, position.z as f64), + max: Vector3::new( + position.x as f64 + 1.0, + position.y as f64 + 1.0, + position.z as f64 + 1.0, + ), + } + } + + pub fn from_block_raw(position: &BlockPos) -> Self { + let position = position.0; + Self { + min: Vector3::new(position.x as f64, position.y as f64, position.z as f64), + max: Vector3::new(position.x as f64, position.y as f64, position.z as f64), } } pub fn intersects(&self, other: &BoundingBox) -> bool { - self.min_x < other.max_x - && self.max_x > other.min_x - && self.min_y < other.max_y - && self.max_y > other.min_y - && self.min_z < other.max_z - && self.max_z > other.min_z - } - - pub fn intersects_block(&self, position: &BlockPos, bounding_box: &[f32]) -> bool { - for i in 0..bounding_box.len() / 6 { - let other = BoundingBox { - min_x: position.0.x as f64 + bounding_box[i * 6] as f64, - min_y: position.0.y as f64 + bounding_box[i * 6 + 1] as f64, - min_z: position.0.z as f64 + bounding_box[i * 6 + 2] as f64, - max_x: position.0.x as f64 + bounding_box[i * 6 + 3] as f64, - max_y: position.0.y as f64 + bounding_box[i * 6 + 4] as f64, - max_z: position.0.z as f64 + bounding_box[i * 6 + 5] as f64, - }; - if self.intersects(&other) { - return true; - } - } - false + self.min.x < other.max.x + && self.max.x > other.min.x + && self.min.y < other.max.y + && self.max.y > other.min.y + && self.min.z < other.max.z + && self.max.z > other.min.z } pub fn squared_magnitude(&self, pos: Vector3) -> f64 { - let d = f64::max(f64::max(self.min_x - pos.x, pos.x - self.max_x), 0.0); - let e = f64::max(f64::max(self.min_y - pos.y, pos.y - self.max_y), 0.0); - let f = f64::max(f64::max(self.min_z - pos.z, pos.z - self.max_z), 0.0); + let d = f64::max(f64::max(self.min.x - pos.x, pos.x - self.max.x), 0.0); + let e = f64::max(f64::max(self.min.y - pos.y, pos.y - self.max.y), 0.0); + let f = f64::max(f64::max(self.min.z - pos.z, pos.z - self.max.z), 0.0); super::squared_magnitude(d, e, f) } } diff --git a/pumpkin-util/src/math/vector3.rs b/pumpkin-util/src/math/vector3.rs index a56be9e16..d4e4197e5 100644 --- a/pumpkin-util/src/math/vector3.rs +++ b/pumpkin-util/src/math/vector3.rs @@ -128,6 +128,19 @@ impl From> for (T, T, T) { } } +impl Vector3 +where + T: Into, +{ + pub fn to_f64(&self) -> Vector3 { + Vector3 { + x: self.x.into(), + y: self.y.into(), + z: self.z.into(), + } + } +} + pub trait Math: Mul //+ Neg diff --git a/pumpkin-world/src/block/block_registry.rs b/pumpkin-world/src/block/block_registry.rs index c620d36ab..1adf55c64 100644 --- a/pumpkin-world/src/block/block_registry.rs +++ b/pumpkin-world/src/block/block_registry.rs @@ -91,18 +91,13 @@ pub fn get_block_by_item<'a>(item_id: u16) -> Option<&'a Block> { BLOCKS_BY_ID.get(block_id) } -pub fn get_block_collision_shapes(block_id: u16) -> Option> { +pub fn get_block_collision_shapes(block_id: u16) -> Option> { let block = BLOCKS_BY_ID.get(&BLOCK_ID_BY_STATE_ID[&block_id])?; let state = &block.states[STATE_INDEX_BY_STATE_ID[&block_id] as usize]; - let mut shapes: Vec = vec![]; + let mut shapes: Vec = vec![]; for i in 0..state.collision_shapes.len() { let shape = &BLOCKS.shapes[state.collision_shapes[i] as usize]; - shapes.push(shape.min[0]); - shapes.push(shape.min[1]); - shapes.push(shape.min[2]); - shapes.push(shape.max[0]); - shapes.push(shape.max[1]); - shapes.push(shape.max[2]); + shapes.push(shape.clone()); } Some(shapes) } @@ -143,7 +138,7 @@ pub struct State { pub block_entity_type: Option, } #[derive(Deserialize, Clone, Debug)] -struct Shape { - min: [f32; 3], - max: [f32; 3], +pub struct Shape { + pub min: [f64; 3], + pub max: [f64; 3], } diff --git a/pumpkin-world/src/block/mod.rs b/pumpkin-world/src/block/mod.rs index 8b6c611e3..bf7607172 100644 --- a/pumpkin-world/src/block/mod.rs +++ b/pumpkin-world/src/block/mod.rs @@ -46,4 +46,14 @@ impl BlockDirection { } .into() } + pub fn opposite(&self) -> BlockDirection { + match self { + BlockDirection::Bottom => BlockDirection::Top, + BlockDirection::Top => BlockDirection::Bottom, + BlockDirection::North => BlockDirection::South, + BlockDirection::South => BlockDirection::North, + BlockDirection::West => BlockDirection::East, + BlockDirection::East => BlockDirection::West, + } + } } diff --git a/pumpkin/src/block/properties.rs b/pumpkin/src/block/properties.rs index 43de85c89..c46787853 100644 --- a/pumpkin/src/block/properties.rs +++ b/pumpkin/src/block/properties.rs @@ -3,7 +3,7 @@ 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}, + block_registry::{Block, State, BLOCKS}, BlockDirection, }; @@ -17,6 +17,7 @@ pub enum BlockProperty { Powered(bool), SlabType(SlabPosition), StairShape(StairShape), + Layers(u8), Half(BlockHalf), // Add other properties as needed } @@ -33,7 +34,7 @@ pub enum BlockHalf { Bottom, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub enum SlabPosition { Top, Bottom, @@ -67,40 +68,84 @@ pub fn get_property_key(property_name: &str) -> Option { "shape" => Some(BlockProperty::StairShape(StairShape::Straight)), "half" => Some(BlockProperty::Half(BlockHalf::Bottom)), "powered" => Some(BlockProperty::Powered(false)), + "layers" => Some(BlockProperty::Layers(1)), "face" => Some(BlockProperty::Face(BlockFace::Wall)), _ => None, } } +#[must_use] +pub fn evaluate_layers( + block: &Block, + clicked_block: &Block, + clicked_block_state: &State, + face: BlockDirection, + use_item_on: &SUseItemOn, + properties: &BlockProperties, + _other: bool, +) -> (String, bool) { + let state = + &properties.property_mappings[&(clicked_block_state.id - clicked_block.states[0].id)]; + for property in state { + // Max + if property == "layers8" { + return (property.clone(), false); + } + + if block.id == clicked_block.id { + dbg!(property); + // bro its is so hacky :crying: + let mut layer: u8 = property.replace("layers", "").parse().unwrap(); + // lets add a new layer + layer += 1; + return (format!("{}{}", "layers", layer), true); + } + } + + let y_pos = use_item_on.cursor_pos.y; + if (y_pos > 0.5 && face != BlockDirection::Bottom) || face == BlockDirection::Top { + return (format!("{}{}", "layers", "1"), false); + } + + (format!("{}{}", "layers", "1"), false) +} + #[must_use] pub fn evaluate_property_type( block: &Block, clicked_block: &Block, + clicked_block_state: &State, 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"); + properties: &BlockProperties, + other: bool, +) -> (String, bool) { + let state = + &properties.property_mappings[&(clicked_block_state.id - clicked_block.states[0].id)]; + for property in state { + if property == "typedouble" { + return (property.clone(), false); } - return format!("{}{}", "type", "bottom"); + if block.id == clicked_block.id { + if property == "typebottom" && face == BlockDirection::Top { + return (format!("{}{}", "type", "double"), true); + } + if property == "typetop" && face == BlockDirection::Bottom { + return (format!("{}{}", "type", "double"), true); + } + if !other { + return (format!("{}{}", "type", "double"), true); + } + } } - format!("{}{}", "type", "bottom") + let y_pos = use_item_on.cursor_pos.y; + if (y_pos > 0.5 && face != BlockDirection::Bottom) || face == BlockDirection::Top { + return (format!("{}{}", "type", "top"), false); + } + + (format!("{}{}", "type", "bottom"), false) } #[must_use] @@ -310,8 +355,8 @@ pub fn evaluate_property_block_face(dir: BlockDirection) -> String { #[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"), + BlockDirection::Bottom => format!("{}{}", "half", "bottom"), + BlockDirection::Top => format!("{}{}", "half", "top"), _ => { if use_item_on.cursor_pos.y > 0.5 { format!("{}{}", "half", "top") @@ -383,7 +428,8 @@ impl BlockPropertiesManager { } } - pub async fn get_state_id( + #[allow(clippy::too_many_arguments)] + pub async fn get_state_data( &self, world: &World, block: &Block, @@ -391,9 +437,11 @@ impl BlockPropertiesManager { block_pos: &BlockPos, use_item_on: &SUseItemOn, player_direction: &Direction, - ) -> u16 { + other: bool, + ) -> (u16, bool) { if let Some(properties) = self.properties_registry.get(&block.id) { let mut hmap_key: Vec = Vec::with_capacity(block.properties.len()); + let mut updateable = false; for raw_property in &block.properties { let property = get_property_key(raw_property.name.as_str()); @@ -401,7 +449,19 @@ impl BlockPropertiesManager { 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) + let clicked_block_state = + world.get_block_state(block_pos).await.unwrap(); + let (state, can_update) = evaluate_property_type( + block, + clicked_block, + clicked_block_state, + *face, + use_item_on, + properties, + other, + ); + updateable = can_update; + state } BlockProperty::Waterlogged(_) => evaluate_property_waterlogged(block), BlockProperty::Facing(_) => { @@ -421,17 +481,37 @@ impl BlockPropertiesManager { } BlockProperty::Powered(_) => "poweredfalse".to_string(), // todo BlockProperty::Face(_) => evaluate_property_block_face(*face), + BlockProperty::Layers(_) => { + let clicked_block = world.get_block(block_pos).await.unwrap(); + let clicked_block_state = + world.get_block_state(block_pos).await.unwrap(); + let (state, can_update) = evaluate_layers( + block, + clicked_block, + clicked_block_state, + *face, + use_item_on, + properties, + other, + ); + updateable = can_update; + state + } }; 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; + return (block.default_state_id, false); } } // Base state id plus offset - return block.states[0].id + properties.state_mappings[&hmap_key]; + let mapping = properties.state_mappings.get(&hmap_key); + if let Some(mapping) = mapping { + return (block.states[0].id + mapping, updateable); + } + log::error!("Failed to get Block Properties mapping for {}", block.name); } - block.default_state_id + (block.default_state_id, false) } } diff --git a/pumpkin/src/net/packet/play.rs b/pumpkin/src/net/packet/play.rs index 192fbe734..4f693ce5b 100644 --- a/pumpkin/src/net/packet/play.rs +++ b/pumpkin/src/net/packet/play.rs @@ -35,6 +35,7 @@ use pumpkin_protocol::{ SSetPlayerGround, SSwingArm, SUseItem, SUseItemOn, Status, }, }; +use pumpkin_util::math::boundingbox::BoundingBox; use pumpkin_util::math::position::BlockPos; use pumpkin_util::text::color::NamedColor; use pumpkin_util::{ @@ -1206,15 +1207,38 @@ impl Player { _ => {} } - let clicked_block_updated_able = false; + let (mut new_state, mut updateable) = server + .block_properties_manager + .get_state_data( + world, + &block, + face, + &clicked_block_pos, + &use_item_on, + &self.get_player_direction(), + true, + ) + .await; - let final_block_pos = if clicked_block_state.replaceable || clicked_block_updated_able { + let final_block_pos = if clicked_block_state.replaceable || updateable { clicked_block_pos } else { let block_pos = BlockPos(location.0 + face.to_offset()); let previous_block_state = world.get_block_state(&block_pos).await?; + (new_state, updateable) = server + .block_properties_manager + .get_state_data( + world, + &block, + &face.opposite(), + &block_pos, + &use_item_on, + &self.get_player_direction(), + false, + ) + .await; - if !previous_block_state.replaceable { + if !previous_block_state.replaceable && !updateable { return Ok(true); } @@ -1222,30 +1246,21 @@ impl Player { }; // To this point we must have the new block state - let block_bounding_box = - get_block_collision_shapes(block.default_state_id).unwrap_or_default(); + let shapes = get_block_collision_shapes(new_state).unwrap_or_default(); 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(&final_block_pos, &block_bounding_box) { - intersects = true; + for player in world.get_nearby_players(location.0.to_f64(), 3.0).await { + let player_box = player.1.living_entity.entity.bounding_box.load(); + for shape in &shapes { + let block_box = BoundingBox::from_block_raw(&final_block_pos) + .offset(BoundingBox::new_array(shape.min, shape.max)); + if player_box.intersects(&block_box) { + intersects = true; + break; + } } } if !intersects { - 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; + let _replaced_id = world.set_block_state(&final_block_pos, new_state).await; server .block_manager .on_placed(&block, self, final_block_pos, server)