Reworking block properties (#529)

* Added 11 blocks properties

Added the following properties:
Axis
Open
Age
Attachment
Signal Fire
6 connection directions

* Clippy my beloved 💕

* Reworked block properties

Changed block properties to make them more extensible and easier to handle.

* Little too much of Clippy

* Lever works!!! 🎉

Made the lever work, implementing modifiers to the “on_place” per block, plus the “on_interact” function to handle the state when interacting with a block that has it.

* Kinda implemented bool as value

* A little bit of Clippy :)
This commit is contained in:
MrMelther
2025-02-09 18:45:18 +01:00
committed by GitHub
parent 8592d095f3
commit daa67c2dfa
25 changed files with 1509 additions and 536 deletions

View File

@@ -12,6 +12,7 @@ pumpkin-data = { path = "../pumpkin-data" }
serde.workspace = true
serde_json.workspace = true
heck = "0.5"
proc-macro2 = "1.0"
quote = "1.0"
syn = "2.0"

View File

@@ -1,3 +1,4 @@
use heck::{ToPascalCase, ToSnakeCase};
use proc_macro::TokenStream;
use pumpkin_data::item::Item;
use quote::quote;
@@ -238,6 +239,142 @@ pub fn pumpkin_item(input: TokenStream, item: TokenStream) -> TokenStream {
gen.into()
}
#[proc_macro_attribute]
pub fn block_property(input: TokenStream, item: TokenStream) -> TokenStream {
let ast: syn::DeriveInput = syn::parse(item.clone()).unwrap();
let name = &ast.ident;
let (impl_generics, ty_generics, _) = ast.generics.split_for_impl();
let input_string = input.to_string();
let input_parts: Vec<&str> = input_string.split("[").collect();
let property_name = input_parts[0].trim_ascii().trim_matches(&['"', ','][..]);
let mut property_values: Vec<&str> = Vec::new();
if input_parts.len() > 1 {
property_values = input_parts[1]
.trim_matches(']')
.split(", ")
.map(|p| p.trim_ascii().trim_matches(&['"', ','][..]))
.collect::<Vec<&str>>();
}
let item: proc_macro2::TokenStream = item.into();
let (variants, is_enum): (Vec<proc_macro2::Ident>, bool) = match ast.data {
syn::Data::Enum(enum_item) => (
enum_item.variants.into_iter().map(|v| v.ident).collect(),
true,
),
syn::Data::Struct(struct_type) => {
let fields = match struct_type.fields {
Fields::Named(_) => panic!("Block properties can't have named fields"),
Fields::Unnamed(fields) => fields.unnamed,
Fields::Unit => panic!("Block properties must have fields"),
};
if fields.len() != 1 {
panic!("Block properties structs must have exactly one field");
}
let struct_type = match fields.first().unwrap().ty {
syn::Type::Path(ref type_path) => {
type_path.path.segments.first().unwrap().ident.to_string()
}
_ => panic!("Block properties can only have primitive types"),
};
match struct_type.as_str() {
"bool" => (
vec![
proc_macro2::Ident::new("true", proc_macro2::Span::call_site()),
proc_macro2::Ident::new("false", proc_macro2::Span::call_site()),
],
false,
),
_ => panic!("This type is not supported (Why not implement it yourself?)"),
}
}
_ => panic!("Block properties can only be enums or structs"),
};
let values = variants.iter().enumerate().map(|(i, v)| match is_enum {
true => {
let mut value = v.to_string().to_snake_case();
if !property_values.is_empty() && i < property_values.len() {
value = property_values[i].to_string();
}
quote! {
Self::#v => #value.to_string(),
}
}
false => {
let value = v.to_string();
quote! {
Self(#v) => #value.to_string(),
}
}
});
let from_values = variants.iter().enumerate().map(|(i, v)| match is_enum {
true => {
let mut value = v.to_string().to_snake_case();
if !property_values.is_empty() && i < property_values.len() {
value = property_values[i].to_string();
}
quote! {
#value => Self::#v,
}
}
false => {
let value = v.to_string();
quote! {
#value => Self(#v),
}
}
});
let extra_fns = variants.iter().map(|v| {
let title = proc_macro2::Ident::new(
&v.to_string().to_pascal_case(),
proc_macro2::Span::call_site(),
);
quote! {
pub fn #title() -> Self {
Self(#v)
}
}
});
let extra = if is_enum {
quote! {}
} else {
quote! {
impl #name {
#(#extra_fns)*
}
}
};
let gen = quote! {
#item
impl #impl_generics crate::block::properties::BlockPropertyMetadata for #name #ty_generics {
fn name(&self) -> &'static str {
#property_name
}
fn value(&self) -> String {
match self {
#(#values)*
}
}
fn from_value(value: String) -> Self {
match value.as_str() {
#(#from_values)*
_ => panic!("Invalid value for block property"),
}
}
}
#extra
};
gen.into()
}
mod block_state;
#[proc_macro]
pub fn block_state(item: TokenStream) -> TokenStream {

View File

@@ -158,6 +158,22 @@ where
}
}
impl<T: Math + Copy> Vector3<T>
where
T: Into<f64>,
{
pub fn to_i32(&self) -> Vector3<i32> {
let x: f64 = self.x.into();
let y: f64 = self.y.into();
let z: f64 = self.z.into();
Vector3 {
x: x.round() as i32,
y: y.round() as i32,
z: z.round() as i32,
}
}
}
pub trait Math:
Mul<Output = Self>
//+ Neg<Output = Self>

