feat: Implement all missing block entites

This commit is contained in:
Alexander Medvedev 2026-07-11 09:35:26 +02:00
parent 9eab7581fd
commit 3c6557d6db
38 changed files with 2233 additions and 9 deletions

View File

@ -179,7 +179,7 @@ tracing-subscriber = { version = "0.3.23", default-features = false, features =
] } ] }
slotmap = { version = "1.1", default-features = false, features = ["std"] } slotmap = { version = "1.1", default-features = false, features = ["std"] }
temp-dir = { version = "0.2.0", default-features = false } 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"] } time = { version = "0.3", default-features = false, features = ["std"] }
arc-swap = { version = "1.9", default-features = false } arc-swap = { version = "1.9", default-features = false }
tokio-util = { version = "0.7.18", default-features = false } tokio-util = { version = "0.7.18", default-features = false }

View File

@ -1,5 +1,6 @@
use crate::block::{ use crate::block::{
BlockBehaviour, BlockFuture, GetStateForNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, BlockBehaviour, BlockFuture, GetStateForNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs,
PlacedArgs,
}; };
use crate::entity::EntityBase; use crate::entity::EntityBase;
use pumpkin_data::BlockStateId; use pumpkin_data::BlockStateId;
@ -9,10 +10,20 @@ use pumpkin_util::math::position::BlockPos;
use pumpkin_world::tick::TickPriority; use pumpkin_world::tick::TickPriority;
use pumpkin_world::world::{BlockAccessor, BlockFlags}; use pumpkin_world::world::{BlockAccessor, BlockFlags};
use crate::block::entities::banner::BannerBlockEntity;
use std::sync::Arc;
#[pumpkin_block_from_tag("minecraft:banners")] #[pumpkin_block_from_tag("minecraft:banners")]
pub struct BannerBlock; pub struct BannerBlock;
impl BlockBehaviour for 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> { fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move { Box::pin(async move {
let mut props = WhiteBannerLikeProperties::default(args.block); let mut props = WhiteBannerLikeProperties::default(args.block);

View File

@ -9,18 +9,27 @@ use pumpkin_data::{
use pumpkin_macros::pumpkin_block_from_tag; use pumpkin_macros::pumpkin_block_from_tag;
use pumpkin_world::tick::TickPriority; use pumpkin_world::tick::TickPriority;
use crate::block::entities::campfire::CampfireBlockEntity;
use crate::{ use crate::{
block::{ block::{
BlockBehaviour, BlockFuture, BlockIsReplacing, GetStateForNeighborUpdateArgs, BlockBehaviour, BlockFuture, BlockIsReplacing, GetStateForNeighborUpdateArgs,
OnEntityCollisionArgs, OnPlaceArgs, OnEntityCollisionArgs, OnPlaceArgs, PlacedArgs,
}, },
entity::EntityBase, entity::EntityBase,
}; };
use std::sync::Arc;
#[pumpkin_block_from_tag("minecraft:campfires")] #[pumpkin_block_from_tag("minecraft:campfires")]
pub struct CampfireBlock; pub struct CampfireBlock;
impl BlockBehaviour for 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) // TODO: cooking food on campfire (CampfireBlockEntity)
fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> { fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move { Box::pin(async move {

View File

@ -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::BlockStateId;
use pumpkin_data::block_properties::BlockProperties; use pumpkin_data::block_properties::BlockProperties;
use pumpkin_macros::pumpkin_block; use pumpkin_macros::pumpkin_block;
use std::sync::Arc;
#[pumpkin_block("minecraft:conduit")] #[pumpkin_block("minecraft:conduit")]
pub struct ConduitBlock; pub struct ConduitBlock;
@ -16,4 +18,11 @@ impl BlockBehaviour for ConduitBlock {
props.to_state_id(args.block) 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));
})
}
} }

View File

@ -1,7 +1,8 @@
use std::sync::Arc; use std::sync::Arc;
use crate::block::entities::enchanting_table::EnchantingTableBlockEntity;
use crate::block::registry::BlockActionResult; 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_data::{Block, BlockStateId, translation};
use pumpkin_inventory::enchanting::enchanting_screen_handler::EnchantingTableScreenHandler; use pumpkin_inventory::enchanting::enchanting_screen_handler::EnchantingTableScreenHandler;
use pumpkin_inventory::player::player_inventory::PlayerInventory; use pumpkin_inventory::player::player_inventory::PlayerInventory;
@ -18,6 +19,13 @@ use tokio::sync::Mutex;
pub struct EnchantingTableBlock; pub struct EnchantingTableBlock;
impl BlockBehaviour for 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> { fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move { Box::pin(async move {
let mut bookshelf_count = 0; let mut bookshelf_count = 0;

View File

@ -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 crate::entity::EntityBase;
use pumpkin_data::BlockStateId; use pumpkin_data::BlockStateId;
use pumpkin_data::block_properties::{BlockProperties, DispenserLikeProperties}; use pumpkin_data::block_properties::{BlockProperties, DispenserLikeProperties};
use pumpkin_macros::pumpkin_block; use pumpkin_macros::pumpkin_block;
use std::sync::Arc;
#[pumpkin_block("minecraft:dispenser")] #[pumpkin_block("minecraft:dispenser")]
pub struct DispenserBlock; pub struct DispenserBlock;
@ -15,4 +17,12 @@ impl BlockBehaviour for DispenserBlock {
props.to_state_id(args.block) 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));
})
}
} }

