Skip to content

Commit

Permalink
Adds support for Block properties (#485)
Browse files Browse the repository at this point in the history
* Something

* Double slabs working

* Revert changes to blocks.json

* Fixed merge and clippy issues

* Fixed Bad merge for play.rs

* fix slabs and adds stairs

* Fix issue with fmt

---------

Co-authored-by: Bafran <[email protected]>
Co-authored-by: Alexander Medvedev <[email protected]>
  • Loading branch information
3 people authored Jan 28, 2025
1 parent 1362547 commit 6c4eb88
Show file tree
Hide file tree
Showing 11 changed files with 713 additions and 16 deletions.
2 changes: 2 additions & 0 deletions pumpkin-world/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ serde.workspace = true
serde_json.workspace = true
log.workspace = true

num-derive = "0.4.2"

dashmap = "6.1.0"

num-traits = "0.2"
Expand Down
5 changes: 2 additions & 3 deletions pumpkin-world/src/block/block_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,10 @@ pub struct Block {
pub default_state_id: u16,
pub states: Vec<State>,
}
#[expect(dead_code)]
#[derive(Deserialize, Clone, Debug)]
pub struct Property {
name: String,
values: Vec<String>,
pub name: String,
pub values: Vec<String>,
}
#[derive(Deserialize, Clone, Debug)]
pub struct State {
Expand Down
2 changes: 2 additions & 0 deletions pumpkin-world/src/block/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
pub mod block_registry;
pub mod block_state;

use num_derive::FromPrimitive;
use pumpkin_util::math::vector3::Vector3;

pub use block_state::BlockState;

#[derive(FromPrimitive, PartialEq, Clone, Copy)]
pub enum BlockFace {
Bottom = 0,
Top,
Expand Down
8 changes: 8 additions & 0 deletions pumpkin-world/src/entity/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
pub mod entity_registry;

#[derive(Debug, PartialEq, Clone, Copy)]
pub enum FacingDirection {
North,
South,
East,
West,
}
135 changes: 135 additions & 0 deletions pumpkin/src/block/block_properties_manager.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
use std::{collections::HashMap, sync::Arc};

use async_trait::async_trait;
use pumpkin_protocol::server::play::SUseItemOn;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::{
block::{
block_registry::{Block, BLOCKS},
BlockFace,
},
entity::FacingDirection,
};

use crate::world::World;

use super::properties::{slab::SlabBehavior, stair::StairBehavior};

#[async_trait]
pub trait BlockBehavior: Send + Sync {
async fn map_state_id(
&self,
world: &World,
block: &Block,
face: &BlockFace,
block_pos: &BlockPos,
use_item_on: &SUseItemOn,
player_direction: &FacingDirection,
) -> u16;
async fn is_updateable(
&self,
world: &World,
block: &Block,
face: &BlockFace,
block_pos: &BlockPos,
) -> bool;
}

#[derive(Clone, Debug)]
pub enum BlockProperty {
Waterlogged(bool),
Facing(Direction),
SlabType(SlabPosition),
StairShape(StairShape),
Half(BlockHalf), // Add other properties as needed
}

#[derive(Clone, Debug)]
pub enum BlockHalf {
Top,
Bottom,
}

#[derive(Clone, Debug)]
pub enum SlabPosition {
Top,
Bottom,
Double,
}

#[derive(Clone, Debug)]
pub enum StairShape {
Straight,
InnerLeft,
InnerRight,
OuterLeft,
OuterRight,
}

#[derive(Clone, Debug)]
pub enum Direction {
North,
South,
East,
West,
}

#[must_use]
pub fn get_property_key(property_name: &str) -> Option<BlockProperty> {
match property_name {
"waterlogged" => Some(BlockProperty::Waterlogged(false)),
"facing" => Some(BlockProperty::Facing(Direction::North)),
"type" => Some(BlockProperty::SlabType(SlabPosition::Top)),
"shape" => Some(BlockProperty::StairShape(StairShape::Straight)),
"half" => Some(BlockProperty::Half(BlockHalf::Bottom)),
_ => None,
}
}

#[derive(Default)]
pub struct BlockPropertiesManager {
properties_registry: HashMap<u16, Arc<dyn BlockBehavior>>,
}

impl BlockPropertiesManager {
pub fn build_properties_registry(&mut self) {
for block in &BLOCKS.blocks {
let behaviour: Arc<dyn BlockBehavior> = match block.name.as_str() {
name if name.ends_with("_slab") => SlabBehavior::get_or_init(&block.properties),
name if name.ends_with("_stairs") => StairBehavior::get_or_init(&block.properties),
_ => continue,
};
self.properties_registry.insert(block.id, behaviour);
}
}

pub async fn get_state_id(
&self,
world: &World,
block: &Block,
face: &BlockFace,
block_pos: &BlockPos,
use_item_on: &SUseItemOn,
player_direction: &FacingDirection,
) -> u16 {
if let Some(behaviour) = self.properties_registry.get(&block.id) {
return behaviour
.map_state_id(world, block, face, block_pos, use_item_on, player_direction)
.await;
}
block.default_state_id
}

pub async fn is_updateable(
&self,
world: &World,
block: &Block,
face: &BlockFace,
block_pos: &BlockPos,
) -> bool {
if let Some(behaviour) = self.properties_registry.get(&block.id) {
return behaviour.is_updateable(world, block, face, block_pos).await;
}
false
}
}
12 changes: 12 additions & 0 deletions pumpkin/src/block/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use block_properties_manager::BlockPropertiesManager;
use blocks::chest::ChestBlock;
use blocks::furnace::FurnaceBlock;

Expand All @@ -7,7 +8,9 @@ use crate::block::blocks::jukebox::JukeboxBlock;
use std::sync::Arc;

pub mod block_manager;
pub mod block_properties_manager;
mod blocks;
mod properties;
pub mod pumpkin_block;

#[must_use]
Expand All @@ -21,3 +24,12 @@ pub fn default_block_manager() -> Arc<BlockManager> {

Arc::new(manager)
}

#[must_use]
pub fn default_block_properties_manager() -> Arc<BlockPropertiesManager> {
let mut manager = BlockPropertiesManager::default();

manager.build_properties_registry();

Arc::new(manager)
}
2 changes: 2 additions & 0 deletions pumpkin/src/block/properties/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub(crate) mod slab;
pub(crate) mod stair;
167 changes: 167 additions & 0 deletions pumpkin/src/block/properties/slab.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
use std::{
collections::HashMap,
sync::{Arc, OnceLock},
};

use pumpkin_protocol::server::play::SUseItemOn;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::block::{block_registry::Property, BlockFace};
use pumpkin_world::{block::block_registry::Block, entity::FacingDirection};

use crate::{
block::block_properties_manager::{get_property_key, BlockBehavior, BlockProperty},
world::World,
};

pub static SLAB_BEHAVIOR: OnceLock<Arc<SlabBehavior>> = OnceLock::new();

// Example of a behavior with shared static data
pub struct SlabBehavior {
// Shared static data for all slabs
state_mappings: HashMap<Vec<String>, u16>,
property_mappings: HashMap<u16, Vec<String>>,
}

impl SlabBehavior {
pub fn get_or_init(properties: &[Property]) -> Arc<Self> {
SLAB_BEHAVIOR
.get_or_init(|| Arc::new(Self::new(properties)))
.clone()
}

pub fn get() -> Arc<Self> {
SLAB_BEHAVIOR.get().expect("Slab Uninitialized").clone()
}

pub fn new(properties: &[Property]) -> Self {
let total_combinations: usize = properties.iter().map(|p| p.values.len()).product();

let mut forward_map = HashMap::with_capacity(total_combinations);
let mut reverse_map = HashMap::with_capacity(total_combinations);

for i in 0..total_combinations {
let mut current = i;
let mut combination = Vec::with_capacity(properties.len());

for property in properties.iter().rev() {
let property_size = property.values.len();
combination.push(current % property_size);
current /= property_size;
}

combination.reverse();

let key: Vec<String> = combination
.iter()
.enumerate()
.map(|(prop_idx, &state_idx)| {
format!(
"{}{}",
properties[prop_idx].name, properties[prop_idx].values[state_idx]
)
})
.collect();

forward_map.insert(key.clone(), i as u16);
reverse_map.insert(i as u16, key);
}

Self {
state_mappings: forward_map,
property_mappings: reverse_map,
}
}

pub fn evalute_property_type(
block: &Block,
clicked_block: &Block,
face: BlockFace,
use_item_on: &SUseItemOn,
) -> String {
if block.id == clicked_block.id && face == BlockFace::Top {
return format!("{}{}", "type", "double");
}

if face == BlockFace::Top {
return format!("{}{}", "type", "bottom");
}

if face == BlockFace::North
|| face == BlockFace::South
|| face == BlockFace::West
|| face == BlockFace::East
{
let y_pos = use_item_on.cursor_pos.y;
if y_pos > 0.5 {
return format!("{}{}", "type", "top");
}

return format!("{}{}", "type", "bottom");
}

format!("{}{}", "type", "bottom")
}

pub fn evalute_property_waterlogged(block: &Block) -> String {
if block.name == "water" {
return format!("{}{}", "waterlogged", "true");
}
format!("{}{}", "waterlogged", "false")
}
}

#[async_trait::async_trait]
impl BlockBehavior for SlabBehavior {
async fn map_state_id(
&self,
world: &World,
block: &Block,
face: &BlockFace,
block_pos: &BlockPos,
use_item_on: &SUseItemOn,
_player_direction: &FacingDirection,
) -> u16 {
let clicked_block = world.get_block(block_pos).await.unwrap();
let mut hmap_key: Vec<String> = Vec::with_capacity(block.properties.len());
let slab_behaviour = Self::get();

for property in &block.properties {
let state = match get_property_key(property.name.as_str()).expect("Property not found")
{
BlockProperty::SlabType(_) => {
Self::evalute_property_type(block, clicked_block, *face, use_item_on)
}
BlockProperty::Waterlogged(false) => Self::evalute_property_waterlogged(block),
_ => panic!("Property not found"),
};
hmap_key.push(state.to_string());
}

// Base state id plus offset
block.states[0].id + slab_behaviour.state_mappings[&hmap_key]
}

async fn is_updateable(
&self,
world: &World,
block: &Block,
_face: &BlockFace,
block_pos: &BlockPos,
) -> bool {
let clicked_block = world.get_block(block_pos).await.unwrap();
if block.id != clicked_block.id {
return false; // Ensure the block being interacted with matches the target block.
}

let clicked_block_state_id = world.get_block_state_id(block_pos).await.unwrap();

let key = clicked_block_state_id - clicked_block.states[0].id;
if let Some(properties) = Self::get().property_mappings.get(&key) {
log::debug!("Properties: {:?}", properties);
if properties.contains(&"typebottom".to_string()) {
return true;
}
}
false
}
}
Loading

0 comments on commit 6c4eb88

Please sign in to comment.