View File

@@ -0,0 +1,60 @@
use crate::entity::player::Player;
use async_trait::async_trait;
use pumpkin_data::item::Item;
use pumpkin_macros::pumpkin_block;
use pumpkin_protocol::server::play::SUseItemOn;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::block::{registry::Block, BlockDirection};
use crate::{
block::{properties::Direction, pumpkin_block::PumpkinBlock, registry::BlockActionResult},
server::Server,
world::World,
};
#[pumpkin_block("minecraft:lever")]
pub struct LeverBlock;
#[async_trait]
impl PumpkinBlock for LeverBlock {
async fn on_place(
&self,
server: &Server,
world: &World,
block: &Block,
face: &BlockDirection,
block_pos: &BlockPos,
use_item_on: &SUseItemOn,
player_direction: &Direction,
other: bool,
) -> u16 {
let face = match face {
BlockDirection::Bottom | BlockDirection::Top => *face,
_ => face.opposite(),
};
server
.block_properties_manager
.on_place_state(
world,
block,
&face,
block_pos,
use_item_on,
player_direction,
other,
)
.await
}
async fn use_with_item(
&self,
_block: &Block,
_player: &Player,
_location: BlockPos,
_item: &Item,
_server: &Server,
) -> BlockActionResult {
BlockActionResult::Consume
}
}

View File

@@ -9,6 +9,7 @@ pub(crate) mod chest;
pub(crate) mod crafting_table;
pub(crate) mod furnace;
pub(crate) mod jukebox;
pub(crate) mod lever;
/// The standard destroy with container removes the player forcibly from the container,
/// drops items to the floor, and back to the player's inventory if the item stack is in movement.

View File

@@ -1,6 +1,21 @@
use blocks::chest::ChestBlock;
use blocks::furnace::FurnaceBlock;
use properties::BlockPropertiesManager;
use blocks::{chest::ChestBlock, furnace::FurnaceBlock, lever::LeverBlock};
use properties::{
age::Age,
attachment::Attachment,
axis::Axis,
cardinal::{Down, East, North, South, Up, West},
face::Face,
facing::Facing,
half::Half,
layers::Layers,
open::Open,
powered::Powered,
signal_fire::SignalFire,
slab_type::SlabType,
stair_shape::StairShape,
waterlog::Waterlogged,
BlockPropertiesManager,
};
use pumpkin_data::entity::EntityType;
use pumpkin_data::item::Item;
use pumpkin_util::math::position::BlockPos;
@@ -30,6 +45,7 @@ pub fn default_registry() -> Arc<BlockRegistry> {
manager.register(CraftingTableBlock);
manager.register(FurnaceBlock);
manager.register(ChestBlock);
manager.register(LeverBlock);
Arc::new(manager)
}
@@ -56,6 +72,27 @@ pub async fn drop_loot(server: &Server, world: &Arc<World>, block: &Block, pos:
pub fn default_block_properties_manager() -> Arc<BlockPropertiesManager> {
let mut manager = BlockPropertiesManager::default();
// This is the default state of the blocks
manager.register(Age::Age0);
manager.register(Attachment::Floor);
manager.register(Axis::Y);
manager.register(Down::False);
manager.register(East::False);
manager.register(Face::Floor);
manager.register(Facing::North);
manager.register(Half::Bottom);
manager.register(Layers::Lay1);
manager.register(North::False);
manager.register(Open::False());
manager.register(Powered::False());
manager.register(SignalFire::False());
manager.register(SlabType::Bottom);
manager.register(South::False);
manager.register(StairShape::Straight);
manager.register(Up::False);
manager.register(Waterlogged::False());
manager.register(West::False);
manager.build_properties_registry();
Arc::new(manager)

View File

@@ -1,516 +0,0 @@
use std::collections::HashMap;
use pumpkin_protocol::server::play::SUseItemOn;
use pumpkin_util::math::{position::BlockPos, vector3::Vector3};
use pumpkin_world::block::{
registry::{Block, State, BLOCKS},
BlockDirection,
};
use crate::world::World;
#[derive(Clone, Debug)]
pub enum BlockProperty {
Waterlogged(bool),
Facing(Direction),
Face(BlockFace),
Powered(bool),
SlabType(SlabPosition),
StairShape(StairShape),
Layers(u8),
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, PartialEq)]
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 automatically 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)),
"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 {
// 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,
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);
}
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);
}
}
}
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]
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::Bottom => format!("{}{}", "half", "bottom"),
BlockDirection::Top => 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,
},
);
}
}
#[allow(clippy::too_many_arguments)]
pub async fn get_state_data(
&self,
world: &World,
block: &Block,
face: &BlockDirection,
block_pos: &BlockPos,
use_item_on: &SUseItemOn,
player_direction: &Direction,
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());
if let Some(property) = property {
let state = match property {
BlockProperty::SlabType(_) => {
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_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(_) => {
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),
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, false);
}
}
// Base state id plus offset
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, false)
}
}