View File

@ -2,8 +2,10 @@ use pumpkin_data::BlockStateId;
use pumpkin_data::block_properties::{AcaciaShelfLikeProperties, BlockProperties}; use pumpkin_data::block_properties::{AcaciaShelfLikeProperties, BlockProperties};
use pumpkin_macros::pumpkin_block_from_tag; 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 crate::entity::EntityBase;
use std::sync::Arc;
#[pumpkin_block_from_tag("minecraft:wooden_shelves")] #[pumpkin_block_from_tag("minecraft:wooden_shelves")]
pub struct ShelfBlock; pub struct ShelfBlock;
@ -19,4 +21,11 @@ impl BlockBehaviour for ShelfBlock {
properties.to_state_id(args.block) 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));
})
}
} }

View File

@ -1,6 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use crate::block::entities::hanging_sign::HangingSignBlockEntity;
use crate::block::entities::sign::SignBlockEntity; use crate::block::entities::sign::SignBlockEntity;
use pumpkin_data::Block; use pumpkin_data::Block;
use pumpkin_data::BlockDirection; use pumpkin_data::BlockDirection;
@ -308,8 +309,13 @@ impl BlockBehaviour for SignBlock {
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> { fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move { Box::pin(async move {
if args.block.name.contains("hanging") {
args.world
.add_block_entity(Arc::new(HangingSignBlockEntity::empty(*args.position)));
} else {
args.world args.world
.add_block_entity(Arc::new(SignBlockEntity::empty(*args.position))); .add_block_entity(Arc::new(SignBlockEntity::empty(*args.position)));
}
}) })
} }

View File

@ -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 { 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> { fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move { Box::pin(async move {
let mut props = SkeletonSkullLikeProperties::default(args.block); let mut props = SkeletonSkullLikeProperties::default(args.block);

View File

@ -19,6 +19,9 @@ impl BlockBehaviour for WitherSkeletonSkullBlock {
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> { fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move { 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 world = args.world;
let pos = args.position; let pos = args.position;

View File

@ -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<Option<String>>,
pub patterns: Mutex<Option<Vec<NbtTag>>>,
}
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<Box<dyn Future<Output = ()> + 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),
}
}
}

View File

@ -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<Option<Vec<NbtTag>>>,
pub flower_pos: Mutex<Option<BlockPos>>,
}
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<Box<dyn Future<Output = ()> + 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),
}
}
}

