mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
Rewrite block properties
This commit is contained in:
@@ -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<i32> for BlockFace {
|
||||
impl TryFrom<i32> for BlockDirection {
|
||||
type Error = InvalidBlockFace;
|
||||
|
||||
fn try_from(value: i32) -> Result<Self, Self::Error> {
|
||||
@@ -34,15 +34,15 @@ impl TryFrom<i32> for BlockFace {
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockFace {
|
||||
impl BlockDirection {
|
||||
pub fn to_offset(&self) -> Vector3<i32> {
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -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<BlockProperty> {
|
||||
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<u16, Arc<dyn BlockBehavior>>,
|
||||
}
|
||||
|
||||
impl BlockPropertiesManager {
|
||||
pub fn build_properties_registry(&mut self) {
|
||||
for block in &BLOCKS.blocks {
|
||||
let behaviour: Arc<dyn BlockBehavior> = 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
|
||||
}
|
||||
}
|
||||
@@ -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]
|
||||
|
||||
437
pumpkin/src/block/properties.rs
Normal file
437
pumpkin/src/block/properties.rs
Normal file
@@ -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<BlockProperty> {
|
||||
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<u16, Vec<String>>,
|
||||
) -> 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<u16, BlockProperties>,
|
||||
}
|
||||
|
||||
pub struct BlockProperties {
|
||||
// Mappings from property state strings -> offset
|
||||
state_mappings: HashMap<Vec<String>, u16>,
|
||||
// Mappings from offset -> property state strings
|
||||
property_mappings: HashMap<u16, Vec<String>>,
|
||||
}
|
||||
|
||||
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<String> = 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<String> = 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
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub(crate) mod slab;
|
||||
pub(crate) mod stair;
|
||||
@@ -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<Arc<SlabBehavior>> = OnceLock::new();
|
||||
|
||||
// Example of a behavior with shared static data
|
||||
pub struct SlabBehavior {
|
||||
// Shared static data for all slabs
|
||||
state_mappings: HashMap<Vec<String>, u16>,
|
||||
property_mappings: HashMap<u16, Vec<String>>,
|
||||
}
|
||||
|
||||
impl SlabBehavior {
|
||||
pub fn get_or_init(properties: &[Property]) -> Arc<Self> {
|
||||
SLAB_BEHAVIOR
|
||||
.get_or_init(|| Arc::new(Self::new(properties)))
|
||||
.clone()
|
||||
}
|
||||
|
||||
pub fn get() -> Arc<Self> {
|
||||
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<String> = 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<String> = 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
|
||||
}
|
||||
}
|
||||
@@ -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<Arc<StairBehavior>> = OnceLock::new();
|
||||
|
||||
/// Behavior for Stairs
|
||||
pub struct StairBehavior {
|
||||
// Mappings from property state strings -> offset
|
||||
state_mappings: HashMap<Vec<String>, u16>,
|
||||
// Mappings from offset -> property state strings
|
||||
property_mappings: HashMap<u16, Vec<String>>,
|
||||
}
|
||||
|
||||
impl StairBehavior {
|
||||
/// Initialize or return the existing `StairBehavior`
|
||||
pub fn get_or_init(properties: &[Property]) -> Arc<Self> {
|
||||
STAIRS_BEHAVIOR
|
||||
.get_or_init(|| Arc::new(Self::new(properties)))
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// Returns the global `StairBehavior` (must have been init once)
|
||||
pub fn get() -> Arc<Self> {
|
||||
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<String> = 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<String> = 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
|
||||
}
|
||||
}
|
||||
@@ -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<bool, Box<dyn PumpkinError>> {
|
||||
// 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<bool, Box<dyn PumpkinError>> {
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user