View File

@@ -0,0 +1,28 @@
use async_trait::async_trait;
use pumpkin_macros::block_property;
use super::BlockProperty;
// Those which requires custom names to values can be defined like this
#[block_property("age", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])]
pub enum Age {
Age0,
Age1,
Age2,
Age3,
Age4,
Age5,
Age6,
Age7,
Age8,
Age9,
Age10,
Age11,
Age12,
Age13,
Age14,
Age15,
}
#[async_trait]
impl BlockProperty for Age {}

View File

@@ -0,0 +1,44 @@
use crate::world::World;
use async_trait::async_trait;
use pumpkin_macros::block_property;
use pumpkin_protocol::server::play::SUseItemOn;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::block::{registry::Block, BlockDirection};
use super::{BlockProperties, BlockProperty, BlockPropertyMetadata, Direction};
#[block_property("attachment")]
pub enum Attachment {
Floor,
Ceiling,
SingleWall,
DoubleWall,
}
#[async_trait]
impl BlockProperty for Attachment {
async fn on_place(
&self,
world: &World,
_block: &Block,
face: &BlockDirection,
block_pos: &BlockPos,
_use_item_on: &SUseItemOn,
_player_direction: &Direction,
_properties: &BlockProperties,
_other: bool,
) -> String {
match face {
BlockDirection::Top => Self::Ceiling.value(),
BlockDirection::Bottom => Self::Floor.value(),
_ => {
let other_side_block = BlockPos(block_pos.0.sub(&face.to_offset()));
let block = world.get_block(&other_side_block).await.unwrap();
if block.id != 0 {
return Self::DoubleWall.value();
}
Self::SingleWall.value()
}
}
}
}

View File

@@ -0,0 +1,36 @@
use crate::world::World;
use async_trait::async_trait;
use pumpkin_macros::block_property;
use pumpkin_protocol::server::play::SUseItemOn;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::block::{registry::Block, BlockDirection};
use super::{BlockProperties, BlockProperty, BlockPropertyMetadata, Direction};
#[block_property("axis")]
pub enum Axis {
X,
Y,
Z,
}
#[async_trait]
impl BlockProperty for Axis {
async fn on_place(
&self,
_world: &World,
_block: &Block,
face: &BlockDirection,
_block_pos: &BlockPos,
_use_item_on: &SUseItemOn,
_player_direction: &Direction,
_properties: &BlockProperties,
_other: bool,
) -> String {
match face {
BlockDirection::North | BlockDirection::South => Self::Z.value(),
BlockDirection::East | BlockDirection::West => Self::X.value(),
BlockDirection::Top | BlockDirection::Bottom => Self::Y.value(),
}
}
}

View File