View File

@ -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<Option<ItemStack>>,
pub hits: Mutex<i32>,
pub direction: Mutex<u8>,
}
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<Box<dyn Future<Output = ()> + 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),
}
}
}

View File

@ -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<i32>,
}
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<Box<dyn Future<Output = ()> + 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),
}
}
}

View File

@ -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<Mutex<ItemStack>>; 4],
pub cooking_times: [tokio::sync::Mutex<i32>; 4],
pub cooking_total_times: [tokio::sync::Mutex<i32>; 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<Box<dyn Future<Output = ()> + 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)],
}
}
}

View File

@ -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<bool>,
pub target: Mutex<Option<NbtTag>>,
}
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<Box<dyn Future<Output = ()> + 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),
}
}
}

View File

@ -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<Box<dyn Future<Output = ()> + 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 }
}
}

View File

@ -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<Mutex<ItemStack>>; 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<Box<dyn Future<Output = ()> + 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<Self>) -> Option<Arc<dyn Inventory>> {
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<Mutex<ItemStack>>> {
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<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move {
for slot in &self.items {
*slot.lock().await = ItemStack::EMPTY.clone();
}
self.mark_dirty();
})
}
}

View File

@ -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<Box<dyn Future<Output = ()> + 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 }
}
}

View File

@ -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<Option<Vec<NbtTag>>>,
pub item: Mutex<Option<ItemStack>>,
}
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<Box<dyn Future<Output = ()> + 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),
}
}
}

View File

@ -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<Mutex<ItemStack>>; Self::INVENTORY_SIZE],
pub dirty: AtomicBool,
}
impl BlockEntity for DispenserBlockEntity {
fn write_nbt<'a>(
&'a self,
nbt: &'a mut NbtCompound,
) -> Pin<Box<dyn Future<Output = ()> + 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<Self>) -> Option<Arc<dyn Inventory>> {
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<MutexGuard<'_, ItemStack>> {
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<Mutex<ItemStack>>> {
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<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move {
for slot in &self.items {
*slot.lock().await = ItemStack::EMPTY.clone();
}
self.mark_dirty();
})
}
}

View File

@ -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<Option<String>>,
}
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<Box<dyn Future<Output = ()> + 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),
}
}
}

View File

@ -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<i64>,
pub exact_teleport: Mutex<bool>,
pub exit_portal: Mutex<Option<BlockPos>>,
}
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<Box<dyn Future<Output = ()> + 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),
}
}
}

View File

@ -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<Mutex<Option<uuid::Uuid>>>,
}
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<Box<dyn Future<Output = ()> + 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<NbtCompound> {
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<str>; 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)),
}
}
}

View File

@ -37,6 +37,32 @@ pub mod sign;
pub mod smoker; pub mod smoker;
pub mod trapped_chest; 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 furnace_like_block_entity::ExperienceContainer;
pub use pumpkin_world::block::entities::PropertyDelegate; pub use pumpkin_world::block::entities::PropertyDelegate;
@ -148,6 +174,7 @@ pub fn block_entity_from_generic<T: BlockEntity>(nbt: &NbtCompound) -> T {
} }
#[must_use] #[must_use]
#[allow(clippy::too_many_lines)]
pub fn block_entity_from_nbt(nbt: &NbtCompound) -> Option<Arc<dyn BlockEntity>> { pub fn block_entity_from_nbt(nbt: &NbtCompound) -> Option<Arc<dyn BlockEntity>> {
let id = nbt.get_string("id")?; let id = nbt.get_string("id")?;
let x = nbt.get_int("x")?; let x = nbt.get_int("x")?;
@ -222,6 +249,75 @@ pub fn block_entity_from_nbt(nbt: &NbtCompound) -> Option<Arc<dyn BlockEntity>>
lectern::LecternBlockEntity::ID => { lectern::LecternBlockEntity::ID => {
Some(Arc::new(lectern::LecternBlockEntity::from_nbt(nbt, pos))) 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, _ => None,
} }
} }
@ -230,3 +326,112 @@ pub fn block_entity_from_nbt(nbt: &NbtCompound) -> Option<Arc<dyn BlockEntity>>
pub fn has_block_block_entity(block: &Block) -> bool { pub fn has_block_block_entity(block: &Block) -> bool {
BLOCK_ENTITY_TYPES.contains(&block.name) 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<Arc<dyn BlockEntity>> {
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,
}
}

View File

@ -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<Box<dyn Future<Output = ()> + 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 }
}
}

