diff --git a/Cargo.toml b/Cargo.toml index bfcda5449..f00a7c4e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -179,7 +179,7 @@ tracing-subscriber = { version = "0.3.23", default-features = false, features = ] } slotmap = { version = "1.1", default-features = false, features = ["std"] } temp-dir = { version = "0.2.0", default-features = false } -thread_local = { version = "1.1.9", default-features = false } +thread_local = { version = "1.1.10", default-features = false } time = { version = "0.3", default-features = false, features = ["std"] } arc-swap = { version = "1.9", default-features = false } tokio-util = { version = "0.7.18", default-features = false } diff --git a/pumpkin/src/block/blocks/banners.rs b/pumpkin/src/block/blocks/banners.rs index 71fcce2dc..603686695 100644 --- a/pumpkin/src/block/blocks/banners.rs +++ b/pumpkin/src/block/blocks/banners.rs @@ -1,5 +1,6 @@ use crate::block::{ BlockBehaviour, BlockFuture, GetStateForNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, + PlacedArgs, }; use crate::entity::EntityBase; use pumpkin_data::BlockStateId; @@ -9,10 +10,20 @@ use pumpkin_util::math::position::BlockPos; use pumpkin_world::tick::TickPriority; use pumpkin_world::world::{BlockAccessor, BlockFlags}; +use crate::block::entities::banner::BannerBlockEntity; +use std::sync::Arc; + #[pumpkin_block_from_tag("minecraft:banners")] pub struct BannerBlock; impl BlockBehaviour for BannerBlock { + fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> { + Box::pin(async move { + let entity = BannerBlockEntity::new(*args.position); + args.world.add_block_entity(Arc::new(entity)); + }) + } + fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { Box::pin(async move { let mut props = WhiteBannerLikeProperties::default(args.block); diff --git a/pumpkin/src/block/blocks/campfire.rs b/pumpkin/src/block/blocks/campfire.rs index 85c820d9d..2e96588a2 100644 --- a/pumpkin/src/block/blocks/campfire.rs +++ b/pumpkin/src/block/blocks/campfire.rs @@ -9,18 +9,27 @@ use pumpkin_data::{ use pumpkin_macros::pumpkin_block_from_tag; use pumpkin_world::tick::TickPriority; +use crate::block::entities::campfire::CampfireBlockEntity; use crate::{ block::{ BlockBehaviour, BlockFuture, BlockIsReplacing, GetStateForNeighborUpdateArgs, - OnEntityCollisionArgs, OnPlaceArgs, + OnEntityCollisionArgs, OnPlaceArgs, PlacedArgs, }, entity::EntityBase, }; +use std::sync::Arc; #[pumpkin_block_from_tag("minecraft:campfires")] pub struct CampfireBlock; impl BlockBehaviour for CampfireBlock { + fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> { + Box::pin(async move { + let entity = CampfireBlockEntity::new(*args.position); + args.world.add_block_entity(Arc::new(entity)); + }) + } + // TODO: cooking food on campfire (CampfireBlockEntity) fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> { Box::pin(async move { diff --git a/pumpkin/src/block/blocks/conduit.rs b/pumpkin/src/block/blocks/conduit.rs index e18582b8c..6df0299ce 100644 --- a/pumpkin/src/block/blocks/conduit.rs +++ b/pumpkin/src/block/blocks/conduit.rs @@ -1,7 +1,9 @@ -use crate::block::{BlockBehaviour, BlockFuture, OnPlaceArgs}; +use crate::block::entities::conduit::ConduitBlockEntity; +use crate::block::{BlockBehaviour, BlockFuture, OnPlaceArgs, PlacedArgs}; use pumpkin_data::BlockStateId; use pumpkin_data::block_properties::BlockProperties; use pumpkin_macros::pumpkin_block; +use std::sync::Arc; #[pumpkin_block("minecraft:conduit")] pub struct ConduitBlock; @@ -16,4 +18,11 @@ impl BlockBehaviour for ConduitBlock { props.to_state_id(args.block) }) } + + fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> { + Box::pin(async move { + let entity = ConduitBlockEntity::new(*args.position); + args.world.add_block_entity(Arc::new(entity)); + }) + } } diff --git a/pumpkin/src/block/blocks/enchanting_table.rs b/pumpkin/src/block/blocks/enchanting_table.rs index 810d7f6fc..1bb809365 100644 --- a/pumpkin/src/block/blocks/enchanting_table.rs +++ b/pumpkin/src/block/blocks/enchanting_table.rs @@ -1,7 +1,8 @@ use std::sync::Arc; +use crate::block::entities::enchanting_table::EnchantingTableBlockEntity; use crate::block::registry::BlockActionResult; -use crate::block::{BlockBehaviour, BlockFuture, NormalUseArgs}; +use crate::block::{BlockBehaviour, BlockFuture, NormalUseArgs, PlacedArgs}; use pumpkin_data::{Block, BlockStateId, translation}; use pumpkin_inventory::enchanting::enchanting_screen_handler::EnchantingTableScreenHandler; use pumpkin_inventory::player::player_inventory::PlayerInventory; @@ -18,6 +19,13 @@ use tokio::sync::Mutex; pub struct EnchantingTableBlock; impl BlockBehaviour for EnchantingTableBlock { + fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> { + Box::pin(async move { + let entity = EnchantingTableBlockEntity::new(*args.position); + args.world.add_block_entity(Arc::new(entity)); + }) + } + fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> { Box::pin(async move { let mut bookshelf_count = 0; diff --git a/pumpkin/src/block/blocks/redstone/dispenser.rs b/pumpkin/src/block/blocks/redstone/dispenser.rs index 1c4c489db..47354c322 100644 --- a/pumpkin/src/block/blocks/redstone/dispenser.rs +++ b/pumpkin/src/block/blocks/redstone/dispenser.rs @@ -1,8 +1,10 @@ -use crate::block::{BlockBehaviour, BlockFuture, OnPlaceArgs}; +use crate::block::entities::dispenser::DispenserBlockEntity; +use crate::block::{BlockBehaviour, BlockFuture, OnPlaceArgs, PlacedArgs}; use crate::entity::EntityBase; use pumpkin_data::BlockStateId; use pumpkin_data::block_properties::{BlockProperties, DispenserLikeProperties}; use pumpkin_macros::pumpkin_block; +use std::sync::Arc; #[pumpkin_block("minecraft:dispenser")] pub struct DispenserBlock; @@ -15,4 +17,12 @@ impl BlockBehaviour for DispenserBlock { props.to_state_id(args.block) }) } + + fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> { + Box::pin(async move { + let dispenser_block_entity = DispenserBlockEntity::new(*args.position); + args.world + .add_block_entity(Arc::new(dispenser_block_entity)); + }) + } } diff --git a/pumpkin/src/block/blocks/shelf.rs b/pumpkin/src/block/blocks/shelf.rs index 93eb68a2c..b42eceba2 100644 --- a/pumpkin/src/block/blocks/shelf.rs +++ b/pumpkin/src/block/blocks/shelf.rs @@ -2,8 +2,10 @@ use pumpkin_data::BlockStateId; use pumpkin_data::block_properties::{AcaciaShelfLikeProperties, BlockProperties}; use pumpkin_macros::pumpkin_block_from_tag; -use crate::block::{BlockBehaviour, BlockFuture, OnPlaceArgs}; +use crate::block::entities::shelf::ShelfBlockEntity; +use crate::block::{BlockBehaviour, BlockFuture, OnPlaceArgs, PlacedArgs}; use crate::entity::EntityBase; +use std::sync::Arc; #[pumpkin_block_from_tag("minecraft:wooden_shelves")] pub struct ShelfBlock; @@ -19,4 +21,11 @@ impl BlockBehaviour for ShelfBlock { properties.to_state_id(args.block) }) } + + fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> { + Box::pin(async move { + let entity = ShelfBlockEntity::new(*args.position); + args.world.add_block_entity(Arc::new(entity)); + }) + } } diff --git a/pumpkin/src/block/blocks/signs.rs b/pumpkin/src/block/blocks/signs.rs index e991d539e..39ba4d6b6 100644 --- a/pumpkin/src/block/blocks/signs.rs +++ b/pumpkin/src/block/blocks/signs.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use std::sync::atomic::Ordering; +use crate::block::entities::hanging_sign::HangingSignBlockEntity; use crate::block::entities::sign::SignBlockEntity; use pumpkin_data::Block; use pumpkin_data::BlockDirection; @@ -308,8 +309,13 @@ impl BlockBehaviour for SignBlock { fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> { Box::pin(async move { - args.world - .add_block_entity(Arc::new(SignBlockEntity::empty(*args.position))); + if args.block.name.contains("hanging") { + args.world + .add_block_entity(Arc::new(HangingSignBlockEntity::empty(*args.position))); + } else { + args.world + .add_block_entity(Arc::new(SignBlockEntity::empty(*args.position))); + } }) } diff --git a/pumpkin/src/block/blocks/skull_block.rs b/pumpkin/src/block/blocks/skull_block.rs index 03c7b728f..9f03ed976 100644 --- a/pumpkin/src/block/blocks/skull_block.rs +++ b/pumpkin/src/block/blocks/skull_block.rs @@ -24,7 +24,18 @@ impl BlockMetadata for SkullBlock { } } +use crate::block::PlacedArgs; +use crate::block::entities::skull::SkullBlockEntity; +use std::sync::Arc; + impl BlockBehaviour for SkullBlock { + fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> { + Box::pin(async move { + let entity = SkullBlockEntity::new(*args.position); + args.world.add_block_entity(Arc::new(entity)); + }) + } + fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { Box::pin(async move { let mut props = SkeletonSkullLikeProperties::default(args.block); diff --git a/pumpkin/src/block/blocks/wither_skull.rs b/pumpkin/src/block/blocks/wither_skull.rs index ba92d8c7d..b078f465d 100644 --- a/pumpkin/src/block/blocks/wither_skull.rs +++ b/pumpkin/src/block/blocks/wither_skull.rs @@ -19,6 +19,9 @@ impl BlockBehaviour for WitherSkeletonSkullBlock { fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> { Box::pin(async move { + let entity = crate::block::entities::skull::SkullBlockEntity::new(*args.position); + args.world.add_block_entity(std::sync::Arc::new(entity)); + let world = args.world; let pos = args.position; diff --git a/pumpkin/src/block/entities/banner.rs b/pumpkin/src/block/entities/banner.rs new file mode 100644 index 000000000..e47f1c20a --- /dev/null +++ b/pumpkin/src/block/entities/banner.rs @@ -0,0 +1,65 @@ +use super::BlockEntity; +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_nbt::tag::NbtTag; +use pumpkin_util::math::position::BlockPos; +use std::pin::Pin; +use tokio::sync::Mutex; + +pub struct BannerBlockEntity { + pub position: BlockPos, + pub custom_name: Mutex>, + pub patterns: Mutex>>, +} + +impl BlockEntity for BannerBlockEntity { + fn resource_location(&self) -> &'static str { + Self::ID + } + + fn get_position(&self) -> BlockPos { + self.position + } + + fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self + where + Self: Sized, + { + let custom_name = nbt.get_string("CustomName").map(ToString::to_string); + let patterns = nbt.get_list("patterns").map(<[_]>::to_vec); + Self { + position, + custom_name: Mutex::new(custom_name), + patterns: Mutex::new(patterns), + } + } + + fn write_nbt<'a>( + &'a self, + nbt: &'a mut NbtCompound, + ) -> Pin + Send + 'a>> { + Box::pin(async move { + if let Some(name) = self.custom_name.lock().await.as_ref() { + nbt.put_string("CustomName", name.clone()); + } + if let Some(pats) = self.patterns.lock().await.as_ref() { + nbt.put_list("patterns", pats.clone()); + } + }) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl BannerBlockEntity { + pub const ID: &'static str = "minecraft:banner"; + #[must_use] + pub const fn new(position: BlockPos) -> Self { + Self { + position, + custom_name: Mutex::const_new(None), + patterns: Mutex::const_new(None), + } + } +} diff --git a/pumpkin/src/block/entities/beehive.rs b/pumpkin/src/block/entities/beehive.rs new file mode 100644 index 000000000..c915b53db --- /dev/null +++ b/pumpkin/src/block/entities/beehive.rs @@ -0,0 +1,75 @@ +use super::BlockEntity; +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_nbt::tag::NbtTag; +use pumpkin_util::math::position::BlockPos; +use std::pin::Pin; +use tokio::sync::Mutex; + +pub struct BeehiveBlockEntity { + pub position: BlockPos, + pub bees: Mutex>>, + pub flower_pos: Mutex>, +} + +impl BlockEntity for BeehiveBlockEntity { + fn resource_location(&self) -> &'static str { + Self::ID + } + + fn get_position(&self) -> BlockPos { + self.position + } + + fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self + where + Self: Sized, + { + let bees = nbt.get_list("Bees").map(<[_]>::to_vec); + let flower_pos = nbt.get_compound("FlowerPos").map(|c| { + BlockPos::new( + c.get_int("X").unwrap_or(0), + c.get_int("Y").unwrap_or(0), + c.get_int("Z").unwrap_or(0), + ) + }); + Self { + position, + bees: Mutex::new(bees), + flower_pos: Mutex::new(flower_pos), + } + } + + fn write_nbt<'a>( + &'a self, + nbt: &'a mut NbtCompound, + ) -> Pin + Send + 'a>> { + Box::pin(async move { + if let Some(b) = self.bees.lock().await.as_ref() { + nbt.put_list("Bees", b.clone()); + } + if let Some(fp) = self.flower_pos.lock().await.as_ref() { + let mut fp_nbt = NbtCompound::new(); + fp_nbt.put_int("X", fp.0.x); + fp_nbt.put_int("Y", fp.0.y); + fp_nbt.put_int("Z", fp.0.z); + nbt.put_compound("FlowerPos", fp_nbt); + } + }) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl BeehiveBlockEntity { + pub const ID: &'static str = "minecraft:beehive"; + #[must_use] + pub const fn new(position: BlockPos) -> Self { + Self { + position, + bees: Mutex::const_new(None), + flower_pos: Mutex::const_new(None), + } + } +} diff --git a/pumpkin/src/block/entities/brushable_block.rs b/pumpkin/src/block/entities/brushable_block.rs new file mode 100644 index 000000000..2aef99a2c --- /dev/null +++ b/pumpkin/src/block/entities/brushable_block.rs @@ -0,0 +1,72 @@ +use super::BlockEntity; +use pumpkin_data::item_stack::ItemStack; +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_util::math::position::BlockPos; +use std::pin::Pin; +use tokio::sync::Mutex; + +pub struct BrushableBlockBlockEntity { + pub position: BlockPos, + pub item: Mutex>, + pub hits: Mutex, + pub direction: Mutex, +} + +impl BlockEntity for BrushableBlockBlockEntity { + fn resource_location(&self) -> &'static str { + Self::ID + } + + fn get_position(&self) -> BlockPos { + self.position + } + + fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self + where + Self: Sized, + { + let item = nbt + .get_compound("item") + .and_then(ItemStack::read_item_stack); + let hits = nbt.get_int("hits").unwrap_or(0); + let direction = nbt.get_byte("direction").unwrap_or(0) as u8; + Self { + position, + item: Mutex::new(item), + hits: Mutex::new(hits), + direction: Mutex::new(direction), + } + } + + fn write_nbt<'a>( + &'a self, + nbt: &'a mut NbtCompound, + ) -> Pin + Send + 'a>> { + Box::pin(async move { + if let Some(it) = self.item.lock().await.as_ref() { + let mut it_nbt = NbtCompound::new(); + it.write_item_stack(&mut it_nbt); + nbt.put_compound("item", it_nbt); + } + nbt.put_int("hits", *self.hits.lock().await); + nbt.put_byte("direction", *self.direction.lock().await as i8); + }) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl BrushableBlockBlockEntity { + pub const ID: &'static str = "minecraft:brushable_block"; + #[must_use] + pub const fn new(position: BlockPos) -> Self { + Self { + position, + item: Mutex::const_new(None), + hits: Mutex::const_new(0), + direction: Mutex::const_new(0), + } + } +} diff --git a/pumpkin/src/block/entities/calibrated_sculk_sensor.rs b/pumpkin/src/block/entities/calibrated_sculk_sensor.rs new file mode 100644 index 000000000..8d7a10987 --- /dev/null +++ b/pumpkin/src/block/entities/calibrated_sculk_sensor.rs @@ -0,0 +1,58 @@ +use super::BlockEntity; +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_util::math::position::BlockPos; +use std::pin::Pin; +use tokio::sync::Mutex; + +pub struct CalibratedSculkSensorBlockEntity { + pub position: BlockPos, + pub last_vibration_frequency: Mutex, +} + +impl BlockEntity for CalibratedSculkSensorBlockEntity { + fn resource_location(&self) -> &'static str { + Self::ID + } + + fn get_position(&self) -> BlockPos { + self.position + } + + fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self + where + Self: Sized, + { + let last_vibration_frequency = nbt.get_int("last_vibration_frequency").unwrap_or(0); + Self { + position, + last_vibration_frequency: Mutex::new(last_vibration_frequency), + } + } + + fn write_nbt<'a>( + &'a self, + nbt: &'a mut NbtCompound, + ) -> Pin + Send + 'a>> { + Box::pin(async move { + nbt.put_int( + "last_vibration_frequency", + *self.last_vibration_frequency.lock().await, + ); + }) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl CalibratedSculkSensorBlockEntity { + pub const ID: &'static str = "minecraft:calibrated_sculk_sensor"; + #[must_use] + pub fn new(position: BlockPos) -> Self { + Self { + position, + last_vibration_frequency: Mutex::new(0), + } + } +} diff --git a/pumpkin/src/block/entities/campfire.rs b/pumpkin/src/block/entities/campfire.rs new file mode 100644 index 000000000..54808cd7b --- /dev/null +++ b/pumpkin/src/block/entities/campfire.rs @@ -0,0 +1,111 @@ +use super::BlockEntity; +use pumpkin_data::item_stack::ItemStack; +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_nbt::tag::NbtTag; +use pumpkin_util::math::position::BlockPos; +use std::pin::Pin; +use std::sync::Arc; +use tokio::sync::Mutex; + +pub struct CampfireBlockEntity { + pub position: BlockPos, + pub items: [Arc>; 4], + pub cooking_times: [tokio::sync::Mutex; 4], + pub cooking_total_times: [tokio::sync::Mutex; 4], +} + +impl BlockEntity for CampfireBlockEntity { + fn resource_location(&self) -> &'static str { + Self::ID + } + + fn get_position(&self) -> BlockPos { + self.position + } + + fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self + where + Self: Sized, + { + let mut items = std::array::from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))); + if let Some(list) = nbt.get_list("Items") { + for tag in list { + if let Some(compound) = tag.extract_compound() { + let slot = compound.get_byte("Slot").unwrap_or(0) as usize; + if slot < 4 + && let Some(stack) = ItemStack::read_item_stack(compound) + { + items[slot] = Arc::new(Mutex::new(stack)); + } + } + } + } + let mut cooking_times = [Mutex::new(0), Mutex::new(0), Mutex::new(0), Mutex::new(0)]; + if let Some(arr) = nbt.get_int_array("CookingTimes") { + for (i, &val) in arr.iter().enumerate().take(4) { + cooking_times[i] = Mutex::new(val); + } + } + let mut cooking_total_times = [Mutex::new(0), Mutex::new(0), Mutex::new(0), Mutex::new(0)]; + if let Some(arr) = nbt.get_int_array("CookingTotalTimes") { + for (i, &val) in arr.iter().enumerate().take(4) { + cooking_total_times[i] = Mutex::new(val); + } + } + + Self { + position, + items, + cooking_times, + cooking_total_times, + } + } + + fn write_nbt<'a>( + &'a self, + nbt: &'a mut NbtCompound, + ) -> Pin + Send + 'a>> { + Box::pin(async move { + let mut list = Vec::new(); + for (i, item_mutex) in self.items.iter().enumerate() { + let stack = item_mutex.lock().await; + if !stack.is_empty() { + let mut item_nbt = NbtCompound::new(); + item_nbt.put_byte("Slot", i as i8); + stack.write_item_stack(&mut item_nbt); + list.push(NbtTag::Compound(item_nbt)); + } + } + nbt.put_list("Items", list); + + let mut times = Vec::new(); + for m in &self.cooking_times { + times.push(*m.lock().await); + } + nbt.put("CookingTimes", NbtTag::IntArray(times)); + + let mut total_times = Vec::new(); + for m in &self.cooking_total_times { + total_times.push(*m.lock().await); + } + nbt.put("CookingTotalTimes", NbtTag::IntArray(total_times)); + }) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl CampfireBlockEntity { + pub const ID: &'static str = "minecraft:campfire"; + #[must_use] + pub fn new(position: BlockPos) -> Self { + Self { + position, + items: std::array::from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + cooking_times: [Mutex::new(0), Mutex::new(0), Mutex::new(0), Mutex::new(0)], + cooking_total_times: [Mutex::new(0), Mutex::new(0), Mutex::new(0), Mutex::new(0)], + } + } +} diff --git a/pumpkin/src/block/entities/conduit.rs b/pumpkin/src/block/entities/conduit.rs new file mode 100644 index 000000000..f3bdc3524 --- /dev/null +++ b/pumpkin/src/block/entities/conduit.rs @@ -0,0 +1,63 @@ +use super::BlockEntity; +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_nbt::tag::NbtTag; +use pumpkin_util::math::position::BlockPos; +use std::pin::Pin; +use tokio::sync::Mutex; + +pub struct ConduitBlockEntity { + pub position: BlockPos, + pub active: Mutex, + pub target: Mutex>, +} + +impl BlockEntity for ConduitBlockEntity { + fn resource_location(&self) -> &'static str { + Self::ID + } + + fn get_position(&self) -> BlockPos { + self.position + } + + fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self + where + Self: Sized, + { + let active = nbt.get_bool("Active").unwrap_or(false); + let target = nbt.get("Target").cloned(); + Self { + position, + active: Mutex::new(active), + target: Mutex::new(target), + } + } + + fn write_nbt<'a>( + &'a self, + nbt: &'a mut NbtCompound, + ) -> Pin + Send + 'a>> { + Box::pin(async move { + nbt.put_bool("Active", *self.active.lock().await); + if let Some(tgt) = self.target.lock().await.as_ref() { + nbt.put("Target", tgt.clone()); + } + }) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl ConduitBlockEntity { + pub const ID: &'static str = "minecraft:conduit"; + #[must_use] + pub fn new(position: BlockPos) -> Self { + Self { + position, + active: Mutex::new(false), + target: Mutex::new(None), + } + } +} diff --git a/pumpkin/src/block/entities/copper_golem_statue.rs b/pumpkin/src/block/entities/copper_golem_statue.rs new file mode 100644 index 000000000..aaef67308 --- /dev/null +++ b/pumpkin/src/block/entities/copper_golem_statue.rs @@ -0,0 +1,46 @@ +use std::pin::Pin; + +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_util::math::position::BlockPos; + +use super::BlockEntity; + +pub struct CopperGolemStatueBlockEntity { + pub position: BlockPos, +} + +impl BlockEntity for CopperGolemStatueBlockEntity { + fn resource_location(&self) -> &'static str { + Self::ID + } + + fn get_position(&self) -> BlockPos { + self.position + } + + fn from_nbt(_nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self + where + Self: Sized, + { + Self { position } + } + + fn write_nbt<'a>( + &'a self, + _nbt: &'a mut NbtCompound, + ) -> Pin + Send + 'a>> { + Box::pin(async {}) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl CopperGolemStatueBlockEntity { + pub const ID: &'static str = "minecraft:copper_golem_statue"; + #[must_use] + pub const fn new(position: BlockPos) -> Self { + Self { position } + } +} diff --git a/pumpkin/src/block/entities/crafter.rs b/pumpkin/src/block/entities/crafter.rs new file mode 100644 index 000000000..f1757a528 --- /dev/null +++ b/pumpkin/src/block/entities/crafter.rs @@ -0,0 +1,160 @@ +use crate::block::entities::BlockEntity; +use pumpkin_data::item_stack::ItemStack; +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture, split_stack}; +use std::any::Any; +use std::array::from_fn; +use std::pin::Pin; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, AtomicI32, Ordering}; +use tokio::sync::Mutex; + +pub struct CrafterBlockEntity { + pub position: BlockPos, + pub items: [Arc>; Self::INVENTORY_SIZE], + pub crafting_ticks_remaining: AtomicI32, + pub triggered: AtomicBool, + pub dirty: AtomicBool, +} + +impl BlockEntity for CrafterBlockEntity { + fn write_nbt<'a>( + &'a self, + nbt: &'a mut NbtCompound, + ) -> Pin + Send + 'a>> { + Box::pin(async move { + self.write_inventory_nbt(nbt, true).await; + nbt.put_int( + "crafting_ticks_remaining", + self.crafting_ticks_remaining.load(Ordering::Relaxed), + ); + nbt.put_bool("triggered", self.triggered.load(Ordering::Relaxed)); + }) + } + + fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self + where + Self: Sized, + { + let crafter = Self { + position, + items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + crafting_ticks_remaining: AtomicI32::new( + nbt.get_int("crafting_ticks_remaining").unwrap_or(0), + ), + triggered: AtomicBool::new(nbt.get_bool("triggered").unwrap_or(false)), + dirty: AtomicBool::new(false), + }; + + crafter.read_data(nbt, &crafter.items); + + crafter + } + + fn resource_location(&self) -> &'static str { + Self::ID + } + + fn get_position(&self) -> BlockPos { + self.position + } + + fn get_inventory(self: Arc) -> Option> { + Some(self) + } + + fn is_dirty(&self) -> bool { + self.dirty.load(Ordering::Relaxed) + } + + fn clear_dirty(&self) { + self.dirty.store(false, Ordering::Relaxed); + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl CrafterBlockEntity { + pub const INVENTORY_SIZE: usize = 9; + pub const ID: &'static str = "minecraft:crafter"; + + #[must_use] + pub fn new(position: BlockPos) -> Self { + Self { + position, + items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + crafting_ticks_remaining: AtomicI32::new(0), + triggered: AtomicBool::new(false), + dirty: AtomicBool::new(false), + } + } +} + +impl Inventory for CrafterBlockEntity { + fn size(&self) -> usize { + self.items.len() + } + + fn is_empty(&self) -> InventoryFuture<'_, bool> { + Box::pin(async move { + for slot in &self.items { + if !slot.lock().await.is_empty() { + return false; + } + } + + true + }) + } + + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { + Box::pin(async move { self.items[slot].clone() }) + } + + fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let mut removed = ItemStack::EMPTY.clone(); + let mut guard = self.items[slot].lock().await; + std::mem::swap(&mut removed, &mut *guard); + self.mark_dirty(); + removed + }) + } + + fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let res = split_stack(&self.items, slot, amount).await; + self.mark_dirty(); + res + }) + } + + fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { + Box::pin(async move { + *self.items[slot].lock().await = stack; + self.mark_dirty(); + }) + } + + fn mark_dirty(&self) { + self.dirty.store(true, Ordering::Relaxed); + } + + fn as_any(&self) -> &dyn Any { + self + } +} + +impl Clearable for CrafterBlockEntity { + fn clear(&self) -> Pin + Send + '_>> { + Box::pin(async move { + for slot in &self.items { + *slot.lock().await = ItemStack::EMPTY.clone(); + } + self.mark_dirty(); + }) + } +} diff --git a/pumpkin/src/block/entities/creaking_heart.rs b/pumpkin/src/block/entities/creaking_heart.rs new file mode 100644 index 000000000..21cd2937d --- /dev/null +++ b/pumpkin/src/block/entities/creaking_heart.rs @@ -0,0 +1,46 @@ +use std::pin::Pin; + +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_util::math::position::BlockPos; + +use super::BlockEntity; + +pub struct CreakingHeartBlockEntity { + pub position: BlockPos, +} + +impl BlockEntity for CreakingHeartBlockEntity { + fn resource_location(&self) -> &'static str { + Self::ID + } + + fn get_position(&self) -> BlockPos { + self.position + } + + fn from_nbt(_nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self + where + Self: Sized, + { + Self { position } + } + + fn write_nbt<'a>( + &'a self, + _nbt: &'a mut NbtCompound, + ) -> Pin + Send + 'a>> { + Box::pin(async {}) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl CreakingHeartBlockEntity { + pub const ID: &'static str = "minecraft:creaking_heart"; + #[must_use] + pub const fn new(position: BlockPos) -> Self { + Self { position } + } +} diff --git a/pumpkin/src/block/entities/decorated_pot.rs b/pumpkin/src/block/entities/decorated_pot.rs new file mode 100644 index 000000000..6ba9be5f3 --- /dev/null +++ b/pumpkin/src/block/entities/decorated_pot.rs @@ -0,0 +1,70 @@ +use super::BlockEntity; +use pumpkin_data::item_stack::ItemStack; +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_nbt::tag::NbtTag; +use pumpkin_util::math::position::BlockPos; +use std::pin::Pin; +use tokio::sync::Mutex; + +pub struct DecoratedPotBlockEntity { + pub position: BlockPos, + pub sherds: Mutex>>, + pub item: Mutex>, +} + +impl BlockEntity for DecoratedPotBlockEntity { + fn resource_location(&self) -> &'static str { + Self::ID + } + + fn get_position(&self) -> BlockPos { + self.position + } + + fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self + where + Self: Sized, + { + let sherds = nbt.get_list("sherds").map(<[_]>::to_vec); + let item = nbt + .get_compound("item") + .and_then(ItemStack::read_item_stack); + Self { + position, + sherds: Mutex::new(sherds), + item: Mutex::new(item), + } + } + + fn write_nbt<'a>( + &'a self, + nbt: &'a mut NbtCompound, + ) -> Pin + Send + 'a>> { + Box::pin(async move { + if let Some(sh) = self.sherds.lock().await.as_ref() { + nbt.put_list("sherds", sh.clone()); + } + if let Some(it) = self.item.lock().await.as_ref() { + let mut it_nbt = NbtCompound::new(); + it.write_item_stack(&mut it_nbt); + nbt.put_compound("item", it_nbt); + } + }) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl DecoratedPotBlockEntity { + pub const ID: &'static str = "minecraft:decorated_pot"; + #[must_use] + pub const fn new(position: BlockPos) -> Self { + Self { + position, + sherds: Mutex::const_new(None), + item: Mutex::const_new(None), + } + } +} diff --git a/pumpkin/src/block/entities/dispenser.rs b/pumpkin/src/block/entities/dispenser.rs new file mode 100644 index 000000000..d2ff52264 --- /dev/null +++ b/pumpkin/src/block/entities/dispenser.rs @@ -0,0 +1,160 @@ +use crate::block::entities::BlockEntity; +use pumpkin_data::item_stack::ItemStack; +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture, split_stack}; +use rand::{RngExt, rng}; +use std::any::Any; +use std::array::from_fn; +use std::pin::Pin; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; +use tokio::sync::{Mutex, MutexGuard}; + +pub struct DispenserBlockEntity { + pub position: BlockPos, + pub items: [Arc>; Self::INVENTORY_SIZE], + pub dirty: AtomicBool, +} + +impl BlockEntity for DispenserBlockEntity { + fn write_nbt<'a>( + &'a self, + nbt: &'a mut NbtCompound, + ) -> Pin + Send + 'a>> { + self.write_inventory_nbt(nbt, true) + } + + fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self + where + Self: Sized, + { + let dispenser = Self { + position, + items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + dirty: AtomicBool::new(false), + }; + + dispenser.read_data(nbt, &dispenser.items); + + dispenser + } + + fn resource_location(&self) -> &'static str { + Self::ID + } + + fn get_position(&self) -> BlockPos { + self.position + } + + fn get_inventory(self: Arc) -> Option> { + Some(self) + } + + fn is_dirty(&self) -> bool { + self.dirty.load(Ordering::Relaxed) + } + + fn clear_dirty(&self) { + self.dirty.store(false, Ordering::Relaxed); + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl DispenserBlockEntity { + pub const INVENTORY_SIZE: usize = 9; + pub const ID: &'static str = "minecraft:dispenser"; + + #[must_use] + pub fn new(position: BlockPos) -> Self { + Self { + position, + items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + dirty: AtomicBool::new(false), + } + } + pub async fn get_random_slot(&self) -> Option> { + let mut ret = None; + let mut j = 1; + for i in &self.items { + let item = i.lock().await; + if !item.is_empty() { + if rng().random_range(0..j) == 0 { + ret = Some(item); + } + j += 1; + } + } + ret + } +} + +impl Inventory for DispenserBlockEntity { + fn size(&self) -> usize { + self.items.len() + } + + fn is_empty(&self) -> InventoryFuture<'_, bool> { + Box::pin(async move { + for slot in &self.items { + if !slot.lock().await.is_empty() { + return false; + } + } + + true + }) + } + + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { + Box::pin(async move { self.items[slot].clone() }) + } + + fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let mut removed = ItemStack::EMPTY.clone(); + let mut guard = self.items[slot].lock().await; + std::mem::swap(&mut removed, &mut *guard); + self.mark_dirty(); + removed + }) + } + + fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let res = split_stack(&self.items, slot, amount).await; + self.mark_dirty(); + res + }) + } + + fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { + Box::pin(async move { + *self.items[slot].lock().await = stack; + self.mark_dirty(); + }) + } + + fn mark_dirty(&self) { + self.dirty.store(true, Ordering::Relaxed); + } + + fn as_any(&self) -> &dyn Any { + self + } +} + +impl Clearable for DispenserBlockEntity { + fn clear(&self) -> Pin + Send + '_>> { + Box::pin(async move { + for slot in &self.items { + *slot.lock().await = ItemStack::EMPTY.clone(); + } + self.mark_dirty(); + }) + } +} diff --git a/pumpkin/src/block/entities/enchanting_table.rs b/pumpkin/src/block/entities/enchanting_table.rs new file mode 100644 index 000000000..a69cfdab3 --- /dev/null +++ b/pumpkin/src/block/entities/enchanting_table.rs @@ -0,0 +1,57 @@ +use super::BlockEntity; +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_util::math::position::BlockPos; +use std::pin::Pin; +use tokio::sync::Mutex; + +pub struct EnchantingTableBlockEntity { + pub position: BlockPos, + pub custom_name: Mutex>, +} + +impl BlockEntity for EnchantingTableBlockEntity { + fn resource_location(&self) -> &'static str { + Self::ID + } + + fn get_position(&self) -> BlockPos { + self.position + } + + fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self + where + Self: Sized, + { + let custom_name = nbt.get_string("CustomName").map(ToString::to_string); + Self { + position, + custom_name: Mutex::new(custom_name), + } + } + + fn write_nbt<'a>( + &'a self, + nbt: &'a mut NbtCompound, + ) -> Pin + Send + 'a>> { + Box::pin(async move { + if let Some(name) = self.custom_name.lock().await.as_ref() { + nbt.put_string("CustomName", name.clone()); + } + }) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl EnchantingTableBlockEntity { + pub const ID: &'static str = "minecraft:enchanting_table"; + #[must_use] + pub const fn new(position: BlockPos) -> Self { + Self { + position, + custom_name: Mutex::const_new(None), + } + } +} diff --git a/pumpkin/src/block/entities/end_gateway.rs b/pumpkin/src/block/entities/end_gateway.rs new file mode 100644 index 000000000..39b303214 --- /dev/null +++ b/pumpkin/src/block/entities/end_gateway.rs @@ -0,0 +1,77 @@ +use super::BlockEntity; +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_util::math::position::BlockPos; +use std::pin::Pin; +use tokio::sync::Mutex; + +pub struct EndGatewayBlockEntity { + pub position: BlockPos, + pub age: Mutex, + pub exact_teleport: Mutex, + pub exit_portal: Mutex>, +} + +impl BlockEntity for EndGatewayBlockEntity { + fn resource_location(&self) -> &'static str { + Self::ID + } + + fn get_position(&self) -> BlockPos { + self.position + } + + fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self + where + Self: Sized, + { + let age = nbt.get_long("Age").unwrap_or(0); + let exact_teleport = nbt.get_bool("ExactTeleport").unwrap_or(false); + let exit_portal = nbt.get_compound("ExitPortal").map(|c| { + BlockPos::new( + c.get_int("X").unwrap_or(0), + c.get_int("Y").unwrap_or(0), + c.get_int("Z").unwrap_or(0), + ) + }); + Self { + position, + age: Mutex::new(age), + exact_teleport: Mutex::new(exact_teleport), + exit_portal: Mutex::new(exit_portal), + } + } + + fn write_nbt<'a>( + &'a self, + nbt: &'a mut NbtCompound, + ) -> Pin + Send + 'a>> { + Box::pin(async move { + nbt.put_long("Age", *self.age.lock().await); + nbt.put_bool("ExactTeleport", *self.exact_teleport.lock().await); + if let Some(exit) = self.exit_portal.lock().await.as_ref() { + let mut exit_nbt = NbtCompound::new(); + exit_nbt.put_int("X", exit.0.x); + exit_nbt.put_int("Y", exit.0.y); + exit_nbt.put_int("Z", exit.0.z); + nbt.put_compound("ExitPortal", exit_nbt); + } + }) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl EndGatewayBlockEntity { + pub const ID: &'static str = "minecraft:end_gateway"; + #[must_use] + pub fn new(position: BlockPos) -> Self { + Self { + position, + age: Mutex::new(0), + exact_teleport: Mutex::new(false), + exit_portal: Mutex::new(None), + } + } +} diff --git a/pumpkin/src/block/entities/hanging_sign.rs b/pumpkin/src/block/entities/hanging_sign.rs new file mode 100644 index 000000000..31830a26d --- /dev/null +++ b/pumpkin/src/block/entities/hanging_sign.rs @@ -0,0 +1,110 @@ +use std::{ + pin::Pin, + sync::{ + Arc, + atomic::{AtomicBool, Ordering}, + }, +}; + +use super::BlockEntity; +use crate::block::entities::sign::Text; +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_util::math::position::BlockPos; +use tokio::sync::Mutex; + +pub struct HangingSignBlockEntity { + pub front_text: Text, + pub back_text: Text, + pub is_waxed: AtomicBool, + position: BlockPos, + pub currently_editing_player: Arc>>, +} + +impl BlockEntity for HangingSignBlockEntity { + fn resource_location(&self) -> &'static str { + Self::ID + } + + fn get_position(&self) -> BlockPos { + self.position + } + + fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self + where + Self: Sized, + { + let front_text = nbt + .get("front_text") + .cloned() + .map(Text::from) + .unwrap_or_default(); + let back_text = nbt + .get("back_text") + .cloned() + .map(Text::from) + .unwrap_or_default(); + let is_waxed = nbt.get_bool("is_waxed").unwrap_or(false); + Self { + position, + front_text, + back_text, + is_waxed: AtomicBool::new(is_waxed), + currently_editing_player: Arc::new(Mutex::new(None)), + } + } + + fn write_nbt<'a>( + &'a self, + nbt: &'a mut NbtCompound, + ) -> Pin + Send + 'a>> { + Box::pin(async move { + nbt.put("front_text", self.front_text.clone()); + nbt.put("back_text", self.back_text.clone()); + nbt.put_bool("is_waxed", self.is_waxed.load(Ordering::Relaxed)); + }) + } + + fn chunk_data_nbt(&self) -> Option { + let mut nbt = NbtCompound::new(); + nbt.put("front_text", self.front_text.clone()); + nbt.put("back_text", self.back_text.clone()); + nbt.put_bool("is_waxed", self.is_waxed.load(Ordering::Relaxed)); + Some(nbt) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl HangingSignBlockEntity { + pub const ID: &'static str = "minecraft:hanging_sign"; + #[must_use] + pub fn new(position: BlockPos, is_front: bool, messages: [Box; 4]) -> Self { + Self { + position, + is_waxed: AtomicBool::new(false), + front_text: if is_front { + Text::new(messages.clone()) + } else { + Text::default() + }, + back_text: if is_front { + Text::default() + } else { + Text::new(messages) + }, + currently_editing_player: Arc::new(Mutex::new(None)), + } + } + #[must_use] + pub fn empty(position: BlockPos) -> Self { + Self { + position, + is_waxed: AtomicBool::new(false), + front_text: Text::default(), + back_text: Text::default(), + currently_editing_player: Arc::new(Mutex::new(None)), + } + } +} diff --git a/pumpkin/src/block/entities/mod.rs b/pumpkin/src/block/entities/mod.rs index c59f44af6..6216853c4 100644 --- a/pumpkin/src/block/entities/mod.rs +++ b/pumpkin/src/block/entities/mod.rs @@ -37,6 +37,32 @@ pub mod sign; pub mod smoker; pub mod trapped_chest; +pub mod banner; +pub mod beehive; +pub mod brushable_block; +pub mod calibrated_sculk_sensor; +pub mod campfire; +pub mod conduit; +pub mod copper_golem_statue; +pub mod crafter; +pub mod creaking_heart; +pub mod decorated_pot; +pub mod dispenser; +pub mod enchanting_table; +pub mod end_gateway; +pub mod hanging_sign; +pub mod potent_sulfur; +pub mod sculk_catalyst; +pub mod sculk_sensor; +pub mod sculk_shrieker; +pub mod shelf; +pub mod skull; +pub mod structure_block; +pub mod test_block; +pub mod test_instance_block; +pub mod trial_spawner; +pub mod vault; + pub use furnace_like_block_entity::ExperienceContainer; pub use pumpkin_world::block::entities::PropertyDelegate; @@ -148,6 +174,7 @@ pub fn block_entity_from_generic(nbt: &NbtCompound) -> T { } #[must_use] +#[allow(clippy::too_many_lines)] pub fn block_entity_from_nbt(nbt: &NbtCompound) -> Option> { let id = nbt.get_string("id")?; let x = nbt.get_int("x")?; @@ -222,6 +249,75 @@ pub fn block_entity_from_nbt(nbt: &NbtCompound) -> Option> lectern::LecternBlockEntity::ID => { Some(Arc::new(lectern::LecternBlockEntity::from_nbt(nbt, pos))) } + dispenser::DispenserBlockEntity::ID => Some(Arc::new( + dispenser::DispenserBlockEntity::from_nbt(nbt, pos), + )), + hanging_sign::HangingSignBlockEntity::ID => Some(Arc::new( + hanging_sign::HangingSignBlockEntity::from_nbt(nbt, pos), + )), + creaking_heart::CreakingHeartBlockEntity::ID => Some(Arc::new( + creaking_heart::CreakingHeartBlockEntity::from_nbt(nbt, pos), + )), + enchanting_table::EnchantingTableBlockEntity::ID => Some(Arc::new( + enchanting_table::EnchantingTableBlockEntity::from_nbt(nbt, pos), + )), + skull::SkullBlockEntity::ID => Some(Arc::new(skull::SkullBlockEntity::from_nbt(nbt, pos))), + banner::BannerBlockEntity::ID => { + Some(Arc::new(banner::BannerBlockEntity::from_nbt(nbt, pos))) + } + structure_block::StructureBlockBlockEntity::ID => Some(Arc::new( + structure_block::StructureBlockBlockEntity::from_nbt(nbt, pos), + )), + end_gateway::EndGatewayBlockEntity::ID => Some(Arc::new( + end_gateway::EndGatewayBlockEntity::from_nbt(nbt, pos), + )), + conduit::ConduitBlockEntity::ID => { + Some(Arc::new(conduit::ConduitBlockEntity::from_nbt(nbt, pos))) + } + campfire::CampfireBlockEntity::ID => { + Some(Arc::new(campfire::CampfireBlockEntity::from_nbt(nbt, pos))) + } + beehive::BeehiveBlockEntity::ID => { + Some(Arc::new(beehive::BeehiveBlockEntity::from_nbt(nbt, pos))) + } + sculk_sensor::SculkSensorBlockEntity::ID => Some(Arc::new( + sculk_sensor::SculkSensorBlockEntity::from_nbt(nbt, pos), + )), + calibrated_sculk_sensor::CalibratedSculkSensorBlockEntity::ID => Some(Arc::new( + calibrated_sculk_sensor::CalibratedSculkSensorBlockEntity::from_nbt(nbt, pos), + )), + sculk_catalyst::SculkCatalystBlockEntity::ID => Some(Arc::new( + sculk_catalyst::SculkCatalystBlockEntity::from_nbt(nbt, pos), + )), + sculk_shrieker::SculkShriekerBlockEntity::ID => Some(Arc::new( + sculk_shrieker::SculkShriekerBlockEntity::from_nbt(nbt, pos), + )), + shelf::ShelfBlockEntity::ID => Some(Arc::new(shelf::ShelfBlockEntity::from_nbt(nbt, pos))), + brushable_block::BrushableBlockBlockEntity::ID => Some(Arc::new( + brushable_block::BrushableBlockBlockEntity::from_nbt(nbt, pos), + )), + decorated_pot::DecoratedPotBlockEntity::ID => Some(Arc::new( + decorated_pot::DecoratedPotBlockEntity::from_nbt(nbt, pos), + )), + crafter::CrafterBlockEntity::ID => { + Some(Arc::new(crafter::CrafterBlockEntity::from_nbt(nbt, pos))) + } + trial_spawner::TrialSpawnerBlockEntity::ID => Some(Arc::new( + trial_spawner::TrialSpawnerBlockEntity::from_nbt(nbt, pos), + )), + vault::VaultBlockEntity::ID => Some(Arc::new(vault::VaultBlockEntity::from_nbt(nbt, pos))), + test_block::TestBlockBlockEntity::ID => Some(Arc::new( + test_block::TestBlockBlockEntity::from_nbt(nbt, pos), + )), + test_instance_block::TestInstanceBlockBlockEntity::ID => Some(Arc::new( + test_instance_block::TestInstanceBlockBlockEntity::from_nbt(nbt, pos), + )), + copper_golem_statue::CopperGolemStatueBlockEntity::ID => Some(Arc::new( + copper_golem_statue::CopperGolemStatueBlockEntity::from_nbt(nbt, pos), + )), + potent_sulfur::PotentSulfurBlockEntity::ID => Some(Arc::new( + potent_sulfur::PotentSulfurBlockEntity::from_nbt(nbt, pos), + )), _ => None, } } @@ -230,3 +326,112 @@ pub fn block_entity_from_nbt(nbt: &NbtCompound) -> Option> pub fn has_block_block_entity(block: &Block) -> bool { BLOCK_ENTITY_TYPES.contains(&block.name) } + +#[must_use] +#[allow(clippy::too_many_lines)] +pub fn create_block_entity( + block_entity_type_id: u16, + position: BlockPos, +) -> Option> { + use pumpkin_data::block_properties::FacingHopper; + if block_entity_type_id == u16::MAX { + return None; + } + let name = BLOCK_ENTITY_TYPES.get(block_entity_type_id as usize)?; + match *name { + "furnace" => Some(Arc::new(furnace::FurnaceBlockEntity::new(position))), + "chest" => Some(Arc::new(chest::ChestBlockEntity::new(position))), + "trapped_chest" => Some(Arc::new(trapped_chest::TrappedChestBlockEntity::new( + position, + ))), + "ender_chest" => Some(Arc::new(ender_chest::EnderChestBlockEntity::new(position))), + "jukebox" => Some(Arc::new(jukebox::JukeboxBlockEntity::new(position))), + "dispenser" => Some(Arc::new(dispenser::DispenserBlockEntity::new(position))), + "dropper" => Some(Arc::new(dropper::DropperBlockEntity::new(position))), + "sign" => Some(Arc::new(sign::SignBlockEntity::empty(position))), + "hanging_sign" => Some(Arc::new(hanging_sign::HangingSignBlockEntity::empty( + position, + ))), + "mob_spawner" => Some(Arc::new(mob_spawner::MobSpawnerBlockEntity::new( + position, None, + ))), + "creaking_heart" => Some(Arc::new(creaking_heart::CreakingHeartBlockEntity::new( + position, + ))), + "brewing_stand" => Some(Arc::new(brewing_stand::BrewingStandBlockEntity::new( + position, + ))), + "enchanting_table" => Some(Arc::new(enchanting_table::EnchantingTableBlockEntity::new( + position, + ))), + "end_portal" => Some(Arc::new(end_portal::EndPortalBlockEntity::new(position))), + "beacon" => Some(Arc::new(beacon::BeaconBlockEntity::new(position))), + "skull" => Some(Arc::new(skull::SkullBlockEntity::new(position))), + "daylight_detector" => Some(Arc::new( + daylight_detector::DaylightDetectorBlockEntity::new(position), + )), + "hopper" => Some(Arc::new(hopper::HopperBlockEntity::new( + position, + FacingHopper::Down, + ))), + "comparator" => Some(Arc::new(comparator::ComparatorBlockEntity::new(position))), + "banner" => Some(Arc::new(banner::BannerBlockEntity::new(position))), + "structure_block" => Some(Arc::new(structure_block::StructureBlockBlockEntity::new( + position, + ))), + "end_gateway" => Some(Arc::new(end_gateway::EndGatewayBlockEntity::new(position))), + "command_block" => Some(Arc::new(command_block::CommandBlockEntity::new( + position, true, false, + ))), + "shulker_box" => Some(Arc::new(shulker_box::ShulkerBoxBlockEntity::new(position))), + "conduit" => Some(Arc::new(conduit::ConduitBlockEntity::new(position))), + "barrel" => Some(Arc::new(barrel::BarrelBlockEntity::new(position))), + "smoker" => Some(Arc::new(smoker::SmokerBlockEntity::new(position))), + "blast_furnace" => Some(Arc::new(blasting_furnace::BlastingFurnaceBlockEntity::new( + position, + ))), + "lectern" => Some(Arc::new(lectern::LecternBlockEntity::new(position))), + "bell" => Some(Arc::new(bell::BellBlockEntity::new(position))), + "jigsaw" => Some(Arc::new(jigsaw_block::JigsawBlockEntity::new(position))), + "campfire" => Some(Arc::new(campfire::CampfireBlockEntity::new(position))), + "beehive" => Some(Arc::new(beehive::BeehiveBlockEntity::new(position))), + "sculk_sensor" => Some(Arc::new(sculk_sensor::SculkSensorBlockEntity::new( + position, + ))), + "calibrated_sculk_sensor" => Some(Arc::new( + calibrated_sculk_sensor::CalibratedSculkSensorBlockEntity::new(position), + )), + "sculk_catalyst" => Some(Arc::new(sculk_catalyst::SculkCatalystBlockEntity::new( + position, + ))), + "sculk_shrieker" => Some(Arc::new(sculk_shrieker::SculkShriekerBlockEntity::new( + position, + ))), + "chiseled_bookshelf" => Some(Arc::new( + chiseled_bookshelf::ChiseledBookshelfBlockEntity::new(position), + )), + "shelf" => Some(Arc::new(shelf::ShelfBlockEntity::new(position))), + "brushable_block" => Some(Arc::new(brushable_block::BrushableBlockBlockEntity::new( + position, + ))), + "decorated_pot" => Some(Arc::new(decorated_pot::DecoratedPotBlockEntity::new( + position, + ))), + "crafter" => Some(Arc::new(crafter::CrafterBlockEntity::new(position))), + "trial_spawner" => Some(Arc::new(trial_spawner::TrialSpawnerBlockEntity::new( + position, + ))), + "vault" => Some(Arc::new(vault::VaultBlockEntity::new(position))), + "test_block" => Some(Arc::new(test_block::TestBlockBlockEntity::new(position))), + "test_instance_block" => Some(Arc::new( + test_instance_block::TestInstanceBlockBlockEntity::new(position), + )), + "copper_golem_statue" => Some(Arc::new( + copper_golem_statue::CopperGolemStatueBlockEntity::new(position), + )), + "potent_sulfur" => Some(Arc::new(potent_sulfur::PotentSulfurBlockEntity::new( + position, + ))), + _ => None, + } +} diff --git a/pumpkin/src/block/entities/potent_sulfur.rs b/pumpkin/src/block/entities/potent_sulfur.rs new file mode 100644 index 000000000..cacc42638 --- /dev/null +++ b/pumpkin/src/block/entities/potent_sulfur.rs @@ -0,0 +1,46 @@ +use std::pin::Pin; + +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_util::math::position::BlockPos; + +use super::BlockEntity; + +pub struct PotentSulfurBlockEntity { + pub position: BlockPos, +} + +impl BlockEntity for PotentSulfurBlockEntity { + fn resource_location(&self) -> &'static str { + Self::ID + } + + fn get_position(&self) -> BlockPos { + self.position + } + + fn from_nbt(_nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self + where + Self: Sized, + { + Self { position } + } + + fn write_nbt<'a>( + &'a self, + _nbt: &'a mut NbtCompound, + ) -> Pin + Send + 'a>> { + Box::pin(async {}) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl PotentSulfurBlockEntity { + pub const ID: &'static str = "minecraft:potent_sulfur"; + #[must_use] + pub const fn new(position: BlockPos) -> Self { + Self { position } + } +} diff --git a/pumpkin/src/block/entities/sculk_catalyst.rs b/pumpkin/src/block/entities/sculk_catalyst.rs new file mode 100644 index 000000000..d0a277f86 --- /dev/null +++ b/pumpkin/src/block/entities/sculk_catalyst.rs @@ -0,0 +1,55 @@ +use super::BlockEntity; +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_util::math::position::BlockPos; +use std::pin::Pin; +use tokio::sync::Mutex; + +pub struct SculkCatalystBlockEntity { + pub position: BlockPos, + pub decay_delay: Mutex, +} + +impl BlockEntity for SculkCatalystBlockEntity { + fn resource_location(&self) -> &'static str { + Self::ID + } + + fn get_position(&self) -> BlockPos { + self.position + } + + fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self + where + Self: Sized, + { + let decay_delay = nbt.get_int("decay_delay").unwrap_or(0); + Self { + position, + decay_delay: Mutex::new(decay_delay), + } + } + + fn write_nbt<'a>( + &'a self, + nbt: &'a mut NbtCompound, + ) -> Pin + Send + 'a>> { + Box::pin(async move { + nbt.put_int("decay_delay", *self.decay_delay.lock().await); + }) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl SculkCatalystBlockEntity { + pub const ID: &'static str = "minecraft:sculk_catalyst"; + #[must_use] + pub fn new(position: BlockPos) -> Self { + Self { + position, + decay_delay: Mutex::new(0), + } + } +} diff --git a/pumpkin/src/block/entities/sculk_sensor.rs b/pumpkin/src/block/entities/sculk_sensor.rs new file mode 100644 index 000000000..f4f3dae4b --- /dev/null +++ b/pumpkin/src/block/entities/sculk_sensor.rs @@ -0,0 +1,58 @@ +use super::BlockEntity; +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_util::math::position::BlockPos; +use std::pin::Pin; +use tokio::sync::Mutex; + +pub struct SculkSensorBlockEntity { + pub position: BlockPos, + pub last_vibration_frequency: Mutex, +} + +impl BlockEntity for SculkSensorBlockEntity { + fn resource_location(&self) -> &'static str { + Self::ID + } + + fn get_position(&self) -> BlockPos { + self.position + } + + fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self + where + Self: Sized, + { + let last_vibration_frequency = nbt.get_int("last_vibration_frequency").unwrap_or(0); + Self { + position, + last_vibration_frequency: Mutex::new(last_vibration_frequency), + } + } + + fn write_nbt<'a>( + &'a self, + nbt: &'a mut NbtCompound, + ) -> Pin + Send + 'a>> { + Box::pin(async move { + nbt.put_int( + "last_vibration_frequency", + *self.last_vibration_frequency.lock().await, + ); + }) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl SculkSensorBlockEntity { + pub const ID: &'static str = "minecraft:sculk_sensor"; + #[must_use] + pub fn new(position: BlockPos) -> Self { + Self { + position, + last_vibration_frequency: Mutex::new(0), + } + } +} diff --git a/pumpkin/src/block/entities/sculk_shrieker.rs b/pumpkin/src/block/entities/sculk_shrieker.rs new file mode 100644 index 000000000..a86c745fa --- /dev/null +++ b/pumpkin/src/block/entities/sculk_shrieker.rs @@ -0,0 +1,55 @@ +use super::BlockEntity; +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_util::math::position::BlockPos; +use std::pin::Pin; +use tokio::sync::Mutex; + +pub struct SculkShriekerBlockEntity { + pub position: BlockPos, + pub warning_level: Mutex, +} + +impl BlockEntity for SculkShriekerBlockEntity { + fn resource_location(&self) -> &'static str { + Self::ID + } + + fn get_position(&self) -> BlockPos { + self.position + } + + fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self + where + Self: Sized, + { + let warning_level = nbt.get_int("warning_level").unwrap_or(0); + Self { + position, + warning_level: Mutex::new(warning_level), + } + } + + fn write_nbt<'a>( + &'a self, + nbt: &'a mut NbtCompound, + ) -> Pin + Send + 'a>> { + Box::pin(async move { + nbt.put_int("warning_level", *self.warning_level.lock().await); + }) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl SculkShriekerBlockEntity { + pub const ID: &'static str = "minecraft:sculk_shrieker"; + #[must_use] + pub fn new(position: BlockPos) -> Self { + Self { + position, + warning_level: Mutex::new(0), + } + } +} diff --git a/pumpkin/src/block/entities/shelf.rs b/pumpkin/src/block/entities/shelf.rs new file mode 100644 index 000000000..edb3fb15e --- /dev/null +++ b/pumpkin/src/block/entities/shelf.rs @@ -0,0 +1,145 @@ +use crate::block::entities::BlockEntity; +use pumpkin_data::item_stack::ItemStack; +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture, split_stack}; +use std::any::Any; +use std::array::from_fn; +use std::pin::Pin; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; +use tokio::sync::Mutex; + +pub struct ShelfBlockEntity { + pub position: BlockPos, + pub items: [Arc>; Self::INVENTORY_SIZE], + pub dirty: AtomicBool, +} + +impl BlockEntity for ShelfBlockEntity { + fn write_nbt<'a>( + &'a self, + nbt: &'a mut NbtCompound, + ) -> Pin + Send + 'a>> { + self.write_inventory_nbt(nbt, true) + } + + fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self + where + Self: Sized, + { + let shelf = Self { + position, + items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + dirty: AtomicBool::new(false), + }; + + shelf.read_data(nbt, &shelf.items); + + shelf + } + + fn resource_location(&self) -> &'static str { + Self::ID + } + + fn get_position(&self) -> BlockPos { + self.position + } + + fn get_inventory(self: Arc) -> Option> { + Some(self) + } + + fn is_dirty(&self) -> bool { + self.dirty.load(Ordering::Relaxed) + } + + fn clear_dirty(&self) { + self.dirty.store(false, Ordering::Relaxed); + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl ShelfBlockEntity { + pub const INVENTORY_SIZE: usize = 9; + pub const ID: &'static str = "minecraft:shelf"; + + #[must_use] + pub fn new(position: BlockPos) -> Self { + Self { + position, + items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))), + dirty: AtomicBool::new(false), + } + } +} + +impl Inventory for ShelfBlockEntity { + fn size(&self) -> usize { + self.items.len() + } + + fn is_empty(&self) -> InventoryFuture<'_, bool> { + Box::pin(async move { + for slot in &self.items { + if !slot.lock().await.is_empty() { + return false; + } + } + + true + }) + } + + fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc>> { + Box::pin(async move { self.items[slot].clone() }) + } + + fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let mut removed = ItemStack::EMPTY.clone(); + let mut guard = self.items[slot].lock().await; + std::mem::swap(&mut removed, &mut *guard); + self.mark_dirty(); + removed + }) + } + + fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { + Box::pin(async move { + let res = split_stack(&self.items, slot, amount).await; + self.mark_dirty(); + res + }) + } + + fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { + Box::pin(async move { + *self.items[slot].lock().await = stack; + self.mark_dirty(); + }) + } + + fn mark_dirty(&self) { + self.dirty.store(true, Ordering::Relaxed); + } + + fn as_any(&self) -> &dyn Any { + self + } +} + +impl Clearable for ShelfBlockEntity { + fn clear(&self) -> Pin + Send + '_>> { + Box::pin(async move { + for slot in &self.items { + *slot.lock().await = ItemStack::EMPTY.clone(); + } + self.mark_dirty(); + }) + } +} diff --git a/pumpkin/src/block/entities/sign.rs b/pumpkin/src/block/entities/sign.rs index f4c3c2722..6a70bc80d 100644 --- a/pumpkin/src/block/entities/sign.rs +++ b/pumpkin/src/block/entities/sign.rs @@ -196,7 +196,8 @@ impl From for Text { } impl Text { - fn new(messages: [Box; 4]) -> Self { + #[must_use] + pub fn new(messages: [Box; 4]) -> Self { Self { has_glowing_text: AtomicBool::new(false), color: AtomicI8::new(DyeColor::default() as i8), diff --git a/pumpkin/src/block/entities/skull.rs b/pumpkin/src/block/entities/skull.rs new file mode 100644 index 000000000..5ce3f532f --- /dev/null +++ b/pumpkin/src/block/entities/skull.rs @@ -0,0 +1,64 @@ +use super::BlockEntity; +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_util::math::position::BlockPos; +use std::pin::Pin; +use tokio::sync::Mutex; + +pub struct SkullBlockEntity { + pub position: BlockPos, + pub note_block_sound: Mutex>, + pub profile: Mutex>, +} + +impl BlockEntity for SkullBlockEntity { + fn resource_location(&self) -> &'static str { + Self::ID + } + + fn get_position(&self) -> BlockPos { + self.position + } + + fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self + where + Self: Sized, + { + let note_block_sound = nbt.get_string("note_block_sound").map(ToString::to_string); + let profile = nbt.get_compound("profile").cloned(); + Self { + position, + note_block_sound: Mutex::new(note_block_sound), + profile: Mutex::new(profile), + } + } + + fn write_nbt<'a>( + &'a self, + nbt: &'a mut NbtCompound, + ) -> Pin + Send + 'a>> { + Box::pin(async move { + if let Some(sound) = self.note_block_sound.lock().await.as_ref() { + nbt.put_string("note_block_sound", sound.clone()); + } + if let Some(prof) = self.profile.lock().await.as_ref() { + nbt.put_compound("profile", prof.clone()); + } + }) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl SkullBlockEntity { + pub const ID: &'static str = "minecraft:skull"; + #[must_use] + pub const fn new(position: BlockPos) -> Self { + Self { + position, + note_block_sound: Mutex::const_new(None), + profile: Mutex::const_new(None), + } + } +} diff --git a/pumpkin/src/block/entities/structure_block.rs b/pumpkin/src/block/entities/structure_block.rs new file mode 100644 index 000000000..9f2bf96d4 --- /dev/null +++ b/pumpkin/src/block/entities/structure_block.rs @@ -0,0 +1,118 @@ +use super::BlockEntity; +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_util::math::position::BlockPos; +use std::pin::Pin; +use tokio::sync::Mutex; + +pub struct StructureBlockBlockEntity { + pub position: BlockPos, + pub name: Mutex, + pub author: Mutex, + pub metadata: Mutex, + pub pos_x: Mutex, + pub pos_y: Mutex, + pub pos_z: Mutex, + pub size_x: Mutex, + pub size_y: Mutex, + pub size_z: Mutex, + pub rotation: Mutex, + pub mirror: Mutex, + pub mode: Mutex, + pub ignore_entities: Mutex, + pub show_air: Mutex, + pub show_bounding_box: Mutex, + pub integrity: Mutex, + pub seed: Mutex, +} + +impl BlockEntity for StructureBlockBlockEntity { + fn resource_location(&self) -> &'static str { + Self::ID + } + + fn get_position(&self) -> BlockPos { + self.position + } + + fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self + where + Self: Sized, + { + Self { + position, + name: Mutex::new(nbt.get_string("name").unwrap_or("").to_string()), + author: Mutex::new(nbt.get_string("author").unwrap_or("").to_string()), + metadata: Mutex::new(nbt.get_string("metadata").unwrap_or("").to_string()), + pos_x: Mutex::new(nbt.get_int("posX").unwrap_or(0)), + pos_y: Mutex::new(nbt.get_int("posY").unwrap_or(0)), + pos_z: Mutex::new(nbt.get_int("posZ").unwrap_or(0)), + size_x: Mutex::new(nbt.get_int("sizeX").unwrap_or(0)), + size_y: Mutex::new(nbt.get_int("sizeY").unwrap_or(0)), + size_z: Mutex::new(nbt.get_int("sizeZ").unwrap_or(0)), + rotation: Mutex::new(nbt.get_string("rotation").unwrap_or("NONE").to_string()), + mirror: Mutex::new(nbt.get_string("mirror").unwrap_or("NONE").to_string()), + mode: Mutex::new(nbt.get_string("mode").unwrap_or("DATA").to_string()), + ignore_entities: Mutex::new(nbt.get_bool("ignoreEntities").unwrap_or(true)), + show_air: Mutex::new(nbt.get_bool("showAir").unwrap_or(false)), + show_bounding_box: Mutex::new(nbt.get_bool("showBoundingBox").unwrap_or(true)), + integrity: Mutex::new(nbt.get_float("integrity").unwrap_or(1.0)), + seed: Mutex::new(nbt.get_long("seed").unwrap_or(0)), + } + } + + fn write_nbt<'a>( + &'a self, + nbt: &'a mut NbtCompound, + ) -> Pin + Send + 'a>> { + Box::pin(async move { + nbt.put_string("name", self.name.lock().await.clone()); + nbt.put_string("author", self.author.lock().await.clone()); + nbt.put_string("metadata", self.metadata.lock().await.clone()); + nbt.put_int("posX", *self.pos_x.lock().await); + nbt.put_int("posY", *self.pos_y.lock().await); + nbt.put_int("posZ", *self.pos_z.lock().await); + nbt.put_int("sizeX", *self.size_x.lock().await); + nbt.put_int("sizeY", *self.size_y.lock().await); + nbt.put_int("sizeZ", *self.size_z.lock().await); + nbt.put_string("rotation", self.rotation.lock().await.clone()); + nbt.put_string("mirror", self.mirror.lock().await.clone()); + nbt.put_string("mode", self.mode.lock().await.clone()); + nbt.put_bool("ignoreEntities", *self.ignore_entities.lock().await); + nbt.put_bool("showAir", *self.show_air.lock().await); + nbt.put_bool("showBoundingBox", *self.show_bounding_box.lock().await); + nbt.put_float("integrity", *self.integrity.lock().await); + nbt.put_long("seed", *self.seed.lock().await); + }) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl StructureBlockBlockEntity { + pub const ID: &'static str = "minecraft:structure_block"; + #[must_use] + pub fn new(position: BlockPos) -> Self { + Self { + position, + name: Mutex::new(String::new()), + author: Mutex::new(String::new()), + metadata: Mutex::new(String::new()), + pos_x: Mutex::new(0), + pos_y: Mutex::new(0), + pos_z: Mutex::new(0), + size_x: Mutex::new(0), + size_y: Mutex::new(0), + size_z: Mutex::new(0), + rotation: Mutex::new("NONE".to_string()), + mirror: Mutex::new("NONE".to_string()), + mode: Mutex::new("DATA".to_string()), + ignore_entities: Mutex::new(true), + show_air: Mutex::new(false), + show_bounding_box: Mutex::new(true), + integrity: Mutex::new(1.0), + seed: Mutex::new(0), + } + } +} diff --git a/pumpkin/src/block/entities/test_block.rs b/pumpkin/src/block/entities/test_block.rs new file mode 100644 index 000000000..9286a1311 --- /dev/null +++ b/pumpkin/src/block/entities/test_block.rs @@ -0,0 +1,46 @@ +use std::pin::Pin; + +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_util::math::position::BlockPos; + +use super::BlockEntity; + +pub struct TestBlockBlockEntity { + pub position: BlockPos, +} + +impl BlockEntity for TestBlockBlockEntity { + fn resource_location(&self) -> &'static str { + Self::ID + } + + fn get_position(&self) -> BlockPos { + self.position + } + + fn from_nbt(_nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self + where + Self: Sized, + { + Self { position } + } + + fn write_nbt<'a>( + &'a self, + _nbt: &'a mut NbtCompound, + ) -> Pin + Send + 'a>> { + Box::pin(async {}) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl TestBlockBlockEntity { + pub const ID: &'static str = "minecraft:test_block"; + #[must_use] + pub const fn new(position: BlockPos) -> Self { + Self { position } + } +} diff --git a/pumpkin/src/block/entities/test_instance_block.rs b/pumpkin/src/block/entities/test_instance_block.rs new file mode 100644 index 000000000..6c59e8cda --- /dev/null +++ b/pumpkin/src/block/entities/test_instance_block.rs @@ -0,0 +1,46 @@ +use std::pin::Pin; + +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_util::math::position::BlockPos; + +use super::BlockEntity; + +pub struct TestInstanceBlockBlockEntity { + pub position: BlockPos, +} + +impl BlockEntity for TestInstanceBlockBlockEntity { + fn resource_location(&self) -> &'static str { + Self::ID + } + + fn get_position(&self) -> BlockPos { + self.position + } + + fn from_nbt(_nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self + where + Self: Sized, + { + Self { position } + } + + fn write_nbt<'a>( + &'a self, + _nbt: &'a mut NbtCompound, + ) -> Pin + Send + 'a>> { + Box::pin(async {}) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl TestInstanceBlockBlockEntity { + pub const ID: &'static str = "minecraft:test_instance_block"; + #[must_use] + pub const fn new(position: BlockPos) -> Self { + Self { position } + } +} diff --git a/pumpkin/src/block/entities/trial_spawner.rs b/pumpkin/src/block/entities/trial_spawner.rs new file mode 100644 index 000000000..572f485ed --- /dev/null +++ b/pumpkin/src/block/entities/trial_spawner.rs @@ -0,0 +1,68 @@ +use super::BlockEntity; +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_util::math::position::BlockPos; +use std::pin::Pin; +use tokio::sync::Mutex; + +pub struct TrialSpawnerBlockEntity { + pub position: BlockPos, + pub normal_config: Mutex>, + pub ominous_config: Mutex>, + pub spawner_data: Mutex>, +} + +impl BlockEntity for TrialSpawnerBlockEntity { + fn resource_location(&self) -> &'static str { + Self::ID + } + + fn get_position(&self) -> BlockPos { + self.position + } + + fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self + where + Self: Sized, + { + Self { + position, + normal_config: Mutex::new(nbt.get_compound("normal_config").cloned()), + ominous_config: Mutex::new(nbt.get_compound("ominous_config").cloned()), + spawner_data: Mutex::new(nbt.get_compound("spawner_data").cloned()), + } + } + + fn write_nbt<'a>( + &'a self, + nbt: &'a mut NbtCompound, + ) -> Pin + Send + 'a>> { + Box::pin(async move { + if let Some(cfg) = self.normal_config.lock().await.as_ref() { + nbt.put_compound("normal_config", cfg.clone()); + } + if let Some(cfg) = self.ominous_config.lock().await.as_ref() { + nbt.put_compound("ominous_config", cfg.clone()); + } + if let Some(data) = self.spawner_data.lock().await.as_ref() { + nbt.put_compound("spawner_data", data.clone()); + } + }) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl TrialSpawnerBlockEntity { + pub const ID: &'static str = "minecraft:trial_spawner"; + #[must_use] + pub const fn new(position: BlockPos) -> Self { + Self { + position, + normal_config: Mutex::const_new(None), + ominous_config: Mutex::const_new(None), + spawner_data: Mutex::const_new(None), + } + } +} diff --git a/pumpkin/src/block/entities/vault.rs b/pumpkin/src/block/entities/vault.rs new file mode 100644 index 000000000..95ad44c3f --- /dev/null +++ b/pumpkin/src/block/entities/vault.rs @@ -0,0 +1,62 @@ +use super::BlockEntity; +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_util::math::position::BlockPos; +use std::pin::Pin; +use tokio::sync::Mutex; + +pub struct VaultBlockEntity { + pub position: BlockPos, + pub config: Mutex>, + pub server_data: Mutex>, +} + +impl BlockEntity for VaultBlockEntity { + fn resource_location(&self) -> &'static str { + Self::ID + } + + fn get_position(&self) -> BlockPos { + self.position + } + + fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self + where + Self: Sized, + { + Self { + position, + config: Mutex::new(nbt.get_compound("config").cloned()), + server_data: Mutex::new(nbt.get_compound("server_data").cloned()), + } + } + + fn write_nbt<'a>( + &'a self, + nbt: &'a mut NbtCompound, + ) -> Pin + Send + 'a>> { + Box::pin(async move { + if let Some(cfg) = self.config.lock().await.as_ref() { + nbt.put_compound("config", cfg.clone()); + } + if let Some(data) = self.server_data.lock().await.as_ref() { + nbt.put_compound("server_data", data.clone()); + } + }) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl VaultBlockEntity { + pub const ID: &'static str = "minecraft:vault"; + #[must_use] + pub const fn new(position: BlockPos) -> Self { + Self { + position, + config: Mutex::const_new(None), + server_data: Mutex::const_new(None), + } + } +} diff --git a/pumpkin/src/block/registry.rs b/pumpkin/src/block/registry.rs index a945d3498..8908bd146 100644 --- a/pumpkin/src/block/registry.rs +++ b/pumpkin/src/block/registry.rs @@ -919,6 +919,15 @@ impl BlockRegistry { old_state_id: BlockStateId, notify: bool, ) { + let state = world.get_block_state(position); + if state.block_entity_type != u16::MAX + && world.get_block_entity(position).is_none() + && let Some(entity) = + crate::block::entities::create_block_entity(state.block_entity_type, *position) + { + world.add_block_entity(entity); + } + let pumpkin_block = self.get_pumpkin_block(block.id); if let Some(pumpkin_block) = pumpkin_block { pumpkin_block