@@ -0,0 +1,210 @@
use crate::world::World;
use async_trait::async_trait;
use pumpkin_macros::block_property;
use pumpkin_protocol::server::play::SUseItemOn;
use pumpkin_util::math::{position::BlockPos, vector3::Vector3};
use pumpkin_world::block::{registry::Block, BlockDirection};
use super::{BlockProperties, BlockProperty, BlockPropertyMetadata, Direction};
#[block_property("down")]
pub enum Down {
True,
False,
Tall,
Low,
None,
}
#[block_property("east")]
pub enum East {
True,
False,
Tall,
Low,
None,
}
#[block_property("north")]
pub enum North {
True,
False,
Tall,
Low,
None,
}
#[block_property("south")]
pub enum South {
True,
False,
Tall,
Low,
None,
}
#[block_property("up")]
pub enum Up {
True,
False,
Tall,
Low,
None,
}
#[block_property("west")]
pub enum West {
True,
False,
Tall,
Low,
None,
}
pub async fn evaluate_fence_direction(
world: &World,
placed_block: &Block,
face: &BlockDirection,
block_pos: &BlockPos,
) -> String {
let other_side_block = BlockPos(block_pos.0.add(&face.to_offset()));
let block = world.get_block(&other_side_block).await.unwrap();
if placed_block.name.ends_with("_wall") {
if face == &BlockDirection::Top {
if block.id != 0 {
return North::True.value();
}
let mut connections = 0u8;
let mut x = 0u8;
let mut z = 0u8;
for side in &[
BlockDirection::North,
BlockDirection::East,
BlockDirection::South,
BlockDirection::West,
] {
let other_side_block = BlockPos(block_pos.0.add(&side.to_offset()));
let block = world.get_block(&other_side_block).await.unwrap();
if block.id != 0 {
connections += 1;
if *side == BlockDirection::North || *side == BlockDirection::South {
x += 1;
} else {
z += 1;
}
}
}
if connections > 2 || x == 2 || z == 2 {
return North::False.value();
}
return North::True.value();
}
if block.id != 0 {
let other_side_block = BlockPos(other_side_block.0.add(&Vector3::new(0, 1, 0)));
let block = world.get_block(&other_side_block).await.unwrap();
if block.id != 0 && block.name.ends_with("_wall") {
return North::Tall.value();
}
return North::Low.value();
}
return North::None.value();
}
if block.id != 0 {
return North::True.value();
}
North::False.value()
}
#[async_trait]
impl BlockProperty for Down {
async fn on_place(
&self,
world: &World,
block: &Block,
face: &BlockDirection,
block_pos: &BlockPos,
_use_item_on: &SUseItemOn,
_player_direction: &Direction,
_properties: &BlockProperties,
_other: bool,
) -> String {
evaluate_fence_direction(world, block, face, block_pos).await
}
}
#[async_trait]
impl BlockProperty for East {
async fn on_place(
&self,
world: &World,
block: &Block,
face: &BlockDirection,
block_pos: &BlockPos,
_use_item_on: &SUseItemOn,
_player_direction: &Direction,
_properties: &BlockProperties,
_other: bool,
) -> String {
evaluate_fence_direction(world, block, face, block_pos).await
}
}
#[async_trait]
impl BlockProperty for North {
async fn on_place(
&self,
world: &World,
block: &Block,
face: &BlockDirection,
block_pos: &BlockPos,
_use_item_on: &SUseItemOn,
_player_direction: &Direction,
_properties: &BlockProperties,
_other: bool,
) -> String {
evaluate_fence_direction(world, block, face, block_pos).await
}
}
#[async_trait]
impl BlockProperty for South {
async fn on_place(
&self,
world: &World,
block: &Block,
face: &BlockDirection,
block_pos: &BlockPos,
_use_item_on: &SUseItemOn,
_player_direction: &Direction,
_properties: &BlockProperties,
_other: bool,
) -> String {
evaluate_fence_direction(world, block, face, block_pos).await
}
}
#[async_trait]
impl BlockProperty for Up {
async fn on_place(
&self,
world: &World,
block: &Block,
face: &BlockDirection,
block_pos: &BlockPos,
_use_item_on: &SUseItemOn,
_player_direction: &Direction,
_properties: &BlockProperties,
_other: bool,
) -> String {
evaluate_fence_direction(world, block, face, block_pos).await
}
}
#[async_trait]
impl BlockProperty for West {
async fn on_place(
&self,
world: &World,
block: &Block,
face: &BlockDirection,
block_pos: &BlockPos,
_use_item_on: &SUseItemOn,
_player_direction: &Direction,
_properties: &BlockProperties,
_other: bool,
) -> String {
evaluate_fence_direction(world, block, face, block_pos).await
}
}

View File

@@ -0,0 +1,37 @@
use crate::world::World;
use async_trait::async_trait;
use pumpkin_macros::block_property;
use pumpkin_protocol::server::play::SUseItemOn;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::block::{registry::Block, BlockDirection};
use super::{BlockProperties, BlockProperty, BlockPropertyMetadata, Direction};
#[block_property("face")]
pub enum Face {
Ceiling,
Floor,
Wall,
}
#[async_trait]
impl BlockProperty for Face {
async fn on_place(
&self,
_world: &World,
_block: &Block,
face: &BlockDirection,
_block_pos: &BlockPos,
_use_item_on: &SUseItemOn,
_player_direction: &Direction,
_properties: &BlockProperties,
_other: bool,
) -> String {
let face = match face {
BlockDirection::Top => Self::Ceiling,
BlockDirection::Bottom => Self::Floor,
_ => Self::Wall,
};
face.value()
}
}

View File

@@ -0,0 +1,46 @@
use crate::world::World;
use async_trait::async_trait;
use pumpkin_macros::block_property;
use pumpkin_protocol::server::play::SUseItemOn;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::block::{registry::Block, BlockDirection};
use super::{BlockProperties, BlockProperty, BlockPropertyMetadata, Direction};
#[block_property("facing")]
pub enum Facing {
North,
South,
East,
West,
}
#[async_trait]
impl BlockProperty for Facing {
async fn on_place(
&self,
_world: &World,
_block: &Block,
face: &BlockDirection,
_block_pos: &BlockPos,
_use_item_on: &SUseItemOn,
player_direction: &Direction,
_properties: &BlockProperties,
_other: bool,
) -> String {
// Some blocks have also facing with top and bottom
let facing = match face {
BlockDirection::North => Self::North,
BlockDirection::South => Self::South,
BlockDirection::East => Self::East,
BlockDirection::West => Self::West,
BlockDirection::Top | BlockDirection::Bottom => match player_direction {
Direction::North => Self::North,
Direction::South => Self::South,
Direction::East => Self::East,
Direction::West => Self::West,
},
};
facing.value()
}
}

