mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
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:
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
60
pumpkin/src/block/blocks/lever.rs
Normal file
60
pumpkin/src/block/blocks/lever.rs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
28
pumpkin/src/block/properties/age.rs
Normal file
28
pumpkin/src/block/properties/age.rs
Normal 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 {}
|
||||
44
pumpkin/src/block/properties/attachment.rs
Normal file
44
pumpkin/src/block/properties/attachment.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
pumpkin/src/block/properties/axis.rs
Normal file
36
pumpkin/src/block/properties/axis.rs
Normal 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
210
pumpkin/src/block/properties/cardinal.rs
Normal file
210
pumpkin/src/block/properties/cardinal.rs
Normal 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
|
||||
}
|
||||
}
|
||||
37
pumpkin/src/block/properties/face.rs
Normal file
37
pumpkin/src/block/properties/face.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
46
pumpkin/src/block/properties/facing.rs
Normal file
46
pumpkin/src/block/properties/facing.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
45
pumpkin/src/block/properties/half.rs
Normal file
45
pumpkin/src/block/properties/half.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
79
pumpkin/src/block/properties/layers.rs
Normal file
79
pumpkin/src/block/properties/layers.rs
Normal 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
|
||||
}
|
||||
}
|
||||
251
pumpkin/src/block/properties/mod.rs
Normal file
251
pumpkin/src/block/properties/mod.rs
Normal 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
|
||||
}
|
||||
}
|
||||
9
pumpkin/src/block/properties/open.rs
Normal file
9
pumpkin/src/block/properties/open.rs
Normal 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 {}
|
||||
20
pumpkin/src/block/properties/powered.rs
Normal file
20
pumpkin/src/block/properties/powered.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
33
pumpkin/src/block/properties/signal_fire.rs
Normal file
33
pumpkin/src/block/properties/signal_fire.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
78
pumpkin/src/block/properties/slab_type.rs
Normal file
78
pumpkin/src/block/properties/slab_type.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
182
pumpkin/src/block/properties/stair_shape.rs
Normal file
182
pumpkin/src/block/properties/stair_shape.rs
Normal 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,
|
||||
)),
|
||||
),
|
||||
}
|
||||
}
|
||||
31
pumpkin/src/block/properties/waterlog.rs
Normal file
31
pumpkin/src/block/properties/waterlog.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user