View File

@ -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<i32>,
}
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<Box<dyn Future<Output = ()> + 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),
}
}
}

View File

@ -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<i32>,
}
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<Box<dyn Future<Output = ()> + 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),
}
}
}

View File

@ -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<i32>,
}
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<Box<dyn Future<Output = ()> + 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),
}
}
}

View File

@ -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<Mutex<ItemStack>>; Self::INVENTORY_SIZE],
pub dirty: AtomicBool,
}
impl BlockEntity for ShelfBlockEntity {
fn write_nbt<'a>(
&'a self,
nbt: &'a mut NbtCompound,
) -> Pin<Box<dyn Future<Output = ()> + 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<Self>) -> Option<Arc<dyn Inventory>> {
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<Mutex<ItemStack>>> {
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<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move {
for slot in &self.items {
*slot.lock().await = ItemStack::EMPTY.clone();
}
self.mark_dirty();
})
}
}

View File

@ -196,7 +196,8 @@ impl From<NbtTag> for Text {
} }
impl Text { impl Text {
fn new(messages: [Box<str>; 4]) -> Self { #[must_use]
pub fn new(messages: [Box<str>; 4]) -> Self {
Self { Self {
has_glowing_text: AtomicBool::new(false), has_glowing_text: AtomicBool::new(false),
color: AtomicI8::new(DyeColor::default() as i8), color: AtomicI8::new(DyeColor::default() as i8),

View File

@ -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<Option<String>>,
pub profile: Mutex<Option<NbtCompound>>,
}
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<Box<dyn Future<Output = ()> + 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),
}
}
}

View File

@ -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<String>,
pub author: Mutex<String>,
pub metadata: Mutex<String>,
pub pos_x: Mutex<i32>,
pub pos_y: Mutex<i32>,
pub pos_z: Mutex<i32>,
pub size_x: Mutex<i32>,
pub size_y: Mutex<i32>,
pub size_z: Mutex<i32>,
pub rotation: Mutex<String>,
pub mirror: Mutex<String>,
pub mode: Mutex<String>,
pub ignore_entities: Mutex<bool>,
pub show_air: Mutex<bool>,
pub show_bounding_box: Mutex<bool>,
pub integrity: Mutex<f32>,
pub seed: Mutex<i64>,
}
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<Box<dyn Future<Output = ()> + 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),
}
}
}

View File

@ -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<Box<dyn Future<Output = ()> + 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 }
}
}

View File

@ -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<Box<dyn Future<Output = ()> + 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 }
}
}

View File

@ -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<Option<NbtCompound>>,
pub ominous_config: Mutex<Option<NbtCompound>>,
pub spawner_data: Mutex<Option<NbtCompound>>,
}
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<Box<dyn Future<Output = ()> + 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),
}
}
}

View File

@ -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<Option<NbtCompound>>,
pub server_data: Mutex<Option<NbtCompound>>,
}
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<Box<dyn Future<Output = ()> + 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),
}
}
}

View File

@ -919,6 +919,15 @@ impl BlockRegistry {
old_state_id: BlockStateId, old_state_id: BlockStateId,
notify: bool, 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); let pumpkin_block = self.get_pumpkin_block(block.id);
if let Some(pumpkin_block) = pumpkin_block { if let Some(pumpkin_block) = pumpkin_block {
pumpkin_block pumpkin_block