View File

@@ -0,0 +1,45 @@
use crate::world::World;
use async_trait::async_trait;
use pumpkin_macros::block_property;
use pumpkin_protocol::server::play::SUseItemOn;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::block::{registry::Block, BlockDirection};
use super::{BlockProperties, BlockProperty, BlockPropertyMetadata, Direction};
#[block_property("half")]
pub enum Half {
Top,
Bottom,
}
#[async_trait]
impl BlockProperty for Half {
async fn on_place(
&self,
_world: &World,
_block: &Block,
face: &BlockDirection,
_block_pos: &BlockPos,
use_item_on: &SUseItemOn,
_player_direction: &Direction,
_properties: &BlockProperties,
_other: bool,
) -> String {
evaluate_half(*face, use_item_on)
}
}
pub fn evaluate_half(face: BlockDirection, use_item_on: &SUseItemOn) -> String {
match face {
BlockDirection::Bottom => Half::Bottom.value(),
BlockDirection::Top => Half::Top.value(),
_ => {
if use_item_on.cursor_pos.y > 0.5 {
Half::Top.value()
} else {
Half::Bottom.value()
}
}
}
}

View File

@@ -0,0 +1,79 @@
use crate::world::World;
use async_trait::async_trait;
use pumpkin_macros::block_property;
use pumpkin_protocol::server::play::SUseItemOn;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::block::{
registry::{Block, State},
BlockDirection,
};
use super::{BlockProperties, BlockProperty, BlockPropertyMetadata, Direction};
// Those which requires custom names to values can be defined like this
#[block_property("layers", [1, 2, 3, 4, 5, 6, 7, 8])]
pub enum Layers {
Lay1,
Lay2,
Lay3,
Lay4,
Lay5,
Lay6,
Lay7,
Lay8,
}
#[async_trait]
impl BlockProperty for Layers {
async fn on_place(
&self,
world: &World,
block: &Block,
_face: &BlockDirection,
block_pos: &BlockPos,
_use_item_on: &SUseItemOn,
_player_direction: &Direction,
properties: &BlockProperties,
_other: bool,
) -> String {
let clicked_block = world.get_block(block_pos).await.unwrap();
let clicked_block_state = world.get_block_state(block_pos).await.unwrap();
let state =
&properties.property_mappings[&(clicked_block_state.id - clicked_block.states[0].id)];
for property in state {
if block.id == clicked_block.id {
// bro its is so hacky :crying:
let mut layer: u8 = property.parse().unwrap();
// lets add a new layer
layer += 1;
return Self::from_value(layer.to_string()).value();
}
}
Self::Lay1.value()
}
async fn can_update(
&self,
value: String,
block: &Block,
_block_state: &State,
face: &BlockDirection,
_use_item_on: &SUseItemOn,
other: bool,
) -> bool {
if value == Self::Lay8.value() {
return false;
}
if value == Self::Lay1.value() {
return true;
}
if !other {
match face {
BlockDirection::Top => return block.name == "snow",
_ => return false,
}
}
true
}
}

View File

