mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-31 08:22:33 +00:00
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 <lilalexmed@proton.me>
This commit is contained in:
@@ -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<f64>,
|
||||
pub max: Vector3<f64>,
|
||||
}
|
||||
|
||||
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<f64>, max: Vector3<f64>) -> 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>) -> 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,6 +128,19 @@ impl<T> From<Vector3<T>> for (T, T, T) {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Math + Copy> Vector3<T>
|
||||
where
|
||||
T: Into<f64>,
|
||||
{
|
||||
pub fn to_f64(&self) -> Vector3<f64> {
|
||||
Vector3 {
|
||||
x: self.x.into(),
|
||||
y: self.y.into(),
|
||||
z: self.z.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Math:
|
||||
Mul<Output = Self>
|
||||
//+ Neg<Output = Self>
|
||||
|
||||
@@ -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<Vec<f32>> {
|
||||
pub fn get_block_collision_shapes(block_id: u16) -> Option<Vec<Shape>> {
|
||||
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<f32> = vec![];
|
||||
let mut shapes: Vec<Shape> = 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<u32>,
|
||||
}
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
struct Shape {
|
||||
min: [f32; 3],
|
||||
max: [f32; 3],
|
||||
pub struct Shape {
|
||||
pub min: [f64; 3],
|
||||
pub max: [f64; 3],
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<BlockProperty> {
|
||||
"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<String> = 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user