@@ -0,0 +1,251 @@
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::{
registry::{Block, State, BLOCKS},
BlockDirection,
},
item::ItemStack,
};
pub(crate) mod age;
pub(crate) mod attachment;
pub(crate) mod axis;
pub(crate) mod cardinal;
pub(crate) mod face;
pub(crate) mod facing;
pub(crate) mod half;
pub(crate) mod layers;
pub(crate) mod open;
pub(crate) mod powered;
pub(crate) mod signal_fire;
pub(crate) mod slab_type;
pub(crate) mod stair_shape;
pub(crate) mod waterlog;
use crate::world::World;
#[derive(Clone, Debug, PartialEq)]
pub enum Direction {
North,
South,
East,
West,
}
pub trait BlockPropertyMetadata: Sync + Send {
fn name(&self) -> &'static str;
fn value(&self) -> String;
fn from_value(value: String) -> Self
where
Self: Sized;
}
#[async_trait]
#[allow(clippy::too_many_arguments)]
pub trait BlockProperty: Sync + Send + BlockPropertyMetadata {
async fn on_place(
&self,
_world: &World,
_block: &Block,
_face: &BlockDirection,
_block_pos: &BlockPos,
_use_item_on: &SUseItemOn,
_player_direction: &Direction,
_properties: &BlockProperties,
_other: bool,
) -> String {
self.value()
}
async fn can_update(
&self,
_value: String,
_block: &Block,
block_state: &State,
_face: &BlockDirection,
_use_item_on: &SUseItemOn,
_other: bool,
) -> bool {
block_state.replaceable
}
async fn on_interact(&self, value: String, _block: &Block, _item: &ItemStack) -> String {
value
}
}
#[derive(Default)]
pub struct BlockPropertiesManager {
properties_registry: HashMap<u16, BlockProperties>,
// Properties that are implemented
registered_properties: HashMap<String, Arc<dyn BlockProperty>>,
}
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)| properties[prop_idx].values[state_idx].clone())
.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 fn register<T: BlockProperty + 'static>(&mut self, property: T) {
self.registered_properties
.insert(property.name().to_string(), Arc::new(property));
}
pub async fn can_update(
&self,
block: &Block,
block_state: &State,
face: &BlockDirection,
use_item_on: &SUseItemOn,
other: bool,
) -> bool {
if let Some(properties) = self.properties_registry.get(&block.id) {
let key = block_state.id - block.states[0].id;
let property_states = properties.property_mappings.get(&key).unwrap();
for (i, property_value) in property_states.iter().enumerate() {
let property_name = block.properties[i].name.clone();
if let Some(property) = self.registered_properties.get(&property_name) {
if property
.can_update(
property_value.clone(),
block,
block_state,
face,
use_item_on,
other,
)
.await
{
return true;
}
}
}
}
false
}
#[allow(clippy::too_many_arguments)]
pub async fn on_place_state(
&self,
world: &World,
block: &Block,
face: &BlockDirection,
block_pos: &BlockPos,
use_item_on: &SUseItemOn,
player_direction: &Direction,
other: bool,
) -> 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 = self.registered_properties.get(&raw_property.name);
if let Some(property) = property {
let state = property
.on_place(
world,
block,
face,
block_pos,
use_item_on,
player_direction,
properties,
other,
)
.await;
hmap_key.push(state);
} 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
let mapping = properties.state_mappings.get(&hmap_key);
if let Some(mapping) = mapping {
return block.states[0].id + mapping;
}
log::error!("Failed to get Block Properties mapping for {}", block.name);
}
block.default_state_id
}
pub async fn on_interact(&self, block: &Block, block_state: &State, item: &ItemStack) -> u16 {
if let Some(properties) = self.properties_registry.get(&block.id) {
if let Some(states) = properties
.property_mappings
.get(&(block_state.id - block.states[0].id))
{
let mut hmap_key: Vec<String> = Vec::with_capacity(block.properties.len());
for (i, raw_property) in block.properties.iter().enumerate() {
let property = self.registered_properties.get(&raw_property.name);
if let Some(property) = property {
let state = property.on_interact(states[i].clone(), block, item).await;
hmap_key.push(state);
} 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
let mapping = properties.state_mappings.get(&hmap_key);
if let Some(mapping) = mapping {
return block.states[0].id + mapping;
}
log::error!("Failed to get Block Properties mapping for {}", block.name);
}
}
block_state.id
}
}

View File

@@ -0,0 +1,9 @@
use super::BlockProperty;
use async_trait::async_trait;
use pumpkin_macros::block_property;
#[block_property("open")]
pub struct Open(bool);
#[async_trait]
impl BlockProperty for Open {}

View File

@@ -0,0 +1,20 @@
use async_trait::async_trait;
use pumpkin_macros::block_property;
use pumpkin_world::block::registry::Block;
use pumpkin_world::item::ItemStack;
use super::{BlockProperty, BlockPropertyMetadata};
#[block_property("powered")]
pub struct Powered(bool);
#[async_trait]
impl BlockProperty for Powered {
async fn on_interact(&self, value: String, _block: &Block, _item: &ItemStack) -> String {
if value == Self::True().value() {
Self::False().value()
} else {
Self::True().value()
}
}
}

View File

@@ -0,0 +1,33 @@
use crate::world::World;
use async_trait::async_trait;
use pumpkin_macros::block_property;
use pumpkin_protocol::server::play::SUseItemOn;
use pumpkin_util::math::{position::BlockPos, vector3::Vector3};
use pumpkin_world::block::{registry::Block, BlockDirection};
use super::{BlockProperties, BlockProperty, BlockPropertyMetadata, Direction};
#[block_property("signal_fire")]
pub struct SignalFire(bool);
#[async_trait]
impl BlockProperty for SignalFire {
async fn on_place(
&self,
world: &World,
_block: &Block,
_face: &BlockDirection,
block_pos: &BlockPos,
_use_item_on: &SUseItemOn,
_player_direction: &Direction,
_properties: &BlockProperties,
_other: bool,
) -> String {
let other_side_block = BlockPos(block_pos.0.sub(&Vector3::new(0, 1, 0)));
let block = world.get_block(&other_side_block).await.unwrap();
if block.name == "hay_block" {
return Self::True().value();
}
Self::False().value()
}
}

View File

@@ -0,0 +1,78 @@
use crate::world::World;
use async_trait::async_trait;
use pumpkin_macros::block_property;
use pumpkin_protocol::server::play::SUseItemOn;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::block::{
registry::{Block, State},
BlockDirection,
};
use super::{BlockProperties, BlockProperty, BlockPropertyMetadata, Direction};
#[block_property("type")]
pub enum SlabType {
Top,
Bottom,
Double,
}
#[async_trait]
impl BlockProperty for SlabType {
async fn on_place(
&self,
world: &World,
block: &Block,
face: &BlockDirection,
block_pos: &BlockPos,
use_item_on: &SUseItemOn,
_player_direction: &Direction,
_properties: &BlockProperties,
other: bool,
) -> String {
let clicked_block = world.get_block(block_pos).await.unwrap();
if block.id == clicked_block.id && !other {
return Self::Double.value();
}
let y_pos = use_item_on.cursor_pos.y;
if (y_pos > 0.5 && face != &BlockDirection::Bottom) || face == &BlockDirection::Top {
return Self::Top.value();
}
Self::Bottom.value()
}
async fn can_update(
&self,
value: String,
_block: &Block,
_block_state: &State,
face: &BlockDirection,
use_item_on: &SUseItemOn,
other: bool,
) -> bool {
if other {
let y = use_item_on.cursor_pos.y;
match face {
BlockDirection::Top => return value == Self::Bottom.value(),
BlockDirection::Bottom => return value == Self::Top.value(),
_ => {
if y < 0.5 {
return value == Self::Top.value();
}
return value == Self::Bottom.value();
}
}
}
if value == Self::Double.value() {
return false;
}
match face {
BlockDirection::Top => value == Self::Bottom.value(),
BlockDirection::Bottom => value == Self::Top.value(),
_ => false,
}
}
}

View File

@@ -0,0 +1,182 @@
use crate::{
block::properties::{facing::Facing, half::evaluate_half, BlockPropertyMetadata},
world::World,
};
use async_trait::async_trait;
use pumpkin_macros::block_property;
use pumpkin_protocol::server::play::SUseItemOn;
use pumpkin_util::math::{position::BlockPos, vector3::Vector3};
use pumpkin_world::block::{registry::Block, BlockDirection};
use super::{BlockProperties, BlockProperty, Direction};
#[block_property("shape")]
pub enum StairShape {
Straight,
InnerLeft,
InnerRight,
OuterLeft,
OuterRight,
}
#[async_trait]
impl BlockProperty for StairShape {
async fn on_place(
&self,
world: &World,
_block: &Block,
face: &BlockDirection,
block_pos: &BlockPos,
use_item_on: &SUseItemOn,
player_direction: &Direction,
properties: &BlockProperties,
_other: bool,
) -> String {
let block_half = evaluate_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) = properties.property_mappings.get(&key) {
if properties.contains(&Self::Straight.value())
&& properties.contains(&block_half)
{
let is_facing_north = properties.contains(&Facing::North.value());
let is_facing_west = properties.contains(&Facing::West.value());
let is_facing_south = properties.contains(&Facing::South.value());
let is_facing_east = properties.contains(&Facing::East.value());
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 Self::OuterRight.value();
}
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 Self::OuterLeft.value();
}
}
}
} 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) = properties.property_mappings.get(&key) {
if properties.contains(&Self::Straight.value())
&& properties.contains(&block_half)
{
let is_facing_north = properties.contains(&Facing::North.value());
let is_facing_west = properties.contains(&Facing::West.value());
let is_facing_south = properties.contains(&Facing::South.value());
let is_facing_east = properties.contains(&Facing::East.value());
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 Self::InnerRight.value();
}
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 Self::InnerLeft.value();
}
}
}
} 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.
Self::Straight.value()
}
}
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,
)),
),
}
}

View File

@@ -0,0 +1,31 @@
use crate::world::World;
use async_trait::async_trait;
use pumpkin_macros::block_property;
use pumpkin_protocol::server::play::SUseItemOn;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::block::{registry::Block, BlockDirection};
use super::{BlockProperties, BlockProperty, BlockPropertyMetadata, Direction};
#[block_property("waterlogged")]
pub struct Waterlogged(bool);
#[async_trait]
impl BlockProperty for Waterlogged {
async fn on_place(
&self,
_world: &World,
block: &Block,
_face: &BlockDirection,
_block_pos: &BlockPos,
_use_item_on: &SUseItemOn,
_player_direction: &Direction,
_properties: &BlockProperties,
_other: bool,
) -> String {
if block.name == "water" {
return Self::True().value();
}
Self::False().value()
}
}

View File

@@ -1,11 +1,16 @@
use crate::block::registry::BlockActionResult;
use crate::entity::player::Player;
use crate::server::Server;
use crate::world::World;
use async_trait::async_trait;
use pumpkin_data::item::Item;
use pumpkin_inventory::OpenContainer;
use pumpkin_protocol::server::play::SUseItemOn;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::block::registry::Block;
use pumpkin_world::block::BlockDirection;
use super::properties::Direction;
pub trait BlockMetadata {
const NAMESPACE: &'static str;
@@ -36,6 +41,32 @@ pub trait PumpkinBlock: Send + Sync {
BlockActionResult::Continue
}
#[allow(clippy::too_many_arguments)]
async fn on_place(
&self,
server: &Server,
world: &World,
block: &Block,
face: &BlockDirection,
block_pos: &BlockPos,
use_item_on: &SUseItemOn,
player_direction: &Direction,
other: bool,
) -> u16 {
server
.block_properties_manager
.on_place_state(
world,
block,
face,
block_pos,
use_item_on,
player_direction,
other,
)
.await
}
async fn placed(
&self,
_block: &Block,

View File

@@ -1,13 +1,18 @@
use crate::block::pumpkin_block::{BlockMetadata, PumpkinBlock};
use crate::entity::player::Player;
use crate::server::Server;
use crate::world::World;
use pumpkin_data::item::Item;
use pumpkin_inventory::OpenContainer;
use pumpkin_protocol::server::play::SUseItemOn;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::block::registry::Block;
use pumpkin_world::block::BlockDirection;
use std::collections::HashMap;
use std::sync::Arc;
use super::properties::Direction;
pub enum BlockActionResult {
/// Allow other actions to be executed
Continue,
@@ -57,6 +62,47 @@ impl BlockRegistry {
BlockActionResult::Continue
}
#[allow(clippy::too_many_arguments)]
pub async fn on_place(
&self,
server: &Server,
world: &World,
block: &Block,
face: &BlockDirection,
block_pos: &BlockPos,
use_item_on: &SUseItemOn,
player_direction: &Direction,
other: bool,
) -> u16 {
let pumpkin_block = self.get_pumpkin_block(block);
if let Some(pumpkin_block) = pumpkin_block {
return pumpkin_block
.on_place(
server,
world,
block,
face,
block_pos,
use_item_on,
player_direction,
other,
)
.await;
}
server
.block_properties_manager
.on_place_state(
world,
block,
face,
block_pos,
use_item_on,
player_direction,
other,
)
.await
}
pub async fn on_placed(
&self,
block: &Block,

View File

@@ -969,6 +969,15 @@ impl Player {
.block_registry
.on_use(block, self, location, server)
.await;
let block_state = world.get_block_state(&location).await?;
let new_state = server
.block_properties_manager
.on_interact(block, block_state, &ItemStack::new(0, Item::AIR))
.await;
world.set_block_state(&location, new_state).await;
self.client
.send_packet(&CAcknowledgeBlockChange::new(use_item_on.sequence))
.await;
}
return Ok(());
};
@@ -982,6 +991,15 @@ impl Player {
.block_registry
.use_with_item(block, self, location, &stack.item, server)
.await;
let block_state = world.get_block_state(&location).await?;
let new_state = server
.block_properties_manager
.on_interact(block, block_state, &ItemStack::new(0, Item::AIR))
.await;
world.set_block_state(&location, new_state).await;
self.client
.send_packet(&CAcknowledgeBlockChange::new(use_item_on.sequence.clone()))
.await;
match action_result {
BlockActionResult::Continue => {}
BlockActionResult::Consume => {
@@ -1206,6 +1224,7 @@ impl Player {
}
}
#[allow(clippy::too_many_lines)]
async fn run_is_block_place(
&self,
block: Block,
@@ -1219,6 +1238,7 @@ impl Player {
let clicked_block_pos = BlockPos(location.0);
let clicked_block_state = world.get_block_state(&clicked_block_pos).await?;
let clicked_block = world.get_block(&clicked_block_pos).await?;
// check block under the world
if location.0.y + face.to_offset().y < WORLD_LOWEST_Y.into() {
@@ -1255,34 +1275,31 @@ impl Player {
_ => {}
}
let (mut new_state, mut updateable) = server
let mut updateable = server
.block_properties_manager
.get_state_data(
world,
&block,
.can_update(
clicked_block,
clicked_block_state,
face,
&clicked_block_pos,
&use_item_on,
&self.get_player_direction(),
true,
false,
)
.await;
let final_block_pos = if clicked_block_state.replaceable || updateable {
clicked_block_pos
let (final_block_pos, final_face) = if updateable {
(clicked_block_pos, face)
} else {
let block_pos = BlockPos(location.0 + face.to_offset());
let previous_block = world.get_block(&block_pos).await?;
let previous_block_state = world.get_block_state(&block_pos).await?;
(new_state, updateable) = server
updateable = server
.block_properties_manager
.get_state_data(
world,
&block,
.can_update(
previous_block,
previous_block_state,
&face.opposite(),
&block_pos,
&use_item_on,
&self.get_player_direction(),
false,
true,
)
.await;
@@ -1290,9 +1307,23 @@ impl Player {
return Ok(true);
}
block_pos
(block_pos, &face.opposite())
};
let new_state = server
.block_registry
.on_place(
server,
world,
&block,
final_face,
&final_block_pos,
&use_item_on,
&self.get_player_direction(),
!(clicked_block_state.replaceable || updateable),
)
.await;
// To this point we must have the new block state
let shapes = get_block_collision_shapes(new_state).unwrap_or_default();
let mut intersects = false;