refactor: move block entities from pumpkin-world -> pumpkin

This commit is contained in:
Alexander Medvedev
2026-05-07 19:35:17 +02:00
parent 2586d46380
commit 5c90387b02
87 changed files with 1514 additions and 1180 deletions

425
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -179,7 +179,7 @@ ureq = "3.3.0"
notify = "8.2.0"
wasmtime = "44.0"
wasmtime-wasi = "44.0"
wasmtime-wasi = { version = "44.0", default-features = false, features = ["p2"] }
wit-bindgen = "0.57"
wasmparser = "0.248"

View File

@@ -0,0 +1,136 @@
use std::{any::Any, sync::Arc};
use pumpkin_data::{item_stack::ItemStack, screen::WindowType};
use pumpkin_world::inventory::Inventory;
use crate::{
player::player_inventory::PlayerInventory,
screen_handler::{
InventoryPlayer, ItemStackFuture, ScreenHandler, ScreenHandlerBehaviour,
ScreenHandlerFuture,
},
slot::NormalSlot,
};
/// Creates a beacon container screen handler.
///
/// Beacons feature a single payment slot and a specialized UI for selecting status effects.
pub async fn create_beacon_handler(
sync_id: u8,
player_inventory: &Arc<PlayerInventory>,
inventory: Arc<dyn Inventory>,
) -> BeaconScreenHandler {
BeaconScreenHandler::new(sync_id, player_inventory, inventory).await
}
/// Screen handler specifically for Beacon blocks.
pub struct BeaconScreenHandler {
/// The beacon's inventory (contains exactly 1 slot for payment).
pub inventory: Arc<dyn Inventory>,
/// Core screen handler behavior (slots, sync ID, listeners).
behaviour: ScreenHandlerBehaviour,
}
impl BeaconScreenHandler {
/// Creates a new beacon screen handler.
async fn new(
sync_id: u8,
player_inventory: &Arc<PlayerInventory>,
inventory: Arc<dyn Inventory>,
) -> Self {
let mut handler = Self {
inventory: inventory.clone(),
behaviour: ScreenHandlerBehaviour::new(sync_id, Some(WindowType::Beacon)),
};
inventory.on_open().await;
// Add the single payment slot for the beacon (slot 0)
handler.add_slot(Arc::new(NormalSlot::new(handler.inventory.clone(), 0)));
// Add the player's inventory slots (27 slots + 9 hotbar)
let player_inventory_arc: Arc<dyn Inventory> = player_inventory.clone();
handler.add_player_slots(&player_inventory_arc);
handler
}
}
impl ScreenHandler for BeaconScreenHandler {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn get_behaviour(&self) -> &ScreenHandlerBehaviour {
&self.behaviour
}
fn get_behaviour_mut(&mut self) -> &mut ScreenHandlerBehaviour {
&mut self.behaviour
}
fn on_closed<'a>(&'a mut self, player: &'a dyn InventoryPlayer) -> ScreenHandlerFuture<'a, ()> {
Box::pin(async move {
self.default_on_closed(player).await;
self.inventory.on_close().await;
})
}
/// Quick move logic specifically for the beacon UI.
///
/// - From beacon payment slot (0): Move to player inventory
/// - From player inventory (1+): Move to beacon payment slot
fn quick_move<'a>(
&'a mut self,
_player: &'a dyn InventoryPlayer,
slot_index: i32,
) -> ItemStackFuture<'a> {
Box::pin(async move {
let mut stack_left = ItemStack::EMPTY.clone();
let slot = self.get_behaviour().slots[slot_index as usize].clone();
if slot.has_stack().await {
let slot_stack_lock = slot.get_stack().await;
let slot_stack_guard = slot_stack_lock.lock().await;
stack_left = slot_stack_guard.clone();
drop(slot_stack_guard);
let mut slot_stack_mut = slot_stack_lock.lock().await;
if slot_index == 0 {
// Move from the single beacon slot to the player inventory (slots 1 to end)
if !self
.insert_item(
&mut slot_stack_mut,
1,
self.get_behaviour().slots.len() as i32,
true,
)
.await
{
return ItemStack::EMPTY.clone();
}
} else {
// Move from player inventory into the beacon payment slot (slot 0)
if !self.insert_item(&mut slot_stack_mut, 0, 1, false).await {
return ItemStack::EMPTY.clone();
}
}
if slot_stack_mut.is_empty() {
drop(slot_stack_mut);
slot.set_stack(ItemStack::EMPTY.clone()).await;
} else {
drop(slot_stack_mut);
slot.mark_dirty().await;
}
}
stack_left
})
}
}

View File

@@ -20,7 +20,7 @@ use std::{any::Any, pin::Pin, sync::Arc};
use pumpkin_data::{fuels::is_fuel, item_stack::ItemStack, screen::WindowType};
use pumpkin_world::{
block::entities::{PropertyDelegate, furnace_like_block_entity::ExperienceContainer},
block::entities::{ExperienceContainer, PropertyDelegate},
inventory::Inventory,
};

View File

@@ -9,9 +9,7 @@
use std::sync::{Arc, atomic::AtomicU8};
use pumpkin_data::{fuels::is_fuel, item::Item};
use pumpkin_world::{
block::entities::furnace_like_block_entity::ExperienceContainer, inventory::Inventory,
};
use pumpkin_world::{block::entities::ExperienceContainer, inventory::Inventory};
use tracing::debug;

View File

@@ -31,6 +31,7 @@
//! [`SyncHandler`]: sync_handler::SyncHandler
pub mod anvil;
pub mod beacon_screen_handler;
pub mod brewing;
pub mod container_click;
pub mod crafting;

View File

@@ -5,7 +5,6 @@ use pumpkin_data::block_state_remap::remap_block_state_for_version;
use pumpkin_data::packet::CURRENT_MC_VERSION;
use pumpkin_data::packet::clientbound::PLAY_LEVEL_CHUNK_WITH_LIGHT;
use pumpkin_macros::java_packet;
use pumpkin_nbt::END_ID;
use pumpkin_util::math::position::get_local_cord;
use pumpkin_util::version::MinecraftVersion;
use pumpkin_world::chunk::format::LightContainer;
@@ -195,23 +194,26 @@ impl ClientPacket for CChunkData<'_> {
let block_entities = self
.0
.block_entities
.pending_block_entities
.lock()
.map_err(|_| WritingError::Message("block_entities lock poisoned".into()))?;
write.write_var_int(&VarInt(block_entities.len() as i32))?;
for block_entity in block_entities.values() {
let pos = block_entity.get_position();
for (pos, nbt) in block_entities.iter() {
let local_xz = ((get_local_cord(pos.0.x) & 0xF) << 4) | (get_local_cord(pos.0.z) & 0xF);
write.write_u8(local_xz as u8)?;
write.write_i16_be(pos.0.y as i16)?;
write.write_var_int(&VarInt(block_entity.get_id() as i32))?;
if let Some(nbt) = block_entity.chunk_data_nbt() {
write.write_nbt(nbt.into())?;
} else {
write.write_u8(END_ID)?;
}
let id = nbt.get_string("id").map_or(0, |id_str| {
let name = id_str.split(':').next_back().unwrap_or(id_str);
pumpkin_data::block_properties::BLOCK_ENTITY_TYPES
.iter()
.position(|&n| n == name)
.unwrap_or(0)
});
write.write_var_int(&VarInt(id as i32))?;
write.write_nbt(nbt.clone().into())?;
}
{

View File

@@ -34,6 +34,7 @@ mod recipe_book_change_settings;
mod recipe_book_seen_recipe;
mod rename_item;
mod select_trade;
mod set_beacon;
mod set_command_block;
mod set_creative_slot;
mod set_held_item;
@@ -78,6 +79,7 @@ pub use recipe_book_change_settings::*;
pub use recipe_book_seen_recipe::*;
pub use rename_item::*;
pub use select_trade::*;
pub use set_beacon::*;
pub use set_command_block::*;
pub use set_creative_slot::*;
pub use set_held_item::*;

View File

@@ -0,0 +1,12 @@
use pumpkin_data::packet::serverbound::PLAY_SET_BEACON;
use pumpkin_macros::java_packet;
use serde::Deserialize;
use crate::codec::var_int::VarInt;
#[derive(Deserialize)]
#[java_packet(PLAY_SET_BEACON)]
pub struct SSetBeacon {
pub primary_effect: Option<VarInt>,
pub secondary_effect: Option<VarInt>,
}

View File

@@ -94,6 +94,49 @@ impl BoundingBox {
}
}
/// Expands this bounding box towards a specific direction.
///
/// If a provided value is negative, it extends the minimum boundary along that axis.
/// If a provided value is positive, it extends the maximum boundary along that axis.
///
/// # Arguments
/// * `x` Amount to expand towards on the X axis.
/// * `y` Amount to expand towards on the Y axis.
/// * `z` Amount to expand towards on the Z axis.
#[must_use]
pub fn expand_towards(&self, x: f64, y: f64, z: f64) -> Self {
let mut min_x = self.min.x;
let mut min_y = self.min.y;
let mut min_z = self.min.z;
let mut max_x = self.max.x;
let mut max_y = self.max.y;
let mut max_z = self.max.z;
if x < 0.0 {
min_x += x;
} else if x > 0.0 {
max_x += x;
}
if y < 0.0 {
min_y += y;
} else if y > 0.0 {
max_y += y;
}
if z < 0.0 {
min_z += z;
} else if z > 0.0 {
max_z += z;
}
Self {
min: Vector3::new(min_x, min_y, min_z),
max: Vector3::new(max_x, max_y, max_z),
}
}
/// Expands this box uniformly along all axes.
///
/// # Arguments

View File

@@ -0,0 +1,11 @@
pub trait PropertyDelegate: Sync + Send {
fn get_property(&self, _index: i32) -> i32;
fn set_property(&self, _index: i32, _value: i32);
fn get_properties_size(&self) -> i32;
}
/// Trait for extracting smelting experience from cooking block entities.
pub trait ExperienceContainer: Send + Sync {
/// Extract and reset accumulated experience, returning the total as an integer
fn extract_experience(&self) -> i32;
}

View File

@@ -1,209 +0,0 @@
use std::pin::Pin;
use std::{any::Any, sync::Arc};
use barrel::BarrelBlockEntity;
use bed::BedBlockEntity;
use brewing_stand::BrewingStandBlockEntity;
use chest::ChestBlockEntity;
use comparator::ComparatorBlockEntity;
use daylight_detector::DaylightDetectorBlockEntity;
use end_portal::EndPortalBlockEntity;
use furnace::FurnaceBlockEntity;
use furnace_like_block_entity::ExperienceContainer;
use piston::PistonBlockEntity;
use pumpkin_data::{Block, block_properties::BLOCK_ENTITY_TYPES};
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::math::position::BlockPos;
use sign::SignBlockEntity;
use trapped_chest::TrappedChestBlockEntity;
use crate::block::entities::bell::BellBlockEntity;
use crate::block::entities::blasting_furnace::BlastingFurnaceBlockEntity;
use crate::block::entities::command_block::CommandBlockEntity;
use crate::block::entities::ender_chest::EnderChestBlockEntity;
use crate::block::entities::hopper::HopperBlockEntity;
use crate::block::entities::jukebox::JukeboxBlockEntity;
use crate::block::entities::lectern::LecternBlockEntity;
use crate::block::entities::mob_spawner::MobSpawnerBlockEntity;
use crate::block::entities::shulker_box::ShulkerBoxBlockEntity;
use crate::block::entities::smoker::SmokerBlockEntity;
use crate::{
BlockStateId, block::entities::chiseled_bookshelf::ChiseledBookshelfBlockEntity,
block::entities::dropper::DropperBlockEntity, inventory::Inventory, world::SimpleWorld,
};
pub mod barrel;
pub mod bed;
pub mod bell;
pub mod blasting_furnace;
pub mod brewing_stand;
pub mod chest;
pub mod chest_like_block_entity;
pub mod chiseled_bookshelf;
pub mod command_block;
pub mod comparator;
pub mod daylight_detector;
pub mod dropper;
pub mod end_portal;
pub mod ender_chest;
pub mod furnace;
pub mod furnace_like_block_entity;
pub mod hopper;
pub mod jukebox;
pub mod lectern;
pub mod mob_spawner;
pub mod piston;
pub mod shulker_box;
pub mod sign;
pub mod smoker;
pub mod trapped_chest;
//TODO: We need a mark_dirty for chests
pub trait BlockEntity: Any + Send + Sync {
fn write_nbt<'a>(
&'a self,
nbt: &'a mut NbtCompound,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>>;
fn from_nbt(nbt: &NbtCompound, position: BlockPos) -> Self
where
Self: Sized;
fn tick<'a>(
&'a self,
_world: &'a Arc<dyn SimpleWorld>,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async {})
}
fn resource_location(&self) -> &'static str;
fn get_position(&self) -> BlockPos;
fn write_internal<'a>(
&'a self,
nbt: &'a mut NbtCompound,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
nbt.put_string("id", self.resource_location().to_string());
let position = self.get_position();
nbt.put_int("x", position.0.x);
nbt.put_int("y", position.0.y);
nbt.put_int("z", position.0.z);
self.write_nbt(nbt).await;
})
}
fn get_id(&self) -> u32 {
pumpkin_data::block_properties::BLOCK_ENTITY_TYPES
.iter()
.position(|block_entity_name| {
*block_entity_name == self.resource_location().split(':').next_back().unwrap()
})
.unwrap() as u32
}
/// Obtain NBT data for sending to the client in [`ChunkData`](crate::chunk::ChunkData)
fn chunk_data_nbt(&self) -> Option<NbtCompound> {
None
}
fn get_inventory(self: Arc<Self>) -> Option<Arc<dyn Inventory>> {
None
}
fn set_block_state(&mut self, _block_state: BlockStateId) {}
fn on_block_replaced<'a>(
self: Arc<Self>,
world: Arc<dyn SimpleWorld>,
position: BlockPos,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>>
where
Self: 'a,
{
Box::pin(async move {
if let Some(inventory) = self.get_inventory() {
// Assuming scatter_inventory is an async method on SimpleWorld
world.scatter_inventory(&position, &inventory).await;
}
})
}
fn is_dirty(&self) -> bool {
false
}
fn clear_dirty(&self) {
// Default implementation does nothing
// Override in implementations that have a dirty flag
}
fn as_any(&self) -> &dyn Any;
fn to_property_delegate(self: Arc<Self>) -> Option<Arc<dyn PropertyDelegate>> {
None
}
fn to_experience_container(self: Arc<Self>) -> Option<Arc<dyn ExperienceContainer>> {
None
}
}
#[must_use]
pub fn block_entity_from_generic<T: BlockEntity>(nbt: &NbtCompound) -> T {
let x = nbt.get_int("x").unwrap();
let y = nbt.get_int("y").unwrap();
let z = nbt.get_int("z").unwrap();
T::from_nbt(nbt, BlockPos::new(x, y, z))
}
#[must_use]
pub fn block_entity_from_nbt(nbt: &NbtCompound) -> Option<Arc<dyn BlockEntity>> {
Some(match nbt.get_string("id").unwrap() {
ChestBlockEntity::ID => Arc::new(block_entity_from_generic::<ChestBlockEntity>(nbt)),
TrappedChestBlockEntity::ID => {
Arc::new(block_entity_from_generic::<TrappedChestBlockEntity>(nbt))
}
EnderChestBlockEntity::ID => {
Arc::new(block_entity_from_generic::<EnderChestBlockEntity>(nbt))
}
JukeboxBlockEntity::ID => Arc::new(block_entity_from_generic::<JukeboxBlockEntity>(nbt)),
SignBlockEntity::ID => Arc::new(block_entity_from_generic::<SignBlockEntity>(nbt)),
BedBlockEntity::ID => Arc::new(block_entity_from_generic::<BedBlockEntity>(nbt)),
ComparatorBlockEntity::ID => {
Arc::new(block_entity_from_generic::<ComparatorBlockEntity>(nbt))
}
BarrelBlockEntity::ID => Arc::new(block_entity_from_generic::<BarrelBlockEntity>(nbt)),
HopperBlockEntity::ID => Arc::new(block_entity_from_generic::<HopperBlockEntity>(nbt)),
MobSpawnerBlockEntity::ID => {
Arc::new(block_entity_from_generic::<MobSpawnerBlockEntity>(nbt))
}
DropperBlockEntity::ID => Arc::new(block_entity_from_generic::<DropperBlockEntity>(nbt)),
ShulkerBoxBlockEntity::ID => {
Arc::new(block_entity_from_generic::<ShulkerBoxBlockEntity>(nbt))
}
PistonBlockEntity::ID => Arc::new(block_entity_from_generic::<PistonBlockEntity>(nbt)),
EndPortalBlockEntity::ID => {
Arc::new(block_entity_from_generic::<EndPortalBlockEntity>(nbt))
}
ChiseledBookshelfBlockEntity::ID => Arc::new(block_entity_from_generic::<
ChiseledBookshelfBlockEntity,
>(nbt)),
FurnaceBlockEntity::ID => Arc::new(block_entity_from_generic::<FurnaceBlockEntity>(nbt)),
CommandBlockEntity::ID => Arc::new(block_entity_from_generic::<CommandBlockEntity>(nbt)),
BlastingFurnaceBlockEntity::ID => {
Arc::new(block_entity_from_generic::<BlastingFurnaceBlockEntity>(nbt))
}
SmokerBlockEntity::ID => Arc::new(block_entity_from_generic::<SmokerBlockEntity>(nbt)),
DaylightDetectorBlockEntity::ID => Arc::new(block_entity_from_generic::<
DaylightDetectorBlockEntity,
>(nbt)),
BrewingStandBlockEntity::ID => {
Arc::new(block_entity_from_generic::<BrewingStandBlockEntity>(nbt))
}
BellBlockEntity::ID => Arc::new(block_entity_from_generic::<BellBlockEntity>(nbt)),
LecternBlockEntity::ID => Arc::new(block_entity_from_generic::<LecternBlockEntity>(nbt)),
_ => return None,
})
}
#[must_use]
pub fn has_block_block_entity(block: &Block) -> bool {
BLOCK_ENTITY_TYPES.contains(&block.name)
}
pub trait PropertyDelegate: Sync + Send {
fn get_property(&self, _index: i32) -> i32;
fn set_property(&self, _index: i32, _value: i32);
fn get_properties_size(&self) -> i32;
}

View File

@@ -1,19 +1,9 @@
use std::{
pin::Pin,
sync::{
Arc,
atomic::{AtomicU16, Ordering},
},
};
use pumpkin_util::math::position::BlockPos;
use crate::{block::entities::BlockEntity, world::SimpleWorld};
use std::sync::atomic::{AtomicU16, Ordering};
#[derive(Debug)]
pub struct ViewerCountTracker {
old: AtomicU16,
current: AtomicU16,
pub old: AtomicU16,
pub current: AtomicU16,
}
impl Default for ViewerCountTracker {
@@ -43,67 +33,4 @@ impl ViewerCountTracker {
pub fn get_viewer_count(&self) -> u16 {
self.current.load(Ordering::Relaxed)
}
pub async fn update_viewer_count<T>(
&self,
entity: &T,
world: &Arc<dyn SimpleWorld>,
position: &BlockPos,
) where
T: BlockEntity + ViewerCountListener + 'static,
{
let current = self.current.load(Ordering::Relaxed);
let old = self.old.swap(current, Ordering::Relaxed);
if old != current {
match (old, current) {
(n, 0) if n > 0 => {
entity.on_container_close(world, position).await;
// TODO: world.emitGameEvent(player, GameEvent.CONTAINER_CLOSE, pos);
// TODO: this.maxBlockInteractionRange = 0.0;
}
(0, n) if n > 0 => {
entity.on_container_open(world, position).await;
// TODO: world.emitGameEvent(player, GameEvent.CONTAINER_OPEN, pos);
// TODO: scheduleBlockTick(world, pos, state);
}
_ => {} // Ignore
}
entity
.on_viewer_count_update(world, position, old, current)
.await;
}
// TODO: Requires players
}
}
pub type ViewerFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
pub trait ViewerCountListener: Send + Sync {
fn on_container_open<'a>(
&'a self,
_world: &'a Arc<dyn SimpleWorld>,
_position: &'a BlockPos,
) -> ViewerFuture<'a, ()> {
Box::pin(async {})
}
fn on_container_close<'a>(
&'a self,
_world: &'a Arc<dyn SimpleWorld>,
_position: &'a BlockPos,
) -> ViewerFuture<'a, ()> {
Box::pin(async {})
}
fn on_viewer_count_update<'a>(
&'a self,
_world: &'a Arc<dyn SimpleWorld>,
_position: &'a BlockPos,
_old: u16,
_new: u16,
) -> ViewerFuture<'a, ()> {
Box::pin(async {})
}
}

View File

@@ -8,7 +8,6 @@ use std::{
};
use bytes::Bytes;
use futures::future::join_all;
use pumpkin_data::{Block, chunk::ChunkStatus, fluid::Fluid};
use pumpkin_nbt::{compound::NbtCompound, nbt_long_array};
use rustc_hash::FxHashMap;
@@ -16,7 +15,6 @@ use tokio::sync::Mutex;
use tracing::debug;
use crate::{
block::entities::block_entity_from_nbt,
chunk::{
ChunkEntityData, ChunkReadingError, ChunkSerializingError,
format::anvil::{SingleChunkDataSerializer, WORLD_DATA_VERSION},
@@ -26,6 +24,7 @@ use crate::{
level::LevelFolder,
tick::{ScheduledTick, scheduler::ChunkTickScheduler},
};
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector2::Vector2;
use serde::{Deserialize, Serialize};
@@ -67,36 +66,11 @@ impl Dirtiable for ChunkData {
#[inline]
fn mark_dirty(&self, flag: bool) {
self.dirty.store(flag, Ordering::Relaxed);
if flag {
return;
}
// When marking chunk as clean, also clear all block entity dirty flags
if let Ok(block_entities) = self.block_entities.lock() {
for block_entity in block_entities.values() {
block_entity.clear_dirty();
}
}
}
#[inline]
fn is_dirty(&self) -> bool {
// Check if chunk itself is dirty
if self.dirty.load(Ordering::Relaxed) {
return true;
}
// Also check if any block entities are dirty (e.g., inventory changes)
if let Ok(block_entities) = self.block_entities.lock() {
for block_entity in block_entities.values() {
if block_entity.is_dirty() {
return true;
}
}
}
false
self.dirty.load(Ordering::Relaxed)
}
}
@@ -180,12 +154,14 @@ impl ChunkData {
dirty: AtomicBool::new(false),
block_ticks: ChunkTickScheduler::from_iter(chunk_data.block_ticks),
fluid_ticks: ChunkTickScheduler::from_iter(chunk_data.fluid_ticks),
block_entities: {
pending_block_entities: {
let mut block_entities = FxHashMap::default();
for nbt in chunk_data.block_entities {
let block_entity = block_entity_from_nbt(&nbt);
if let Some(block_entity) = block_entity {
block_entities.insert(block_entity.get_position(), block_entity);
if let Some(x) = nbt.get_int("x")
&& let Some(y) = nbt.get_int("y")
&& let Some(z) = nbt.get_int("z")
{
block_entities.insert(BlockPos::new(x, y, z), nbt);
}
}
std::sync::Mutex::new(block_entities)
@@ -202,20 +178,11 @@ impl ChunkData {
.light_populated
.load(std::sync::atomic::Ordering::Relaxed);
let entities_to_serialize = {
let entities_guard = self.block_entities.lock().unwrap();
let block_entities_nbt = {
let entities_guard = self.pending_block_entities.lock().unwrap();
entities_guard.values().cloned().collect::<Vec<_>>()
};
let block_entities_nbt = join_all(entities_to_serialize.into_iter().map(
|block_entity| async move {
let mut nbt = NbtCompound::new();
block_entity.write_internal(&mut nbt).await;
nbt
},
))
.await;
fn extract_light_ref(light: Option<&LightContainer>) -> Option<&[u8]> {
match light {
Some(LightContainer::Full(data)) => Some(data.as_ref()),

View File

@@ -1,5 +1,4 @@
use crate::BlockStateId;
use crate::block::entities::BlockEntity;
use crate::chunk::format::LightContainer;
use crate::tick::scheduler::ChunkTickScheduler;
use palette::{BiomePalette, BlockPalette, has_random_ticking_fluid};
@@ -8,12 +7,13 @@ use pumpkin_data::chunk::ChunkStatus;
use pumpkin_data::fluid::Fluid;
use pumpkin_data::tag::Block::MINECRAFT_LEAVES;
use pumpkin_data::{Block, BlockState};
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_nbt::nbt_long_array;
use pumpkin_util::math::position::BlockPos;
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use std::sync::RwLock;
use std::sync::{Arc, atomic::AtomicBool};
use std::sync::atomic::AtomicBool;
use thiserror::Error;
use tokio::sync::Mutex;
@@ -76,7 +76,7 @@ pub struct ChunkData {
pub z: i32,
pub block_ticks: ChunkTickScheduler<&'static Block>,
pub fluid_ticks: ChunkTickScheduler<&'static Fluid>,
pub block_entities: std::sync::Mutex<FxHashMap<BlockPos, Arc<dyn BlockEntity>>>,
pub pending_block_entities: std::sync::Mutex<FxHashMap<BlockPos, NbtCompound>>,
pub light_engine: std::sync::Mutex<ChunkLight>,
pub light_populated: AtomicBool,
pub status: ChunkStatus,

View File

@@ -1,4 +1,3 @@
use crate::block::entities::block_entity_from_nbt;
use crate::chunk::{ChunkData, ChunkLight, ChunkSections};
use crate::generation::biome_coords;
use pumpkin_config::lighting::LightingEngineConfig;
@@ -225,7 +224,7 @@ impl Chunk {
z: 0,
block_ticks: Default::default(),
fluid_ticks: Default::default(),
block_entities: Default::default(),
pending_block_entities: Default::default(),
light_engine: Mutex::new(ChunkLight::default()),
light_populated: AtomicBool::new(false),
status: ChunkStatus::Empty,
@@ -298,11 +297,14 @@ impl Chunk {
&& *lighting_config == LightingEngineConfig::Default;
// Convert pending block entities from structure generation to actual block entities
let mut block_entities = FxHashMap::default();
let mut pending_block_entities = FxHashMap::default();
for nbt in proto_chunk.pending_block_entities {
if let Some(block_entity) = block_entity_from_nbt(&nbt) {
let pos = block_entity.get_position();
block_entities.insert(pos, block_entity);
if let Some(x) = nbt.get_int("x")
&& let Some(y) = nbt.get_int("y")
&& let Some(z) = nbt.get_int("z")
{
pending_block_entities
.insert(pumpkin_util::math::position::BlockPos::new(x, y, z), nbt);
}
}
@@ -316,7 +318,7 @@ impl Chunk {
dirty: AtomicBool::new(true),
block_ticks: Default::default(),
fluid_ticks: Default::default(),
block_entities: Mutex::new(block_entities),
pending_block_entities: Mutex::new(pending_block_entities),
status: proto_chunk.stage.into(),
blending_data: proto_chunk.blending_data.clone(),
};

View File

@@ -1,13 +1,11 @@
use pumpkin_data::{Block, BlockDirection, entity::EntityType};
use pumpkin_data::{Block, BlockDirection};
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::{
math::{position::BlockPos, vector3::Vector3},
random::{RandomGenerator, RandomImpl},
};
use crate::{
block::entities::mob_spawner::MobSpawnerBlockEntity, generation::proto_chunk::GenerationCache,
};
use crate::generation::proto_chunk::GenerationCache;
/// The three mob types that can appear in a dungeon spawner.
///
@@ -148,9 +146,19 @@ impl DungeonFeature {
// TODO: set spawner entity type
let mob = DUNGEON_MOBS[random.next_bounded_i32(DUNGEON_MOBS.len() as i32) as usize];
chunk.set_block_state(&pos.0, Block::SPAWNER.default_state);
let spawner_block_entity = MobSpawnerBlockEntity::new(pos, EntityType::from_name(mob));
let mut entity_nbt = NbtCompound::new();
spawner_block_entity.write_nbt(&mut entity_nbt);
entity_nbt.put_string("id", "minecraft:mob_spawner".to_string());
entity_nbt.put_int("x", pos.0.x);
entity_nbt.put_int("y", pos.0.y);
entity_nbt.put_int("z", pos.0.z);
let mut spawn_entry = NbtCompound::new();
let mut entity_nbt_inner = NbtCompound::new();
entity_nbt_inner.put_string("id", mob.to_string());
spawn_entry.put_compound("entity", entity_nbt_inner);
entity_nbt.put_compound("SpawnData", spawn_entry);
chunk.add_block_entity(&pos.0, entity_nbt);
true

View File

@@ -1,18 +1,12 @@
use pumpkin_data::{
Block,
block_properties::{BlockProperties, OakFenceLikeProperties},
entity::EntityType,
};
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::{
BlockDirection,
math::{block_box::BlockBox, position::BlockPos},
random::RandomGenerator,
};
use pumpkin_util::{BlockDirection, math::block_box::BlockBox, random::RandomGenerator};
use crate::{
ProtoChunk,
block::entities::mob_spawner::MobSpawnerBlockEntity,
generation::structure::{
piece::StructurePieceType,
structures::{
@@ -147,10 +141,18 @@ impl StructurePieceBase for BridgePlatformPiece {
spawner_pos.z,
Block::SPAWNER.default_state,
);
let spawner_block_entity =
MobSpawnerBlockEntity::new(BlockPos(spawner_pos), Some(&EntityType::BLAZE));
let mut entity_nbt = NbtCompound::new();
spawner_block_entity.write_nbt(&mut entity_nbt);
entity_nbt.put_string("id", "minecraft:mob_spawner".to_string());
entity_nbt.put_int("x", spawner_pos.x);
entity_nbt.put_int("y", spawner_pos.y);
entity_nbt.put_int("z", spawner_pos.z);
let mut spawn_entry = NbtCompound::new();
let mut entity_nbt_inner = NbtCompound::new();
entity_nbt_inner.put_string("id", "minecraft:blaze".to_string());
spawn_entry.put_compound("entity", entity_nbt_inner);
entity_nbt.put_compound("SpawnData", spawn_entry);
chunk.add_block_entity(entity_nbt);
}
}

View File

@@ -4,18 +4,16 @@ use pumpkin_data::{
BlockProperties, EndPortalFrameLikeProperties, HorizontalFacing, OakFenceLikeProperties,
OakStairsLikeProperties,
},
entity::EntityType,
};
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::{
BlockDirection,
math::{block_box::BlockBox, position::BlockPos},
math::block_box::BlockBox,
random::{RandomGenerator, RandomImpl},
};
use crate::{
ProtoChunk,
block::entities::mob_spawner::MobSpawnerBlockEntity,
generation::structure::{
piece::StructurePieceType,
structures::{
@@ -358,10 +356,18 @@ impl StructurePieceBase for PortalRoomPiece {
self.spawner_placed = true;
let spawner = Block::SPAWNER.default_state;
inner.add_block(chunk, spawner, 5, 3, 6, &box_limit);
let spawner_block_entity =
MobSpawnerBlockEntity::new(BlockPos(pos), Some(&EntityType::SILVERFISH));
let mut entity_nbt = NbtCompound::new();
spawner_block_entity.write_nbt(&mut entity_nbt);
entity_nbt.put_string("id", "minecraft:mob_spawner".to_string());
entity_nbt.put_int("x", pos.x);
entity_nbt.put_int("y", pos.y);
entity_nbt.put_int("z", pos.z);
let mut spawn_entry = NbtCompound::new();
let mut entity_nbt_inner = NbtCompound::new();
entity_nbt_inner.put_string("id", "minecraft:silverfish".to_string());
spawn_entry.put_compound("entity", entity_nbt_inner);
entity_nbt.put_compound("SpawnData", spawn_entry);
chunk.add_block_entity(entity_nbt);
}
}

View File

@@ -5,7 +5,7 @@ use crate::generation::generator::VanillaGenerator;
use crate::lighting::DynamicLightEngine;
use crate::{
BlockStateId,
block::{RawBlockState, entities::BlockEntity},
block::RawBlockState,
chunk::{
ChunkData, ChunkEntityData, ChunkReadingError,
format::anvil::AnvilChunkFile,
@@ -110,7 +110,6 @@ pub struct TickData {
pub block_ticks: Vec<OrderedTick<&'static Block>>,
pub fluid_ticks: Vec<OrderedTick<&'static Fluid>>,
pub random_ticks: Vec<RandomTickSample>,
pub block_entities: Vec<Arc<dyn BlockEntity>>,
}
#[derive(Clone, Copy)]
@@ -446,7 +445,6 @@ impl Level {
block_ticks: Vec::new(),
fluid_ticks: Vec::new(),
random_ticks: Vec::with_capacity(active_chunks.len() * 3),
block_entities: Vec::new(),
};
// 1. Process active chunks (random ticks, block entities)
@@ -457,10 +455,6 @@ impl Level {
let chunk_z_base = chunk.z * 16;
let section_count = chunk.section.count;
ticks
.block_entities
.extend(chunk.block_entities.lock().unwrap().values().cloned());
// Use the bitmask to skip sections
let mask = chunk.section.randomly_ticking_mask.load(Ordering::Relaxed);
if mask != 0 {

View File

@@ -1,17 +1,9 @@
use std::pin::Pin;
use std::sync::Arc;
use crate::block::entities::BlockEntity;
use crate::{BlockStateId, inventory::Inventory, level::Level};
use crate::BlockStateId;
use bitflags::bitflags;
use pumpkin_data::dimension::Dimension;
use pumpkin_data::entity::EntityType;
use pumpkin_data::sound::{Sound, SoundCategory};
use pumpkin_data::world::WorldEvent;
use pumpkin_data::{Block, BlockDirection, BlockState};
use pumpkin_util::math::boundingbox::BoundingBox;
use pumpkin_data::{Block, BlockState};
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3;
use thiserror::Error;
bitflags! {
@@ -63,97 +55,6 @@ impl std::fmt::Display for GetBlockError {
pub type WorldFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
pub trait SimpleWorld: BlockAccessor + Send + Sync {
fn set_block_state(
self: Arc<Self>,
position: &BlockPos,
block_state_id: BlockStateId,
flags: BlockFlags,
) -> WorldFuture<'_, BlockStateId>;
fn update_neighbor<'a>(
self: Arc<Self>,
neighbor_block_pos: &'a BlockPos,
source_block: &'a pumpkin_data::Block,
) -> WorldFuture<'a, ()>;
fn update_neighbors(
self: Arc<Self>,
block_pos: &BlockPos,
except: Option<BlockDirection>,
) -> WorldFuture<'_, ()>;
fn is_space_empty(&self, bounding_box: BoundingBox) -> WorldFuture<'_, bool>;
fn spawn_from_type(
self: Arc<Self>,
entity_type: &'static EntityType,
position: Vector3<f64>,
) -> WorldFuture<'static, ()>;
fn add_synced_block_event(&self, pos: BlockPos, r#type: u8, data: u8) -> WorldFuture<'_, ()>;
fn sync_world_event(
&self,
world_event: WorldEvent,
position: BlockPos,
data: i32,
) -> WorldFuture<'_, ()>;
fn remove_block_entity<'a>(&'a self, block_pos: &'a BlockPos) -> WorldFuture<'a, ()>;
fn get_block_entity<'a>(
&'a self,
block_pos: &'a BlockPos,
) -> WorldFuture<'a, Option<Arc<dyn BlockEntity>>>;
fn get_world_age(&self) -> WorldFuture<'_, i64>;
fn get_time_of_day(&self) -> WorldFuture<'_, i64>;
fn get_level(&self) -> WorldFuture<'_, &Arc<Level>>;
fn get_dimension(&self) -> WorldFuture<'_, &Dimension>;
fn play_sound<'a>(
&'a self,
sound: Sound,
category: SoundCategory,
position: &'a Vector3<f64>,
) -> WorldFuture<'a, ()>;
fn play_sound_fine<'a>(
&'a self,
sound: Sound,
category: SoundCategory,
position: &'a Vector3<f64>,
volume: f32,
pitch: f32,
) -> WorldFuture<'a, ()>;
/* ItemScatterer */
fn scatter_inventory<'a>(
self: Arc<Self>,
position: &'a BlockPos,
inventory: &'a Arc<dyn Inventory>,
) -> WorldFuture<'a, ()>;
/// Spawn experience orbs at the given position with the specified amount
fn spawn_experience_orbs(
self: Arc<Self>,
position: Vector3<f64>,
amount: u32,
) -> WorldFuture<'static, ()>;
/// `Block.updateFromNeighbourShapes`: updates a block state by calling
/// `get_state_for_neighbor_update` on itself for each of the 6 directions.
fn update_from_neighbor_shapes(
self: Arc<Self>,
block_state_id: BlockStateId,
position: &BlockPos,
) -> WorldFuture<'_, BlockStateId>;
}
pub trait BlockRegistryExt: Send + Sync {
fn can_place_at(
&self,

View File

@@ -92,7 +92,7 @@ rustc-hash.workspace = true
tokio-util = { workspace = true, features = ["rt"] }
# System details & Rustc version
sysinfo = "0.38"
sysinfo = "0.39"
rustc_version_runtime = "0.3"
flate2.workspace = true

View File

@@ -6,6 +6,7 @@ use crate::block::{
{BlockBehaviour, NormalUseArgs},
};
use crate::block::entities::barrel::BarrelBlockEntity;
use pumpkin_data::block_properties::{BarrelLikeProperties, BlockProperties};
use pumpkin_data::translation;
use pumpkin_inventory::generic_container_screen_handler::create_generic_9x3;
@@ -16,7 +17,6 @@ use pumpkin_inventory::screen_handler::{
use pumpkin_macros::pumpkin_block;
use pumpkin_util::text::TextComponent;
use pumpkin_world::BlockStateId;
use pumpkin_world::block::entities::barrel::BarrelBlockEntity;
use pumpkin_world::inventory::Inventory;
use tokio::sync::Mutex;

View File

@@ -0,0 +1,69 @@
use std::sync::Arc;
use tokio::sync::Mutex;
use crate::block::entities::BlockEntity;
use pumpkin_data::translation;
use pumpkin_inventory::player::player_inventory::PlayerInventory;
use pumpkin_inventory::screen_handler::{
BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::text::TextComponent;
use pumpkin_world::inventory::Inventory;
use crate::block::registry::BlockActionResult;
use crate::block::{BlockBehaviour, BlockFuture, NormalUseArgs};
// Create the factory just like ChestScreenFactory
struct BeaconScreenFactory(Arc<dyn Inventory>);
impl ScreenHandlerFactory for BeaconScreenFactory {
fn create_screen_handler<'a>(
&'a self,
sync_id: u8,
player_inventory: &'a Arc<PlayerInventory>,
_player: &'a dyn InventoryPlayer,
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
Box::pin(async move {
// Assumes create_beacon_handler exists in your generic_container_screen_handler equivalent
use pumpkin_inventory::beacon_screen_handler::create_beacon_handler;
let concrete_handler =
create_beacon_handler(sync_id, player_inventory, self.0.clone()).await;
let concrete_arc = Arc::new(Mutex::new(concrete_handler));
Some(concrete_arc as SharedScreenHandler)
})
}
fn get_display_name(&self) -> TextComponent {
TextComponent::translate_cross(
translation::java::CONTAINER_BEACON,
translation::bedrock::CONTAINER_BEACON,
&[],
)
}
}
#[pumpkin_block("minecraft:beacon")]
pub struct BeaconBlock;
impl BlockBehaviour for BeaconBlock {
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
let block_entity = args.world.get_block_entity(args.position).await;
// Extract the inventory from the entity
let Some(inventory) = block_entity.and_then(BlockEntity::get_inventory) else {
return BlockActionResult::Fail;
};
// Open the screen using the factory
args.player
.open_handled_screen(&BeaconScreenFactory(inventory), Some(*args.position))
.await;
BlockActionResult::Success
})
}
}

View File

@@ -1,5 +1,6 @@
use std::sync::Arc;
use crate::block::entities::bed::BedBlockEntity;
use pumpkin_data::Block;
use pumpkin_data::block_properties::BedPart;
use pumpkin_data::block_properties::BlockProperties;
@@ -11,7 +12,6 @@ use pumpkin_util::GameMode;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::text::TextComponent;
use pumpkin_world::BlockStateId;
use pumpkin_world::block::entities::bed::BedBlockEntity;
use pumpkin_world::world::BlockFlags;
use crate::block::BlockFuture;

View File

@@ -1,5 +1,9 @@
use std::sync::Arc;
use crate::block::entities::{
PropertyDelegate, blasting_furnace::BlastingFurnaceBlockEntity,
furnace_like_block_entity::ExperienceContainer,
};
use pumpkin_data::{
block_properties::{BlockProperties, FurnaceLikeProperties},
screen::WindowType,
@@ -12,14 +16,7 @@ use pumpkin_inventory::{
};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::text::TextComponent;
use pumpkin_world::{
BlockStateId,
block::entities::{
PropertyDelegate, blasting_furnace::BlastingFurnaceBlockEntity,
furnace_like_block_entity::ExperienceContainer,
},
inventory::Inventory,
};
use pumpkin_world::{BlockStateId, inventory::Inventory};
use tokio::sync::Mutex;
use crate::{

View File

@@ -6,17 +6,17 @@ use crate::block::{
{BlockBehaviour, NormalUseArgs},
};
use crate::block::entities::brewing_stand::BrewingStandBlockEntity;
use pumpkin_data::translation;
use pumpkin_inventory::player::player_inventory::PlayerInventory;
use pumpkin_inventory::screen_handler::{BoxFuture, ScreenHandlerFactory, SharedScreenHandler};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::text::TextComponent;
use pumpkin_world::block::entities::brewing_stand::BrewingStandBlockEntity;
use pumpkin_world::inventory::Inventory;
struct BrewingScreenFactory(
Arc<dyn Inventory>,
Arc<dyn pumpkin_world::block::entities::PropertyDelegate>,
Arc<dyn crate::block::entities::PropertyDelegate>,
);
impl ScreenHandlerFactory for BrewingScreenFactory {

View File

@@ -1,5 +1,7 @@
use std::sync::Arc;
use crate::block::entities::BlockEntity;
use crate::block::entities::chest::ChestBlockEntity;
use futures::future::join;
use pumpkin_data::block_properties::{
BlockProperties, ChestLikeProperties, ChestType, HorizontalFacing,
@@ -16,8 +18,6 @@ use pumpkin_macros::{pumpkin_block, pumpkin_block_from_tag};
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::text::TextComponent;
use pumpkin_world::BlockStateId;
use pumpkin_world::block::entities::BlockEntity;
use pumpkin_world::block::entities::chest::ChestBlockEntity;
use pumpkin_world::inventory::Inventory;
use pumpkin_world::world::BlockFlags;
use tokio::sync::Mutex;
@@ -445,7 +445,7 @@ impl BlockBehaviour for TrappedChestBlock {
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
use pumpkin_world::block::entities::trapped_chest::TrappedChestBlockEntity;
use crate::block::entities::trapped_chest::TrappedChestBlockEntity;
Box::pin(placed_chest_impl(args, TrappedChestBlockEntity::new))
}
@@ -469,7 +469,7 @@ impl BlockBehaviour for TrappedChestBlock {
args: GetRedstonePowerArgs<'a>,
) -> BlockFuture<'a, u8> {
Box::pin(async move {
use pumpkin_world::block::entities::trapped_chest::TrappedChestBlockEntity;
use crate::block::entities::trapped_chest::TrappedChestBlockEntity;
// Get viewer count from this chest
let viewer_count = if let Some(block_entity) =

View File

@@ -2,6 +2,7 @@ use std::sync::{Arc, atomic::Ordering};
use pumpkin_macros::pumpkin_block;
use crate::block::entities::chiseled_bookshelf::ChiseledBookshelfBlockEntity;
use crate::{
block::{
BlockBehaviour, BlockFuture, BlockHitResult, GetComparatorOutputArgs, NormalUseArgs,
@@ -20,10 +21,7 @@ use pumpkin_data::{
};
use pumpkin_inventory::screen_handler::InventoryPlayer;
use pumpkin_util::math::{position::BlockPos, vector2::Vector2};
use pumpkin_world::{
BlockStateId, block::entities::chiseled_bookshelf::ChiseledBookshelfBlockEntity,
inventory::Inventory,
};
use pumpkin_world::{BlockStateId, inventory::Inventory};
use tokio::sync::Mutex;
#[pumpkin_block("minecraft:chiseled_bookshelf")]

View File

@@ -1,6 +1,7 @@
use std::sync::{Arc, atomic::Ordering};
use super::redstone::block_receives_redstone_power;
use crate::block::entities::{BlockEntity, command_block::CommandBlockEntity};
use crate::command::CommandSender;
use crate::{
block::{
@@ -16,11 +17,7 @@ use pumpkin_data::{
block_properties::{BlockProperties, CommandBlockLikeProperties, Facing},
};
use pumpkin_util::{GameMode, PermissionLvl, math::position::BlockPos};
use pumpkin_world::{
BlockStateId,
block::entities::{BlockEntity, command_block::CommandBlockEntity},
tick::TickPriority,
};
use pumpkin_world::{BlockStateId, tick::TickPriority};
use tracing::warn;
pub struct CommandBlock;

View File

@@ -4,9 +4,9 @@ use crate::block::BlockBehaviour;
use crate::block::BlockFuture;
use crate::block::OnEntityCollisionArgs;
use crate::block::PlacedArgs;
use crate::block::entities::end_portal::EndPortalBlockEntity;
use pumpkin_data::dimension::Dimension;
use pumpkin_macros::pumpkin_block;
use pumpkin_world::block::entities::end_portal::EndPortalBlockEntity;
#[pumpkin_block("minecraft:end_portal")]
pub struct EndPortalBlock;

View File

@@ -1,5 +1,6 @@
use std::sync::Arc;
use crate::block::entities::ender_chest::EnderChestBlockEntity;
use crate::block::{
BlockBehaviour, BlockFuture, NormalUseArgs, OnPlaceArgs, OnSyncedBlockEventArgs, PlacedArgs,
registry::BlockActionResult,
@@ -15,9 +16,7 @@ use pumpkin_inventory::{
use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::text::TextComponent;
use pumpkin_world::{
BlockStateId, block::entities::ender_chest::EnderChestBlockEntity, inventory::Inventory,
};
use pumpkin_world::{BlockStateId, inventory::Inventory};
use tokio::sync::Mutex;
struct EnderChestScreenFactory(Arc<dyn Inventory>);

View File

@@ -1,5 +1,8 @@
use std::sync::Arc;
use crate::block::entities::{
PropertyDelegate, furnace::FurnaceBlockEntity, furnace_like_block_entity::ExperienceContainer,
};
use pumpkin_data::{
block_properties::{BlockProperties, FurnaceLikeProperties},
screen::WindowType,
@@ -12,14 +15,7 @@ use pumpkin_inventory::{
};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::text::TextComponent;
use pumpkin_world::{
BlockStateId,
block::entities::{
PropertyDelegate, furnace::FurnaceBlockEntity,
furnace_like_block_entity::ExperienceContainer,
},
inventory::Inventory,
};
use pumpkin_world::{BlockStateId, inventory::Inventory};
use tokio::sync::Mutex;
use crate::{

View File

@@ -8,6 +8,7 @@ use crate::block::{
};
use crate::world::World;
use crate::block::entities::hopper::HopperBlockEntity;
use pumpkin_data::block_properties::{BlockProperties, FacingHopper};
use pumpkin_data::{Block, BlockDirection, translation};
use pumpkin_inventory::generic_container_screen_handler::create_hopper;
@@ -19,7 +20,6 @@ use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::text::TextComponent;
use pumpkin_world::BlockStateId;
use pumpkin_world::block::entities::hopper::HopperBlockEntity;
use pumpkin_world::inventory::Inventory;
use pumpkin_world::world::BlockFlags;
use tokio::sync::Mutex;

View File

@@ -1,5 +1,6 @@
use std::sync::Arc;
use crate::block::entities::jukebox::JukeboxBlockEntity;
use crate::block::registry::BlockActionResult;
use crate::block::{
BlockBehaviour, BlockFuture, BrokenArgs, EmitsRedstonePowerArgs, GetComparatorOutputArgs,
@@ -19,7 +20,6 @@ use pumpkin_data::{
use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_world::block::entities::jukebox::JukeboxBlockEntity;
use pumpkin_world::world::BlockFlags;
use rand::{RngExt, rng};

View File

@@ -1,5 +1,6 @@
use std::sync::Arc;
use crate::block::entities::lectern::LecternBlockEntity;
use crate::block::registry::BlockActionResult;
use crate::block::{
BlockBehaviour, BlockFuture, BrokenArgs, NormalUseArgs, OnPlaceArgs, PlacedArgs,
@@ -15,7 +16,6 @@ use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_world::BlockStateId;
use pumpkin_world::block::entities::lectern::LecternBlockEntity;
use pumpkin_world::inventory::Inventory;
use pumpkin_world::world::BlockFlags;

View File

@@ -97,3 +97,5 @@ pub mod tnt;
// Misc / abstract
pub mod abstract_wall_mounting;
pub mod beacon;

View File

@@ -1,5 +1,6 @@
use std::sync::Arc;
use crate::block::entities::{has_block_block_entity, piston::PistonBlockEntity};
use pumpkin_data::{
Block, BlockDirection, BlockState, FacingExt,
block_properties::{
@@ -9,11 +10,7 @@ use pumpkin_data::{
sound::{Sound, SoundCategory},
};
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::{
BlockStateId,
block::entities::{has_block_block_entity, piston::PistonBlockEntity},
world::BlockFlags,
};
use pumpkin_world::{BlockStateId, world::BlockFlags};
use rand::RngExt;
use rustc_hash::FxHashMap;

View File

@@ -1,6 +1,7 @@
use crate::Arc;
use crate::block::blocks::abstract_wall_mounting::WallMountedBlock;
use crate::block::blocks::redstone::block_receives_redstone_power;
use crate::block::entities::bell::BellBlockEntity;
use crate::block::registry::BlockActionResult;
use crate::block::{
BlockBehaviour, BlockFuture, BlockHitResult, BrokenArgs, CanPlaceAtArgs, NormalUseArgs,
@@ -18,7 +19,6 @@ use pumpkin_data::{HorizontalFacingExt, tag};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::BlockStateId;
use pumpkin_world::block::entities::bell::BellBlockEntity;
use pumpkin_world::world::BlockFlags;
async fn ring_bell(

View File

@@ -1,5 +1,6 @@
use std::sync::{Arc, atomic::Ordering};
use crate::block::entities::comparator::ComparatorBlockEntity;
use pumpkin_data::{
Block, BlockDirection, BlockState,
block_properties::{
@@ -9,10 +10,7 @@ use pumpkin_data::{
};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::{boundingbox::BoundingBox, position::BlockPos};
use pumpkin_world::{
BlockStateId, block::entities::comparator::ComparatorBlockEntity, tick::TickPriority,
world::BlockFlags,
};
use pumpkin_world::{BlockStateId, tick::TickPriority, world::BlockFlags};
use crate::{
block::{

View File

@@ -1,9 +1,9 @@
use std::sync::Arc;
use crate::block::entities::daylight_detector::DaylightDetectorBlockEntity;
use pumpkin_data::{Block, block_properties::BlockProperties};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::block::entities::daylight_detector::DaylightDetectorBlockEntity;
use pumpkin_world::world::BlockFlags;
use crate::block::{

View File

@@ -7,6 +7,8 @@ use crate::block::{
use crate::entity::Entity;
use crate::entity::item::ItemEntity;
use crate::block::entities::dropper::DropperBlockEntity;
use crate::block::entities::hopper::HopperBlockEntity;
use pumpkin_data::block_properties::{BlockProperties, Facing};
use pumpkin_data::entity::EntityType;
use pumpkin_data::world::WorldEvent;
@@ -20,8 +22,6 @@ use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::text::TextComponent;
use pumpkin_world::BlockStateId;
use pumpkin_world::block::entities::dropper::DropperBlockEntity;
use pumpkin_world::block::entities::hopper::HopperBlockEntity;
use pumpkin_world::inventory::Inventory;
use pumpkin_world::tick::TickPriority;
use pumpkin_world::world::BlockFlags;

View File

@@ -6,6 +6,7 @@ use crate::block::{
{BlockBehaviour, NormalUseArgs},
};
use crate::block::entities::shulker_box::ShulkerBoxBlockEntity;
use pumpkin_data::block_properties::BlockProperties;
use pumpkin_data::tag::{self};
use pumpkin_data::translation;
@@ -16,7 +17,6 @@ use pumpkin_inventory::screen_handler::{
};
use pumpkin_util::text::TextComponent;
use pumpkin_world::BlockStateId;
use pumpkin_world::block::entities::shulker_box::ShulkerBoxBlockEntity;
use pumpkin_world::inventory::Inventory;
use tokio::sync::Mutex;

View File

@@ -1,6 +1,7 @@
use std::sync::Arc;
use std::sync::atomic::Ordering;
use crate::block::entities::sign::SignBlockEntity;
use pumpkin_data::Block;
use pumpkin_data::BlockDirection;
use pumpkin_data::block_properties::EnumVariants;
@@ -10,7 +11,6 @@ use pumpkin_macros::pumpkin_block_from_tag;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_world::BlockStateId;
use pumpkin_world::block::entities::sign::SignBlockEntity;
use uuid::Uuid;
use crate::block::BlockBehaviour;

View File

@@ -1,5 +1,8 @@
use std::sync::Arc;
use crate::block::entities::{
PropertyDelegate, furnace_like_block_entity::ExperienceContainer, smoker::SmokerBlockEntity,
};
use pumpkin_data::{
block_properties::{BlockProperties, FurnaceLikeProperties},
screen::WindowType,
@@ -12,13 +15,7 @@ use pumpkin_inventory::{
};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::text::TextComponent;
use pumpkin_world::{
BlockStateId,
block::entities::{
PropertyDelegate, furnace_like_block_entity::ExperienceContainer, smoker::SmokerBlockEntity,
},
inventory::Inventory,
};
use pumpkin_world::{BlockStateId, inventory::Inventory};
use tokio::sync::Mutex;
use crate::{

View File

@@ -1,7 +1,7 @@
use std::sync::Arc;
use crate::block::entities::mob_spawner::MobSpawnerBlockEntity;
use pumpkin_macros::pumpkin_block;
use pumpkin_world::block::entities::mob_spawner::MobSpawnerBlockEntity;
use crate::block::{BlockBehaviour, BlockFuture, PlacedArgs};

View File

@@ -17,12 +17,14 @@ use std::{
};
use tokio::sync::Mutex;
use crate::block::viewer::{ViewerCountListener, ViewerCountTracker, ViewerFuture};
use crate::inventory::InventoryFuture;
use crate::inventory::{
use crate::block::viewer::{
ViewerCountListener, ViewerCountTracker, ViewerCountTrackerExt, ViewerFuture,
};
use crate::world::{BlockFlags, World};
use pumpkin_world::inventory::InventoryFuture;
use pumpkin_world::inventory::{
split_stack, {Clearable, Inventory},
};
use crate::world::{BlockFlags, SimpleWorld};
use super::BlockEntity;
@@ -67,10 +69,7 @@ impl BlockEntity for BarrelBlockEntity {
self.write_inventory_nbt(nbt, true)
}
fn tick<'a>(
&'a self,
world: &'a Arc<dyn SimpleWorld>,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
fn tick<'a>(&'a self, world: &'a Arc<World>) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
self.viewers
.update_viewer_count::<Self>(self, world, &self.position)
@@ -98,7 +97,7 @@ impl BlockEntity for BarrelBlockEntity {
impl ViewerCountListener for BarrelBlockEntity {
fn on_container_open<'a>(
&'a self,
world: &'a Arc<dyn SimpleWorld>,
world: &'a Arc<World>,
_position: &'a BlockPos,
) -> ViewerFuture<'a, ()> {
Box::pin(async move {
@@ -109,7 +108,7 @@ impl ViewerCountListener for BarrelBlockEntity {
fn on_container_close<'a>(
&'a self,
world: &'a Arc<dyn SimpleWorld>,
world: &'a Arc<World>,
_position: &'a BlockPos,
) -> ViewerFuture<'a, ()> {
Box::pin(async move {
@@ -133,7 +132,7 @@ impl BarrelBlockEntity {
}
}
async fn set_open(&self, world: &Arc<dyn SimpleWorld>, open: bool) {
async fn set_open(&self, world: &Arc<World>, open: bool) {
let state = world.get_block_state(&self.position).await;
let mut properties = BarrelLikeProperties::from_state_id(state.id, &Block::BARREL);
@@ -149,7 +148,7 @@ impl BarrelBlockEntity {
.await;
}
async fn play_sound(&self, world: &Arc<dyn SimpleWorld>, sound: Sound) {
async fn play_sound(&self, world: &Arc<World>, sound: Sound) {
let mut rng = Xoroshiro::from_seed(get_seed());
let state = world.get_block_state(&self.position).await;

View File

@@ -0,0 +1,345 @@
use futures::Future;
use pumpkin_data::data_component_impl::IDSetContent;
use pumpkin_data::tag::Taggable;
use std::any::Any;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
use pumpkin_data::effect::StatusEffect;
use pumpkin_data::item_stack::ItemStack;
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::math::boundingbox::BoundingBox;
use pumpkin_util::math::position::BlockPos;
use tokio::sync::Mutex;
use crate::block::entities::BlockEntity;
use crate::world::World;
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture};
pub struct BeaconBlockEntity {
pub position: BlockPos,
pub primary_effect: AtomicI32,
pub secondary_effect: AtomicI32,
pub levels: AtomicI32,
pub dirty: AtomicBool,
pub payment: Arc<Mutex<ItemStack>>,
// Vanilla Parity Fields
pub custom_name: Mutex<Option<String>>,
pub lock_key: Mutex<Option<String>>,
pub last_check_y: AtomicI32,
}
impl BeaconBlockEntity {
pub const ID: &'static str = "minecraft:beacon";
// ContainerData Property Constants
pub const DATA_LEVELS: usize = 0;
pub const DATA_PRIMARY: usize = 1;
pub const DATA_SECONDARY: usize = 2;
pub const NUM_DATA_VALUES: usize = 3;
#[must_use]
pub fn new(position: BlockPos) -> Self {
Self {
position,
primary_effect: AtomicI32::new(-1),
secondary_effect: AtomicI32::new(-1),
levels: AtomicI32::new(0),
dirty: AtomicBool::new(false),
payment: Arc::new(Mutex::new(ItemStack::EMPTY.clone())),
custom_name: Mutex::new(None),
lock_key: Mutex::new(None),
last_check_y: AtomicI32::new(position.0.y - 1),
}
}
/// Replicates the Java `ContainerData` used to sync values to the `BeaconMenu`
pub fn get_data(&self, id: usize) -> i32 {
match id {
Self::DATA_LEVELS => self.levels.load(Ordering::Relaxed),
Self::DATA_PRIMARY => self.primary_effect.load(Ordering::Relaxed),
Self::DATA_SECONDARY => self.secondary_effect.load(Ordering::Relaxed),
_ => 0,
}
}
pub fn set_data(&self, id: usize, value: i32) {
match id {
Self::DATA_LEVELS => self.levels.store(value, Ordering::Relaxed),
Self::DATA_PRIMARY => self.primary_effect.store(value, Ordering::Relaxed),
Self::DATA_SECONDARY => self.secondary_effect.store(value, Ordering::Relaxed),
_ => {}
}
self.mark_dirty();
}
/// Replicates Java's `updateBase` logic
async fn update_base(&self, world: &Arc<World>) -> i32 {
let mut levels = 0;
let x = self.position.0.x;
let y = self.position.0.y;
let z = self.position.0.z;
for step in 1..=4 {
let ly = y - step;
if ly < world.dimension.min_y {
break;
}
let mut is_ok = true;
for lx in (x - step)..=(x + step) {
for lz in (z - step)..=(z + step) {
let pos = BlockPos::new(lx, ly, lz);
let block = world.get_block(&pos).await;
if !block.has_tag(&pumpkin_data::tag::Block::MINECRAFT_BEACON_BASE_BLOCKS) {
is_ok = false;
break;
}
}
if !is_ok {
break;
}
}
if !is_ok {
break;
}
levels = step;
}
levels
}
/// Replicates Java's `applyEffects` bounding box mapping and duration mapping
async fn apply_effects(&self, world: &Arc<World>, levels: i32) {
if levels <= 0 {
return;
}
let primary_id = self.primary_effect.load(Ordering::Relaxed);
let secondary_id = self.secondary_effect.load(Ordering::Relaxed);
if primary_id <= 0 {
return;
}
let primary_effect = StatusEffect::from_id(primary_id as u16);
let secondary_effect = if secondary_id > 0 {
StatusEffect::from_id(secondary_id as u16)
} else {
None
};
// Vanilla: expandTowards(0.0, level.getHeight(), 0.0) -> Reaches across the entire Y axis
let range = (levels * 10 + 10) as f64;
let pos = self.position.0.to_f64();
// Use the dimension height for vanilla parity (usually 384.0 in modern versions)
let world_height = world.dimension.height as f64;
let bounding_box = BoundingBox::new(pos, pos.add_raw(1.0, 1.0, 1.0))
.expand(range, range, range)
.expand_towards(0.0, world_height, 0.0);
let players = world.get_players_at_box(&bounding_box);
let duration_ticks = (9 + levels * 2) * 20;
let base_amp = i32::from(levels >= 4 && primary_id == secondary_id);
for player in players {
if let Some(effect) = primary_effect {
player
.add_effect(pumpkin_data::potion::Effect {
effect_type: effect,
duration: duration_ticks,
amplifier: base_amp as u8,
ambient: true,
show_particles: true,
show_icon: true,
blend: false,
})
.await;
}
if levels >= 4
&& primary_id != secondary_id
&& let Some(effect) = secondary_effect
{
player
.add_effect(pumpkin_data::potion::Effect {
effect_type: effect,
duration: duration_ticks,
amplifier: 0,
ambient: true,
show_particles: true,
show_icon: true,
blend: false,
})
.await;
}
}
}
}
impl BlockEntity for BeaconBlockEntity {
fn resource_location(&self) -> &'static str {
Self::ID
}
fn get_position(&self) -> BlockPos {
self.position
}
fn from_nbt(nbt: &NbtCompound, position: BlockPos) -> Self
where
Self: Sized,
{
// Aligning to strict vanilla NBT tags
let primary = nbt.get_int("primary_effect").unwrap_or(-1);
let secondary = nbt.get_int("secondary_effect").unwrap_or(-1);
let levels = nbt.get_int("Levels").unwrap_or(0); // Vanilla uses capital L
let custom_name = nbt
.get_string("CustomName")
.map(std::string::ToString::to_string);
let lock_key = nbt.get_string("Lock").map(std::string::ToString::to_string);
Self {
position,
primary_effect: AtomicI32::new(primary),
secondary_effect: AtomicI32::new(secondary),
levels: AtomicI32::new(levels),
dirty: AtomicBool::new(false),
payment: Arc::new(Mutex::new(ItemStack::EMPTY.clone())),
custom_name: Mutex::new(custom_name),
lock_key: Mutex::new(lock_key),
last_check_y: AtomicI32::new(position.0.y - 1),
}
}
fn write_nbt<'a>(
&'a self,
nbt: &'a mut NbtCompound,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
nbt.put_int(
"primary_effect",
self.primary_effect.load(Ordering::Relaxed),
);
nbt.put_int(
"secondary_effect",
self.secondary_effect.load(Ordering::Relaxed),
);
nbt.put_int("Levels", self.levels.load(Ordering::Relaxed));
if let Some(name) = &*self.custom_name.lock().await {
nbt.put_string("CustomName", name.clone());
}
if let Some(lock) = &*self.lock_key.lock().await {
nbt.put_string("Lock", lock.clone());
}
})
}
fn tick<'a>(&'a self, world: &'a Arc<World>) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
// Check properties every 80 ticks matching Java
if world.get_time_of_day().await % 80 == 0 {
let levels = self.update_base(world).await;
self.levels.store(levels, Ordering::Relaxed);
// TODO: Beam Section validation (scanning upward to heightmap to check for sky visibility)
// is typically checked here before applying effects in Vanilla.
if levels > 0 {
self.apply_effects(world, levels).await;
}
}
})
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn get_inventory(self: Arc<Self>) -> Option<Arc<dyn Inventory>> {
Some(self as Arc<dyn Inventory>)
}
}
impl Inventory for BeaconBlockEntity {
fn size(&self) -> usize {
1
}
fn is_empty(&self) -> InventoryFuture<'_, bool> {
Box::pin(async move { self.payment.lock().await.is_empty() })
}
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
Box::pin(async move {
if slot == 0 {
self.payment.clone()
} else {
Arc::new(Mutex::new(ItemStack::EMPTY.clone()))
}
})
}
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move {
if slot == 0 {
let mut removed = ItemStack::EMPTY.clone();
let mut guard = self.payment.lock().await;
std::mem::swap(&mut removed, &mut *guard);
self.mark_dirty();
removed
} else {
ItemStack::EMPTY.clone()
}
})
}
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move {
if slot == 0 {
let mut stack = self.payment.lock().await;
if stack.is_empty() {
return ItemStack::EMPTY.clone();
}
let res = stack.split(amount);
self.mark_dirty();
res
} else {
ItemStack::EMPTY.clone()
}
})
}
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
Box::pin(async move {
if slot == 0 {
*self.payment.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 BeaconBlockEntity {
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move {
if let Ok(mut payment) = self.payment.try_lock() {
*payment = ItemStack::EMPTY.clone();
}
})
}
}

View File

@@ -1,5 +1,5 @@
use crate::block::entities::BlockEntity;
use crate::world::SimpleWorld;
use crate::world::World;
use crossbeam::atomic::AtomicCell;
use pumpkin_data::block_properties::HorizontalFacing;
use pumpkin_data::sound::{Sound, SoundCategory};
@@ -61,10 +61,7 @@ impl BlockEntity for BellBlockEntity {
Self::new(position)
}
fn tick<'a>(
&'a self,
world: &'a Arc<dyn SimpleWorld>,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
fn tick<'a>(&'a self, world: &'a Arc<World>) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
if self.ringing.load() {
self.ring_ticks.fetch_add(1);

View File

@@ -1,5 +1,5 @@
use crate::inventory::Inventory;
use pumpkin_data::{block_properties::BlockProperties, item_stack::ItemStack};
use pumpkin_world::inventory::Inventory;
use std::{
array::from_fn,

View File

@@ -8,7 +8,6 @@ use std::sync::{
};
use crate::block::entities::PropertyDelegate;
use crate::inventory::Inventory;
use pumpkin_data::block_properties::BlockProperties;
use pumpkin_data::data_component_impl::DataComponentImpl;
use pumpkin_data::item::Item;
@@ -19,6 +18,7 @@ use pumpkin_data::tag::{self, Taggable};
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_world::inventory::Inventory;
use tokio::sync::Mutex;
pub struct BrewingStandBlockEntity {
@@ -50,12 +50,11 @@ impl BrewingStandBlockEntity {
}
/// Check if the current ingredient matches the stored ingredient
async fn ingredient_matches(&self, ingredient: &ItemStack) -> bool {
if let Some(stored) = *self.ingredient_item.lock().unwrap() {
!ingredient.is_empty() && ingredient.get_item().id == stored.id
} else {
false
}
fn ingredient_matches(&self, ingredient: &ItemStack) -> bool {
self.ingredient_item
.lock()
.unwrap()
.is_some_and(|stored| !ingredient.is_empty() && ingredient.get_item().id == stored.id)
}
/// Check if any potion slot has a valid recipe with the ingredient
@@ -101,7 +100,7 @@ impl BrewingStandBlockEntity {
}
/// Perform brewing on all valid potion slots
async fn do_brew(&self, world: &Arc<dyn crate::world::SimpleWorld>, ingredient: &ItemStack) {
async fn do_brew(&self, world: &Arc<crate::world::World>, ingredient: &ItemStack) {
let ingredient_id = ingredient.get_item().id;
// Apply recipes to each slot
@@ -120,18 +119,19 @@ impl BrewingStandBlockEntity {
{
let new_item = recipe.to();
let potion_comp = slot.get_data_component::<pumpkin_data::data_component_impl::PotionContentsImpl>().cloned();
let new_stack = if let Some(pc) = potion_comp {
ItemStack::new_with_component(
slot.item_count,
new_item,
vec![(
pumpkin_data::data_component::DataComponent::PotionContents,
Some(pc.to_dyn()),
)],
)
} else {
ItemStack::new(slot.item_count, new_item)
};
let new_stack = potion_comp.map_or_else(
|| ItemStack::new(slot.item_count, new_item),
|pc| {
ItemStack::new_with_component(
slot.item_count,
new_item,
vec![(
pumpkin_data::data_component::DataComponent::PotionContents,
Some(pc.to_dyn()),
)],
)
},
);
new_stack_opt = Some(new_stack);
break;
}
@@ -203,12 +203,12 @@ impl BrewingStandBlockEntity {
}
}
impl crate::inventory::Inventory for BrewingStandBlockEntity {
impl pumpkin_world::inventory::Inventory for BrewingStandBlockEntity {
fn size(&self) -> usize {
Self::INVENTORY_SIZE
}
fn is_empty(&self) -> crate::inventory::InventoryFuture<'_, bool> {
fn is_empty(&self) -> pumpkin_world::inventory::InventoryFuture<'_, bool> {
Box::pin(async move {
for slot in &self.items {
if !slot.lock().await.is_empty() {
@@ -222,11 +222,14 @@ impl crate::inventory::Inventory for BrewingStandBlockEntity {
fn get_stack(
&self,
slot: usize,
) -> crate::inventory::InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
) -> pumpkin_world::inventory::InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
Box::pin(async move { self.items[slot].clone() })
}
fn remove_stack(&self, slot: usize) -> crate::inventory::InventoryFuture<'_, ItemStack> {
fn remove_stack(
&self,
slot: usize,
) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
Box::pin(async move {
let mut removed = ItemStack::EMPTY.clone();
let mut guard = self.items[slot].lock().await;
@@ -239,7 +242,7 @@ impl crate::inventory::Inventory for BrewingStandBlockEntity {
&self,
slot: usize,
amount: u8,
) -> crate::inventory::InventoryFuture<'_, ItemStack> {
) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
Box::pin(async move {
let mut guard = self.items[slot].lock().await;
let mut taken = ItemStack::EMPTY.clone();
@@ -258,18 +261,18 @@ impl crate::inventory::Inventory for BrewingStandBlockEntity {
&self,
slot: usize,
stack: ItemStack,
) -> crate::inventory::InventoryFuture<'_, ()> {
) -> pumpkin_world::inventory::InventoryFuture<'_, ()> {
Box::pin(async move {
*self.items[slot].lock().await = stack;
self.mark_dirty();
})
}
fn on_open(&self) -> crate::inventory::InventoryFuture<'_, ()> {
fn on_open(&self) -> pumpkin_world::inventory::InventoryFuture<'_, ()> {
Box::pin(async move {})
}
fn on_close(&self) -> crate::inventory::InventoryFuture<'_, ()> {
fn on_close(&self) -> pumpkin_world::inventory::InventoryFuture<'_, ()> {
Box::pin(async move {})
}
@@ -307,7 +310,7 @@ impl crate::inventory::Inventory for BrewingStandBlockEntity {
}
}
impl crate::inventory::Clearable for BrewingStandBlockEntity {
impl pumpkin_world::inventory::Clearable for BrewingStandBlockEntity {
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move {
for slot in &self.items {
@@ -368,15 +371,15 @@ impl crate::block::entities::BlockEntity for BrewingStandBlockEntity {
fn write_nbt<'a>(
&'a self,
_nbt: &'a mut NbtCompound,
nbt: &'a mut NbtCompound,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
// Persist brew state
_nbt.put_int("BrewTime", self.brew_time.load(Ordering::Relaxed));
_nbt.put_int("Fuel", self.fuel.load(Ordering::Relaxed));
nbt.put_int("BrewTime", self.brew_time.load(Ordering::Relaxed));
nbt.put_int("Fuel", self.fuel.load(Ordering::Relaxed));
// Save inventory contents to NBT
self.write_inventory_nbt(_nbt, true).await;
self.write_inventory_nbt(nbt, true).await;
})
}
@@ -406,7 +409,7 @@ impl crate::block::entities::BlockEntity for BrewingStandBlockEntity {
fn tick<'a>(
&'a self,
world: &'a Arc<dyn crate::world::SimpleWorld>,
world: &'a Arc<crate::world::World>,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
// Refill fuel counter from fuel item if needed
@@ -442,7 +445,7 @@ impl crate::block::entities::BlockEntity for BrewingStandBlockEntity {
if is_done_brewing && brewable {
// Brewing complete
self.do_brew(world, &ingredient).await;
} else if !brewable || !self.ingredient_matches(&ingredient).await {
} else if !brewable || !self.ingredient_matches(&ingredient) {
// Cancel brewing
self.brew_time.store(0, Ordering::Relaxed);
self.mark_dirty();

View File

@@ -18,7 +18,7 @@ macro_rules! impl_block_entity_for_chest {
where
Self: Sized,
{
use $crate::inventory::Inventory;
use pumpkin_world::inventory::Inventory;
let chest = Self {
position,
@@ -36,7 +36,7 @@ macro_rules! impl_block_entity_for_chest {
&'a self,
nbt: &'a mut pumpkin_nbt::compound::NbtCompound,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + 'a>> {
use $crate::inventory::Inventory;
use pumpkin_world::inventory::Inventory;
// Write inventory data to NBT
self.write_inventory_nbt(nbt, true)
@@ -44,16 +44,20 @@ macro_rules! impl_block_entity_for_chest {
fn tick<'a>(
&'a self,
world: &'a Arc<dyn $crate::world::SimpleWorld>,
world: &'a Arc<$crate::world::World>,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
self.viewers
.update_viewer_count::<Self>(self, world, &self.position)
.await;
$crate::block::viewer::ViewerCountTrackerExt::update_viewer_count::<$struct_name>(
&self.viewers,
self,
world,
&self.position,
)
.await;
})
}
fn get_inventory(self: Arc<Self>) -> Option<Arc<dyn $crate::inventory::Inventory>> {
fn get_inventory(self: Arc<Self>) -> Option<Arc<dyn pumpkin_world::inventory::Inventory>> {
Some(self)
}
@@ -77,12 +81,12 @@ macro_rules! impl_block_entity_for_chest {
#[macro_export]
macro_rules! impl_inventory_for_chest {
($struct_name:ty) => {
impl $crate::inventory::Inventory for $struct_name {
impl pumpkin_world::inventory::Inventory for $struct_name {
fn size(&self) -> usize {
self.items.len()
}
fn is_empty(&self) -> $crate::inventory::InventoryFuture<'_, bool> {
fn is_empty(&self) -> pumpkin_world::inventory::InventoryFuture<'_, bool> {
Box::pin(async move {
for slot in &self.items {
if !slot.lock().await.is_empty() {
@@ -97,14 +101,14 @@ macro_rules! impl_inventory_for_chest {
fn get_stack(
&self,
slot: usize,
) -> $crate::inventory::InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
) -> pumpkin_world::inventory::InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
Box::pin(async move { self.items[slot].clone() })
}
fn remove_stack(
&self,
slot: usize,
) -> $crate::inventory::InventoryFuture<'_, ItemStack> {
) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
Box::pin(async move {
let mut removed = ItemStack::EMPTY.clone();
let mut guard = self.items[slot].lock().await;
@@ -118,9 +122,10 @@ macro_rules! impl_inventory_for_chest {
&self,
slot: usize,
amount: u8,
) -> $crate::inventory::InventoryFuture<'_, ItemStack> {
) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
Box::pin(async move {
let res = $crate::inventory::split_stack(&self.items, slot, amount).await;
let res =
pumpkin_world::inventory::split_stack(&self.items, slot, amount).await;
self.mark_dirty();
res
})
@@ -130,20 +135,20 @@ macro_rules! impl_inventory_for_chest {
&self,
slot: usize,
stack: ItemStack,
) -> $crate::inventory::InventoryFuture<'_, ()> {
) -> pumpkin_world::inventory::InventoryFuture<'_, ()> {
Box::pin(async move {
*self.items[slot].lock().await = stack;
self.mark_dirty();
})
}
fn on_open(&self) -> $crate::inventory::InventoryFuture<'_, ()> {
fn on_open(&self) -> pumpkin_world::inventory::InventoryFuture<'_, ()> {
Box::pin(async move {
self.viewers.open_container();
})
}
fn on_close(&self) -> $crate::inventory::InventoryFuture<'_, ()> {
fn on_close(&self) -> pumpkin_world::inventory::InventoryFuture<'_, ()> {
Box::pin(async move {
self.viewers.close_container();
})
@@ -164,7 +169,7 @@ macro_rules! impl_inventory_for_chest {
#[macro_export]
macro_rules! impl_clearable_for_chest {
($struct_name:ty) => {
impl $crate::inventory::Clearable for $struct_name {
impl pumpkin_world::inventory::Clearable for $struct_name {
fn clear(
&self,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + '_>> {
@@ -172,7 +177,7 @@ macro_rules! impl_clearable_for_chest {
for slot in &self.items {
*slot.lock().await = ItemStack::EMPTY.clone();
}
<$struct_name as $crate::inventory::Inventory>::mark_dirty(self);
<$struct_name as pumpkin_world::inventory::Inventory>::mark_dirty(self);
})
}
}
@@ -189,7 +194,7 @@ macro_rules! impl_viewer_count_listener_for_chest {
impl $crate::block::viewer::ViewerCountListener for $struct_name {
fn on_container_open<'a>(
&'a self,
world: &'a Arc<dyn $crate::world::SimpleWorld>,
world: &'a Arc<$crate::world::World>,
_position: &'a pumpkin_util::math::position::BlockPos,
) -> $crate::block::viewer::ViewerFuture<'a, ()> {
Box::pin(async move {
@@ -200,7 +205,7 @@ macro_rules! impl_viewer_count_listener_for_chest {
fn on_container_close<'a>(
&'a self,
world: &'a Arc<dyn $crate::world::SimpleWorld>,
world: &'a Arc<$crate::world::World>,
_position: &'a pumpkin_util::math::position::BlockPos,
) -> $crate::block::viewer::ViewerFuture<'a, ()> {
Box::pin(async move {
@@ -211,7 +216,7 @@ macro_rules! impl_viewer_count_listener_for_chest {
fn on_viewer_count_update<'a>(
&'a self,
world: &'a Arc<dyn $crate::world::SimpleWorld>,
world: &'a Arc<$crate::world::World>,
position: &'a pumpkin_util::math::position::BlockPos,
old: u16,
new: u16,
@@ -271,7 +276,7 @@ macro_rules! impl_chest_helper_methods {
async fn play_sound(
&self,
world: &Arc<dyn $crate::world::SimpleWorld>,
world: &Arc<$crate::world::World>,
sound: pumpkin_data::sound::Sound,
) {
let mut rng = pumpkin_util::random::xoroshiro128::Xoroshiro::from_seed(

View File

@@ -15,12 +15,12 @@ use std::{
use tokio::sync::Mutex;
use tracing::warn;
use crate::inventory::InventoryFuture;
use crate::{
block::entities::BlockEntity,
inventory::{Clearable, Inventory, split_stack},
world::{BlockFlags, SimpleWorld},
world::{BlockFlags, World},
};
use pumpkin_world::inventory::InventoryFuture;
use pumpkin_world::inventory::{Clearable, Inventory, split_stack};
pub struct ChiseledBookshelfBlockEntity {
pub position: BlockPos,
@@ -108,7 +108,7 @@ impl ChiseledBookshelfBlockEntity {
pub async fn update_state(
&self,
mut properties: ChiseledBookshelfLikeProperties,
world: Arc<dyn SimpleWorld>,
world: Arc<World>,
slot: i8,
) {
if slot >= 0 && slot < self.items.len() as i8 {

View File

@@ -5,7 +5,7 @@ use pumpkin_data::block_properties::BlockProperties;
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::math::position::BlockPos;
use crate::world::{BlockFlags, SimpleWorld};
use crate::world::{BlockFlags, World};
use super::BlockEntity;
@@ -42,12 +42,9 @@ impl BlockEntity for DaylightDetectorBlockEntity {
self
}
fn tick<'a>(
&'a self,
world: &'a Arc<dyn SimpleWorld>,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
fn tick<'a>(&'a self, world: &'a Arc<World>) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async {
if world.get_world_age().await % 20 == 0 && world.get_dimension().await.has_skylight {
if world.get_world_age().await % 20 == 0 && world.dimension.has_skylight {
Self::update_power(world, &self.position).await;
}
})
@@ -62,13 +59,13 @@ impl DaylightDetectorBlockEntity {
Self { position }
}
pub async fn update_power<W: SimpleWorld + ?Sized>(world: &Arc<W>, block_pos: &BlockPos) {
pub async fn update_power(world: &Arc<World>, block_pos: &BlockPos) {
use std::f32::consts::PI;
let (block, state) = world.get_block_and_state(block_pos).await;
let mut props = DaylightDetectorProperties::from_state_id(state.id, block);
let level = world.get_level().await;
let level = world.level.clone();
let inverted = props.inverted;
@@ -102,7 +99,7 @@ impl DaylightDetectorBlockEntity {
let sky_light_level = level
.light_engine
.get_sky_light_level(level, block_pos)
.get_sky_light_level(&level, block_pos)
.await;
let mut power = sky_light_level - ambient_darkness;

View File

@@ -1,8 +1,8 @@
use crate::block::entities::BlockEntity;
use crate::inventory::{Clearable, Inventory, InventoryFuture, split_stack};
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;

View File

@@ -7,8 +7,10 @@ use std::any::Any;
use std::pin::Pin;
use std::sync::Arc;
use crate::block::viewer::{ViewerCountListener, ViewerCountTracker, ViewerFuture};
use crate::world::SimpleWorld;
use crate::block::viewer::{
ViewerCountListener, ViewerCountTracker, ViewerCountTrackerExt, ViewerFuture,
};
use crate::world::World;
use super::BlockEntity;
@@ -45,10 +47,7 @@ impl BlockEntity for EnderChestBlockEntity {
Box::pin(async {})
}
fn tick<'a>(
&'a self,
world: &'a Arc<dyn SimpleWorld>,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
fn tick<'a>(&'a self, world: &'a Arc<World>) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
self.viewers
.update_viewer_count::<Self>(self, world, &self.position)
@@ -64,7 +63,7 @@ impl BlockEntity for EnderChestBlockEntity {
impl ViewerCountListener for EnderChestBlockEntity {
fn on_container_open<'a>(
&'a self,
world: &'a Arc<dyn SimpleWorld>,
world: &'a Arc<World>,
_position: &'a BlockPos,
) -> ViewerFuture<'a, ()> {
Box::pin(async move {
@@ -74,7 +73,7 @@ impl ViewerCountListener for EnderChestBlockEntity {
fn on_container_close<'a>(
&'a self,
world: &'a Arc<dyn SimpleWorld>,
world: &'a Arc<World>,
_position: &'a BlockPos,
) -> ViewerFuture<'a, ()> {
Box::pin(async move {
@@ -84,7 +83,7 @@ impl ViewerCountListener for EnderChestBlockEntity {
fn on_viewer_count_update<'a>(
&'a self,
world: &'a Arc<dyn SimpleWorld>,
world: &'a Arc<World>,
position: &'a BlockPos,
_old: u16,
new: u16,
@@ -114,7 +113,7 @@ impl EnderChestBlockEntity {
self.viewers.clone()
}
async fn play_sound(&self, world: &Arc<dyn SimpleWorld>, sound: Sound) {
async fn play_sound(&self, world: &Arc<World>, sound: Sound) {
let mut rng = Xoroshiro::from_seed(get_seed());
world

View File

@@ -1,5 +1,5 @@
use crate::inventory::Inventory;
use pumpkin_data::block_properties::BlockProperties;
use pumpkin_world::inventory::Inventory;
use std::{
array::from_fn,

View File

@@ -3,18 +3,11 @@ use std::sync::Arc;
use pumpkin_data::{item_stack::ItemStack, recipes::CookingRecipe};
use tokio::sync::Mutex;
use crate::{
block::entities::{BlockEntity, PropertyDelegate},
inventory::{Clearable, Inventory},
};
use crate::block::entities::{BlockEntity, PropertyDelegate};
pub use pumpkin_world::block::entities::ExperienceContainer;
use pumpkin_world::inventory::{Clearable, Inventory};
/// Trait for extracting smelting experience from cooking block entities.
/// This is a separate dyn-compatible trait since `CookingBlockEntityBase` is not.
pub trait ExperienceContainer: Send + Sync {
/// Extract and reset accumulated experience, returning the total as an integer
fn extract_experience(&self) -> i32;
}
pub trait CookingBlockEntityBase:
Sync + Send + Inventory + PropertyDelegate + BlockEntity + Clearable
{
@@ -130,10 +123,7 @@ macro_rules! impl_cooking_block_entity_base {
recipe: Option<&pumpkin_data::recipes::CookingRecipe>,
max_count: u8,
) -> bool {
let recipe = match recipe {
Some(cooking_recipe) => cooking_recipe,
None => return false,
};
let Some(recipe) = recipe else { return false };
let top_item_stack = self.items[0].lock().await;
let is_top_items_empty = top_item_stack.is_empty();
@@ -165,11 +155,10 @@ macro_rules! impl_cooking_block_entity_base {
if let Some(recipe) = recipe {
if can_accept_output {
let mut side_items = self.items[2].lock().await;
let output_item = match pumpkin_data::item::Item::from_registry_key(
let Some(output_item) = pumpkin_data::item::Item::from_registry_key(
recipe.result.id.strip_prefix("minecraft:").unwrap(),
) {
Some(item) => item,
None => return false,
) else {
return false;
};
let output_item_stack = ItemStack::new(recipe.result.count, output_item);
@@ -243,7 +232,7 @@ macro_rules! impl_property_delegate_for_cooking {
#[macro_export]
macro_rules! impl_clearable_for_cooking {
($struct_name:ty) => {
impl $crate::inventory::Clearable for $struct_name {
impl pumpkin_world::inventory::Clearable for $struct_name {
fn clear(&self) -> std::pin::Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move {
for slot in self.items.iter() {
@@ -273,12 +262,12 @@ macro_rules! impl_experience_container_for_cooking {
#[macro_export]
macro_rules! impl_inventory_for_cooking {
($struct_name:ty) => {
impl $crate::inventory::Inventory for $struct_name {
impl pumpkin_world::inventory::Inventory for $struct_name {
fn size(&self) -> usize {
self.items.len()
}
fn is_empty(&self) -> $crate::inventory::InventoryFuture<'_, bool> {
fn is_empty(&self) -> pumpkin_world::inventory::InventoryFuture<'_, bool> {
Box::pin(async move {
for slot in self.items.iter() {
if !slot.lock().await.is_empty() {
@@ -292,14 +281,14 @@ macro_rules! impl_inventory_for_cooking {
fn get_stack(
&self,
slot: usize,
) -> $crate::inventory::InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
) -> pumpkin_world::inventory::InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
Box::pin(async move { self.items[slot].clone() })
}
fn remove_stack(
&self,
slot: usize,
) -> $crate::inventory::InventoryFuture<'_, ItemStack> {
) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
Box::pin(async move {
let mut removed = ItemStack::EMPTY.clone();
let mut guard = self.items[slot].lock().await;
@@ -313,9 +302,10 @@ macro_rules! impl_inventory_for_cooking {
&self,
slot: usize,
amount: u8,
) -> $crate::inventory::InventoryFuture<'_, ItemStack> {
) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
Box::pin(async move {
let res = $crate::inventory::split_stack(&self.items, slot, amount).await;
let res =
pumpkin_world::inventory::split_stack(&self.items, slot, amount).await;
self.mark_dirty();
res
})
@@ -325,7 +315,7 @@ macro_rules! impl_inventory_for_cooking {
&self,
slot: usize,
stack: ItemStack,
) -> $crate::inventory::InventoryFuture<'_, ()> {
) -> pumpkin_world::inventory::InventoryFuture<'_, ()> {
Box::pin(async move {
let furnace_stack = self.get_stack(slot).await;
let mut furnace_stack = furnace_stack.lock().await;
@@ -370,9 +360,10 @@ macro_rules! impl_inventory_for_cooking {
macro_rules! impl_block_entity_for_cooking {
($struct_name:ty,$recipe_kind:expr) => {
impl $crate::block::entities::BlockEntity for $struct_name {
#[expect(clippy::too_many_lines)]
fn tick<'a>(
&'a self,
world: &'a Arc<dyn $crate::world::SimpleWorld>,
world: &'a Arc<$crate::world::World>,
) -> std::pin::Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
let is_burning = self.is_burning();
@@ -586,7 +577,9 @@ macro_rules! impl_block_entity_for_cooking {
})
}
fn get_inventory(self: Arc<Self>) -> Option<Arc<dyn $crate::inventory::Inventory>> {
fn get_inventory(
self: Arc<Self>,
) -> Option<Arc<dyn pumpkin_world::inventory::Inventory>> {
Some(self)
}

View File

@@ -1,7 +1,6 @@
use crate::BlockStateId;
use crate::block::entities::BlockEntity;
use crate::inventory::{Clearable, Inventory, InventoryFuture, split_stack};
use crate::world::SimpleWorld;
use crate::entity::experience_orb::ExperienceOrbEntity;
use crate::world::World;
use pumpkin_data::block_properties::{BlockProperties, FacingHopper, HopperLikeProperties};
use pumpkin_data::item_stack::ItemStack;
use pumpkin_data::tag::Taggable;
@@ -10,6 +9,8 @@ use pumpkin_nbt::compound::NbtCompound;
use pumpkin_nbt::tag::NbtTag;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_world::BlockStateId;
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture, split_stack};
use std::any::Any;
use std::array::from_fn;
use std::pin::Pin;
@@ -71,10 +72,7 @@ impl BlockEntity for HopperBlockEntity {
hopper
}
fn tick<'a>(
&'a self,
world: &'a Arc<dyn SimpleWorld>,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
fn tick<'a>(&'a self, world: &'a Arc<World>) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
self.ticked_game_time
.store(world.get_world_age().await, Ordering::Relaxed);
@@ -134,12 +132,13 @@ impl HopperBlockEntity {
ticked_game_time: AtomicI64::new(0),
}
}
async fn try_move_items(&self, state: &HopperLikeProperties, world: &Arc<dyn SimpleWorld>) {
async fn try_move_items(&self, state: &HopperLikeProperties, world: &Arc<World>) {
if self.cooldown_time.load(Ordering::Relaxed) <= 0 && state.enabled {
let mut success = false;
if !self.is_empty().await {
success = self.eject_items(world).await;
}
let mut success = if self.is_empty().await {
false
} else {
self.eject_items(world).await
};
if !self.inventory_full().await {
success |= self.suck_in_items(world).await;
}
@@ -160,7 +159,7 @@ impl HopperBlockEntity {
true
}
async fn suck_in_items(&self, world: &Arc<dyn SimpleWorld>) -> bool {
async fn suck_in_items(&self, world: &Arc<World>) -> bool {
// TODO getEntityContainer
let pos_up = &self.position.up();
if let Some(entity) = world.get_block_entity(pos_up).await
@@ -184,7 +183,7 @@ impl HopperBlockEntity {
let xp = experience_container.extract_experience();
if xp > 0 {
let pos = self.position.to_f64();
world.clone().spawn_experience_orbs(pos, xp as u32).await;
ExperienceOrbEntity::spawn(world, pos, xp as u32).await;
}
}
return true;
@@ -202,7 +201,7 @@ impl HopperBlockEntity {
false
}
async fn eject_items(&self, world: &Arc<dyn SimpleWorld>) -> bool {
async fn eject_items(&self, world: &Arc<World>) -> bool {
// TODO getEntityContainer
if let Some(entity) = world

View File

@@ -10,8 +10,8 @@ use pumpkin_util::math::position::BlockPos;
use tokio::sync::Mutex;
use crate::block::entities::BlockEntity;
use crate::inventory::{Clearable, Inventory, InventoryFuture};
use crate::world::SimpleWorld;
use crate::world::World;
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture};
/// Matches vanilla's `JukeboxBlockEntity`
pub struct JukeboxBlockEntity {
@@ -77,10 +77,7 @@ impl BlockEntity for JukeboxBlockEntity {
})
}
fn tick<'a>(
&'a self,
_world: &'a Arc<dyn SimpleWorld>,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
fn tick<'a>(&'a self, _world: &'a Arc<World>) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
// Increment ticks if we're playing
let song_length = self.song_length_ticks.load(Ordering::Relaxed);

View File

@@ -11,10 +11,8 @@ use std::{
};
use tokio::sync::Mutex;
use crate::{
block::entities::BlockEntity,
inventory::{Clearable, Inventory, InventoryFuture},
};
use crate::block::entities::BlockEntity;
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture};
pub struct LecternBlockEntity {
pub position: BlockPos,
@@ -38,8 +36,10 @@ impl BlockEntity for LecternBlockEntity {
let book = nbt
.get_compound("Book")
.and_then(ItemStack::read_item_stack)
.map(|stack| Arc::new(Mutex::new(stack)))
.unwrap_or_else(|| Arc::new(Mutex::new(ItemStack::EMPTY.clone())));
.map_or_else(
|| Arc::new(Mutex::new(ItemStack::EMPTY.clone())),
|stack| Arc::new(Mutex::new(stack)),
);
Self {
position,

View File

@@ -15,7 +15,7 @@ use pumpkin_util::math::{
vector3::Vector3,
};
use crate::{block::entities::BlockEntity, world::SimpleWorld};
use crate::{block::entities::BlockEntity, world::World};
pub struct MobSpawnerBlockEntity {
pub position: BlockPos,
@@ -69,7 +69,7 @@ impl MobSpawnerBlockEntity {
}
impl MobSpawnerBlockEntity {
async fn update_spawns(&self, world: &Arc<dyn SimpleWorld>) {
async fn update_spawns(&self, world: &Arc<World>) {
let min_delay = self.min_delay;
let max_delay = self.max_delay;
@@ -98,10 +98,7 @@ impl BlockEntity for MobSpawnerBlockEntity {
self.position
}
fn tick<'a>(
&'a self,
world: &'a Arc<dyn SimpleWorld>,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
fn tick<'a>(&'a self, world: &'a Arc<World>) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
if let Some(entity_type) = &self.entity_type.load() {
if self.delay.load(Ordering::Relaxed) == -1 {
@@ -140,7 +137,14 @@ impl BlockEntity for MobSpawnerBlockEntity {
{
continue;
}
world.clone().spawn_from_type(entity_type, spawn_pos).await;
let entity = crate::entity::r#type::from_type(
entity_type,
spawn_pos,
world,
uuid::Uuid::new_v4(),
)
.await;
world.spawn_entity(entity).await;
world
.sync_world_event(WorldEvent::ParticlesMobblockSpawn, self.position, 0)
.await;

View File

@@ -0,0 +1,207 @@
use std::pin::Pin;
use std::{any::Any, sync::Arc};
use pumpkin_data::{Block, block_properties::BLOCK_ENTITY_TYPES};
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::math::position::BlockPos;
use crate::world::World;
use pumpkin_world::BlockStateId;
use pumpkin_world::inventory::Inventory;
pub mod barrel;
pub mod beacon;
pub mod bed;
pub mod bell;
pub mod blasting_furnace;
pub mod brewing_stand;
pub mod chest;
pub mod chest_like_block_entity;
pub mod chiseled_bookshelf;
pub mod command_block;
pub mod comparator;
pub mod daylight_detector;
pub mod dropper;
pub mod end_portal;
pub mod ender_chest;
pub mod furnace;
pub mod furnace_like_block_entity;
pub mod hopper;
pub mod jukebox;
pub mod lectern;
pub mod mob_spawner;
pub mod piston;
pub mod shulker_box;
pub mod sign;
pub mod smoker;
pub mod trapped_chest;
pub use furnace_like_block_entity::ExperienceContainer;
pub use pumpkin_world::block::entities::PropertyDelegate;
//TODO: We need a mark_dirty for chests
pub trait BlockEntity: Any + Send + Sync {
fn write_nbt<'a>(
&'a self,
nbt: &'a mut NbtCompound,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>>;
fn from_nbt(nbt: &NbtCompound, position: BlockPos) -> Self
where
Self: Sized;
fn tick<'a>(&'a self, _world: &'a Arc<World>) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async {})
}
fn resource_location(&self) -> &'static str;
fn get_position(&self) -> BlockPos;
fn write_internal<'a>(
&'a self,
nbt: &'a mut NbtCompound,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
nbt.put_string("id", self.resource_location().to_string());
let position = self.get_position();
nbt.put_int("x", position.0.x);
nbt.put_int("y", position.0.y);
nbt.put_int("z", position.0.z);
self.write_nbt(nbt).await;
})
}
fn get_id(&self) -> u32 {
pumpkin_data::block_properties::BLOCK_ENTITY_TYPES
.iter()
.position(|block_entity_name| {
*block_entity_name == self.resource_location().split(':').next_back().unwrap()
})
.unwrap() as u32
}
/// Obtain NBT data for sending to the client in [`ChunkData`](crate::chunk::ChunkData)
fn chunk_data_nbt(&self) -> Option<NbtCompound> {
None
}
fn get_inventory(self: Arc<Self>) -> Option<Arc<dyn Inventory>> {
None
}
fn set_block_state(&mut self, _block_state: BlockStateId) {}
fn on_block_replaced<'a>(
self: Arc<Self>,
world: Arc<World>,
position: BlockPos,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>>
where
Self: 'a,
{
Box::pin(async move {
if let Some(inventory) = self.get_inventory() {
// Assuming scatter_inventory is an async method on World
world.scatter_inventory(&position, &inventory).await;
}
})
}
fn is_dirty(&self) -> bool {
false
}
fn clear_dirty(&self) {
// Default implementation does nothing
// Override in implementations that have a dirty flag
}
fn as_any(&self) -> &dyn Any;
fn to_property_delegate(self: Arc<Self>) -> Option<Arc<dyn PropertyDelegate>> {
None
}
fn to_experience_container(self: Arc<Self>) -> Option<Arc<dyn ExperienceContainer>> {
None
}
}
#[must_use]
pub fn block_entity_from_generic<T: BlockEntity>(nbt: &NbtCompound) -> T {
let x = nbt.get_int("x").unwrap();
let y = nbt.get_int("y").unwrap();
let z = nbt.get_int("z").unwrap();
T::from_nbt(nbt, BlockPos::new(x, y, z))
}
#[must_use]
pub fn block_entity_from_nbt(nbt: &NbtCompound) -> Option<Arc<dyn BlockEntity>> {
let id = nbt.get_string("id")?;
let x = nbt.get_int("x")?;
let y = nbt.get_int("y")?;
let z = nbt.get_int("z")?;
let pos = BlockPos::new(x, y, z);
match id {
barrel::BarrelBlockEntity::ID => {
Some(Arc::new(barrel::BarrelBlockEntity::from_nbt(nbt, pos)))
}
chest::ChestBlockEntity::ID => Some(Arc::new(chest::ChestBlockEntity::from_nbt(nbt, pos))),
trapped_chest::TrappedChestBlockEntity::ID => Some(Arc::new(
trapped_chest::TrappedChestBlockEntity::from_nbt(nbt, pos),
)),
ender_chest::EnderChestBlockEntity::ID => Some(Arc::new(
ender_chest::EnderChestBlockEntity::from_nbt(nbt, pos),
)),
furnace::FurnaceBlockEntity::ID => {
Some(Arc::new(furnace::FurnaceBlockEntity::from_nbt(nbt, pos)))
}
blasting_furnace::BlastingFurnaceBlockEntity::ID => Some(Arc::new(
blasting_furnace::BlastingFurnaceBlockEntity::from_nbt(nbt, pos),
)),
smoker::SmokerBlockEntity::ID => {
Some(Arc::new(smoker::SmokerBlockEntity::from_nbt(nbt, pos)))
}
brewing_stand::BrewingStandBlockEntity::ID => Some(Arc::new(
brewing_stand::BrewingStandBlockEntity::from_nbt(nbt, pos),
)),
hopper::HopperBlockEntity::ID => {
Some(Arc::new(hopper::HopperBlockEntity::from_nbt(nbt, pos)))
}
jukebox::JukeboxBlockEntity::ID => {
Some(Arc::new(jukebox::JukeboxBlockEntity::from_nbt(nbt, pos)))
}
mob_spawner::MobSpawnerBlockEntity::ID => Some(Arc::new(
mob_spawner::MobSpawnerBlockEntity::from_nbt(nbt, pos),
)),
sign::SignBlockEntity::ID => Some(Arc::new(sign::SignBlockEntity::from_nbt(nbt, pos))),
piston::PistonBlockEntity::ID => {
Some(Arc::new(piston::PistonBlockEntity::from_nbt(nbt, pos)))
}
chiseled_bookshelf::ChiseledBookshelfBlockEntity::ID => Some(Arc::new(
chiseled_bookshelf::ChiseledBookshelfBlockEntity::from_nbt(nbt, pos),
)),
dropper::DropperBlockEntity::ID => {
Some(Arc::new(dropper::DropperBlockEntity::from_nbt(nbt, pos)))
}
command_block::CommandBlockEntity::ID => Some(Arc::new(
command_block::CommandBlockEntity::from_nbt(nbt, pos),
)),
comparator::ComparatorBlockEntity::ID => Some(Arc::new(
comparator::ComparatorBlockEntity::from_nbt(nbt, pos),
)),
daylight_detector::DaylightDetectorBlockEntity::ID => Some(Arc::new(
daylight_detector::DaylightDetectorBlockEntity::from_nbt(nbt, pos),
)),
end_portal::EndPortalBlockEntity::ID => Some(Arc::new(
end_portal::EndPortalBlockEntity::from_nbt(nbt, pos),
)),
beacon::BeaconBlockEntity::ID => {
Some(Arc::new(beacon::BeaconBlockEntity::from_nbt(nbt, pos)))
}
bed::BedBlockEntity::ID => Some(Arc::new(bed::BedBlockEntity::from_nbt(nbt, pos))),
bell::BellBlockEntity::ID => Some(Arc::new(bell::BellBlockEntity::from_nbt(nbt, pos))),
shulker_box::ShulkerBoxBlockEntity::ID => Some(Arc::new(
shulker_box::ShulkerBoxBlockEntity::from_nbt(nbt, pos),
)),
lectern::LecternBlockEntity::ID => {
Some(Arc::new(lectern::LecternBlockEntity::from_nbt(nbt, pos)))
}
_ => None,
}
}
#[must_use]
pub fn has_block_block_entity(block: &Block) -> bool {
BLOCK_ENTITY_TYPES.contains(&block.name)
}

View File

@@ -5,7 +5,7 @@ use pumpkin_data::{Block, BlockDirection, BlockState};
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::math::position::BlockPos;
use crate::world::{BlockFlags, SimpleWorld};
use crate::world::{BlockFlags, World};
use super::BlockEntity;
@@ -22,7 +22,7 @@ pub struct PistonBlockEntity {
impl PistonBlockEntity {
pub const ID: &'static str = "minecraft:piston";
pub async fn finish(&self, world: Arc<dyn SimpleWorld>) {
pub async fn finish(&self, world: Arc<World>) {
if self.last_progress.load() < 1.0 {
let pos = self.position;
world.remove_block_entity(&pos).await;
@@ -61,10 +61,7 @@ impl BlockEntity for PistonBlockEntity {
self.position
}
fn tick<'a>(
&'a self,
world: &'a Arc<dyn SimpleWorld>,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
fn tick<'a>(&'a self, world: &'a Arc<World>) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
let current_progress = self.current_progress.load();
self.last_progress.store(current_progress);

View File

@@ -13,12 +13,14 @@ use std::{
};
use tokio::sync::Mutex;
use crate::block::viewer::{ViewerCountListener, ViewerCountTracker, ViewerFuture};
use crate::inventory::InventoryFuture;
use crate::inventory::{
use crate::block::viewer::{
ViewerCountListener, ViewerCountTracker, ViewerCountTrackerExt, ViewerFuture,
};
use crate::world::World;
use pumpkin_world::inventory::InventoryFuture;
use pumpkin_world::inventory::{
split_stack, {Clearable, Inventory},
};
use crate::world::SimpleWorld;
use super::BlockEntity;
@@ -63,10 +65,7 @@ impl BlockEntity for ShulkerBoxBlockEntity {
self.write_inventory_nbt(nbt, true)
}
fn tick<'a>(
&'a self,
world: &'a Arc<dyn SimpleWorld>,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
fn tick<'a>(&'a self, world: &'a Arc<World>) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
self.viewers
.update_viewer_count::<Self>(self, world, &self.position)
@@ -76,7 +75,7 @@ impl BlockEntity for ShulkerBoxBlockEntity {
fn on_block_replaced<'a>(
self: Arc<Self>,
_world: Arc<dyn SimpleWorld>,
_world: Arc<World>,
_position: BlockPos,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>>
where
@@ -107,7 +106,7 @@ impl BlockEntity for ShulkerBoxBlockEntity {
impl ViewerCountListener for ShulkerBoxBlockEntity {
fn on_container_open<'a>(
&'a self,
world: &'a Arc<dyn SimpleWorld>,
world: &'a Arc<World>,
position: &'a BlockPos,
) -> ViewerFuture<'a, ()> {
Box::pin(async move {
@@ -119,7 +118,7 @@ impl ViewerCountListener for ShulkerBoxBlockEntity {
fn on_container_close<'a>(
&'a self,
world: &'a Arc<dyn SimpleWorld>,
world: &'a Arc<World>,
position: &'a BlockPos,
) -> ViewerFuture<'a, ()> {
Box::pin(async move {
@@ -131,7 +130,7 @@ impl ViewerCountListener for ShulkerBoxBlockEntity {
fn on_viewer_count_update<'a>(
&'a self,
world: &'a Arc<dyn SimpleWorld>,
world: &'a Arc<World>,
position: &'a BlockPos,
_old: u16,
new: u16,
@@ -159,7 +158,7 @@ impl ShulkerBoxBlockEntity {
}
}
async fn play_sound(&self, world: &Arc<dyn SimpleWorld>, position: &BlockPos, sound: Sound) {
async fn play_sound(&self, world: &Arc<World>, position: &BlockPos, sound: Sound) {
let mut rng = Xoroshiro::from_seed(get_seed());
world

View File

@@ -139,11 +139,12 @@ impl Default for Text {
Self {
has_glowing_text: AtomicBool::new(false),
color: AtomicI8::new(DyeColor::default() as i8),
messages: Default::default(),
messages: Arc::default(),
}
}
}
#[allow(clippy::fallible_impl_from)]
impl From<Text> for NbtTag {
fn from(value: Text) -> Self {
let mut nbt = NbtCompound::new();
@@ -166,6 +167,7 @@ impl From<Text> for NbtTag {
}
}
#[allow(clippy::fallible_impl_from)]
impl From<NbtTag> for Text {
fn from(tag: NbtTag) -> Self {
let nbt = tag.extract_compound().unwrap();

View File

@@ -1,5 +1,5 @@
use crate::inventory::Inventory;
use pumpkin_data::{block_properties::BlockProperties, item_stack::ItemStack};
use pumpkin_world::inventory::Inventory;
use std::{
array::from_fn,

View File

@@ -13,8 +13,10 @@ use std::sync::Arc;
use std::sync::atomic::Ordering;
pub mod blocks;
pub mod entities;
pub mod fluid;
pub mod registry;
pub mod viewer;
use crate::block::registry::BlockActionResult;
use crate::entity::EntityBase;

View File

@@ -1,8 +1,8 @@
use crate::block::blocks::anvil::AnvilBlock;
use crate::block::blocks::banners::BannerBlock;
use crate::block::blocks::barrel::BarrelBlock;
use crate::block::blocks::barrier::BarrierBlock;
use crate::block::blocks::beacon::BeaconBlock;
use crate::block::blocks::bed::BedBlock;
use crate::block::blocks::brewing_stand::BrewingStandBlock;
use crate::block::blocks::cake::CakeBlock;
@@ -179,6 +179,7 @@ pub fn default_registry() -> Arc<BlockRegistry> {
// Blocks
manager.register(AnvilBlock);
manager.register(BeaconBlock);
manager.register(BedBlock);
manager.register(SaplingBlock);
manager.register(CactusBlock);

View File

@@ -0,0 +1,83 @@
use std::{
pin::Pin,
sync::{Arc, atomic::Ordering},
};
use pumpkin_util::math::position::BlockPos;
use crate::{block::entities::BlockEntity, world::World};
pub use pumpkin_world::block::viewer::ViewerCountTracker;
pub trait ViewerCountTrackerExt {
fn update_viewer_count<'a, T>(
&'a self,
entity: &'a T,
world: &'a Arc<World>,
position: &'a BlockPos,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>>
where
T: BlockEntity + ViewerCountListener + 'static;
}
impl ViewerCountTrackerExt for ViewerCountTracker {
fn update_viewer_count<'a, T>(
&'a self,
entity: &'a T,
world: &'a Arc<World>,
position: &'a BlockPos,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>>
where
T: BlockEntity + ViewerCountListener + 'static,
{
Box::pin(async move {
let current = self.current.load(Ordering::Relaxed);
let old = self.old.swap(current, Ordering::Relaxed);
if old != current {
match (old, current) {
(n, 0) if n > 0 => {
entity.on_container_close(world, position).await;
}
(0, n) if n > 0 => {
entity.on_container_open(world, position).await;
}
_ => {} // Ignore
}
entity
.on_viewer_count_update(world, position, old, current)
.await;
}
})
}
}
pub type ViewerFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
pub trait ViewerCountListener: Send + Sync {
fn on_container_open<'a>(
&'a self,
_world: &'a Arc<World>,
_position: &'a BlockPos,
) -> ViewerFuture<'a, ()> {
Box::pin(async {})
}
fn on_container_close<'a>(
&'a self,
_world: &'a Arc<World>,
_position: &'a BlockPos,
) -> ViewerFuture<'a, ()> {
Box::pin(async {})
}
fn on_viewer_count_update<'a>(
&'a self,
_world: &'a Arc<World>,
_position: &'a BlockPos,
_old: u16,
_new: u16,
) -> ViewerFuture<'a, ()> {
Box::pin(async {})
}
}

View File

@@ -1,3 +1,4 @@
use crate::block::entities::BlockEntity;
use crate::command::{
CommandError, CommandExecutor, CommandResult, CommandSender,
args::{
@@ -7,7 +8,6 @@ use crate::command::{
tree::{CommandTree, builder::argument},
};
use pumpkin_util::{math::vector3::Vector3, text::TextComponent};
use pumpkin_world::block::entities::BlockEntity;
const NAMES: [&str; 1] = ["particle"];
const DESCRIPTION: &str = "Spawns a Particle at position.";

View File

@@ -13,7 +13,7 @@ use pumpkin_data::translation;
use pumpkin_util::{math::vector3::Vector3, text::TextComponent};
use uuid::Uuid;
use pumpkin_world::block::entities::BlockEntity;
use crate::block::entities::BlockEntity;
const NAMES: [&str; 1] = ["summon"];

View File

@@ -8,6 +8,8 @@ use crate::server::Server;
use crate::world::World;
use args::ConsumedArgs;
use crate::block::entities::BlockEntity;
use crate::block::entities::command_block::CommandBlockEntity;
use crate::command::context::command_source::CommandSource;
use crate::entity::EntityBase;
use dispatcher::CommandError;
@@ -21,8 +23,6 @@ use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::permission::{PermissionDefault, PermissionLvl};
use pumpkin_util::text::TextComponent;
use pumpkin_util::translation::Locale;
use pumpkin_world::block::entities::BlockEntity;
use pumpkin_world::block::entities::command_block::CommandBlockEntity;
pub mod args;
pub mod argument_builder;

View File

@@ -1,11 +1,11 @@
use std::sync::Arc;
use pumpkin_data::tag;
use pumpkin_util::GameMode;
use pumpkin_world::block::entities::{
use crate::block::entities::{
BlockEntity,
sign::{DyeColor, Text},
};
use pumpkin_data::tag;
use pumpkin_util::GameMode;
use crate::{
block::{UseWithItemArgs, registry::BlockActionResult},

View File

@@ -1,7 +1,7 @@
use std::sync::{Arc, atomic::Ordering};
use crate::block::entities::{BlockEntity, sign::Text};
use pumpkin_data::item::Item;
use pumpkin_world::block::entities::{BlockEntity, sign::Text};
use crate::{
block::{UseWithItemArgs, registry::BlockActionResult},

View File

@@ -3,6 +3,8 @@ use std::sync::Arc;
use std::sync::atomic::Ordering;
use crate::block::UseWithItemArgs;
use crate::block::entities::BlockEntity;
use crate::block::entities::sign::SignBlockEntity;
use crate::block::registry::BlockActionResult;
use crate::entity::player::Player;
use crate::item::{ItemBehaviour, ItemMetadata};
@@ -17,8 +19,6 @@ use pumpkin_data::world::WorldEvent;
use pumpkin_data::{Block, tag};
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_world::block::entities::BlockEntity;
use pumpkin_world::block::entities::sign::SignBlockEntity;
use pumpkin_world::world::BlockFlags;
pub struct HoneyCombItem;

View File

@@ -1,7 +1,7 @@
use std::sync::{Arc, atomic::Ordering};
use crate::block::entities::{BlockEntity, sign::Text};
use pumpkin_data::item::Item;
use pumpkin_world::block::entities::{BlockEntity, sign::Text};
use crate::{
block::{UseWithItemArgs, registry::BlockActionResult},

View File

@@ -1,5 +1,6 @@
use std::pin::Pin;
use crate::block::entities::mob_spawner::MobSpawnerBlockEntity;
use crate::entity::player::Player;
use crate::entity::r#type::from_type;
use crate::item::{ItemBehaviour, ItemMetadata};
@@ -10,7 +11,6 @@ use pumpkin_data::{Block, BlockDirection};
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::math::wrap_degrees;
use pumpkin_world::block::entities::mob_spawner::MobSpawnerBlockEntity;
use uuid::Uuid;
pub struct SpawnEggItem;

View File

@@ -33,6 +33,8 @@ use crate::plugin::player::player_move::PlayerMoveEvent;
use crate::plugin::player::player_toggle_flight_event::PlayerToggleFlightEvent;
use crate::plugin::player::player_toggle_sneak_event::PlayerToggleSneakEvent;
use crate::block::entities::command_block::CommandBlockEntity;
use crate::block::entities::sign::SignBlockEntity;
use crate::plugin::player::player_toggle_sprint_event::PlayerToggleSprintEvent;
use crate::server::{Server, seasonal_events};
use crate::world::{World, chunker};
@@ -71,8 +73,6 @@ use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::math::{polynomial_rolling_hash, position::BlockPos, wrap_degrees};
use pumpkin_util::text::color::NamedColor;
use pumpkin_util::{GameMode, text::TextComponent};
use pumpkin_world::block::entities::command_block::CommandBlockEntity;
use pumpkin_world::block::entities::sign::SignBlockEntity;
use pumpkin_world::world::BlockFlags;
use tokio::sync::Mutex;

View File

@@ -1,7 +1,7 @@
use pumpkin_data::BlockDirection as InternalBlockDirection;
use pumpkin_data::block_state::PistonBehavior;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::world::{BlockFlags, SimpleWorld};
use pumpkin_world::world::BlockFlags;
use std::sync::Arc;
use wasmtime::component::Resource;

View File

@@ -1,3 +1,5 @@
use crate::block::entities::BlockEntity;
use dashmap::DashMap;
use pumpkin_protocol::bedrock::client::level_event::{CLevelEvent, LevelEvent};
use pumpkin_protocol::codec::data_component::data_to_proto_sound;
use std::pin::Pin;
@@ -18,9 +20,7 @@ pub mod time;
use crate::block::RandomTickArgs;
use crate::world::chunker::is_within_view_distance;
use crate::world::{chunker::get_view_distance, loot::LootContextParameters};
use crate::{
block::BlockEvent, entity::experience_orb::ExperienceOrbEntity, entity::item::ItemEntity,
};
use crate::{block::BlockEvent, entity::item::ItemEntity};
use crate::{
block::{
self,
@@ -120,14 +120,13 @@ use pumpkin_util::{
random::{RandomImpl, get_seed, xoroshiro128::Xoroshiro},
};
use pumpkin_world::inventory::Clearable;
use pumpkin_world::world::{GetBlockError, WorldFuture};
use pumpkin_world::world::GetBlockError;
use pumpkin_world::{
BlockStateId, CURRENT_BEDROCK_MC_VERSION, biome, block::entities::BlockEntity,
chunk::io::Dirtiable, inventory::Inventory, world::SimpleWorld,
BlockStateId, CURRENT_BEDROCK_MC_VERSION, biome, chunk::io::Dirtiable, inventory::Inventory,
};
use pumpkin_world::{chunk::ChunkData, world::BlockAccessor};
use pumpkin_world::{level::Level, tick::TickPriority};
use pumpkin_world::{world::BlockFlags, world_info::LevelData};
pub use pumpkin_world::{world::BlockFlags, world_info::LevelData};
use rand::seq::SliceRandom;
use rand::{RngExt, rng};
use scoreboard::Scoreboard;
@@ -213,6 +212,7 @@ pub struct World {
pub dragon_fight: Option<Mutex<dragon_fight::DragonFight>>,
pub spawn_state: ArcSwap<SpawnState>,
pub active_chunks: ArcSwap<FxHashSet<Vector2<i32>>>,
pub block_entities: DashMap<BlockPos, Arc<dyn BlockEntity>>,
}
impl PartialEq for World {
@@ -260,6 +260,7 @@ impl World {
spawn_state: ArcSwap::new(Arc::new(SpawnState::empty())),
active_chunks: ArcSwap::new(Arc::new(FxHashSet::default())),
server,
block_entities: DashMap::new(),
}
}
@@ -777,6 +778,7 @@ impl World {
.await;
}
#[expect(clippy::too_many_lines)]
pub async fn tick(self: &Arc<Self>, server: Arc<Server>) {
let start = tokio::time::Instant::now();
@@ -853,6 +855,28 @@ impl World {
}
let entity_elapsed = entity_start.elapsed();
let block_entity_start = tokio::time::Instant::now();
let block_entities: Vec<Arc<dyn BlockEntity>> = self
.block_entities
.iter()
.map(|e| e.value().clone())
.collect();
let block_entity_count = block_entities.len();
let mut block_entity_tasks = tokio::task::JoinSet::new();
for block_entity in block_entities {
let world_clone = self.clone();
block_entity_tasks.spawn(async move {
block_entity.tick(&world_clone).await;
});
}
while let Some(res) = block_entity_tasks.join_next().await {
if let Err(e) = res {
error!("Block entity tick panicked: {:?}", e);
}
}
let block_entity_elapsed = block_entity_start.elapsed();
//self.level.chunk_loading.lock().unwrap().send_change();
// Tick the End dragon fight (only on THE_END worlds).
@@ -863,13 +887,15 @@ impl World {
let total_elapsed = start.elapsed();
if total_elapsed.as_millis() > 50 {
debug!(
"Slow Tick [{}ms]: Chunks: {:?} | Players({}): {:?} | Entities({}): {:?}",
"Slow Tick [{}ms]: Chunks: {:?} | Players({}): {:?} | Entities({}): {:?} | Block Entities({}): {:?}",
total_elapsed.as_millis(),
chunk_elapsed,
player_count,
player_elapsed,
entity_count,
entity_elapsed,
block_entity_count,
block_entity_elapsed,
);
}
}
@@ -974,7 +1000,6 @@ impl World {
}
}
#[expect(clippy::too_many_lines)]
pub async fn tick_chunks(self: &Arc<Self>) {
let active_chunks = self.active_chunks.load();
let tick_data = self.level.get_tick_data(&active_chunks);
@@ -1031,16 +1056,6 @@ impl World {
}
}
let world_simple: Arc<dyn SimpleWorld> = self.clone();
let mut block_entity_tasks = JoinSet::new();
for block_entity in tick_data.block_entities {
let world_simple = world_simple.clone();
block_entity_tasks.spawn(async move {
block_entity.tick(&world_simple).await;
});
}
while block_entity_tasks.join_next().await.is_some() {}
let spawn_state = self.spawn_state.load();
// TODO gamerule this.spawnEnemies || this.spawnFriendlies
@@ -1486,6 +1501,14 @@ impl World {
spawn_for_chunk(self, chunk_pos, chunk, spawn_state, spawn_list).await;
}
pub async fn get_world_age(&self) -> i64 {
self.level_time.lock().await.world_age
}
pub async fn get_time_of_day(&self) -> i64 {
self.level_time.lock().await.time_of_day
}
pub async fn set_time_of_day(&self, time: i64) {
let mut level_time = self.level_time.lock().await;
level_time.set_time(time);
@@ -3306,8 +3329,7 @@ impl World {
&& old_block.default_state.block_entity_type != u16::MAX
&& let Some(entity) = self.get_block_entity(position).await
{
let world: Arc<dyn SimpleWorld> = self.clone();
entity.on_block_replaced(world, *position).await;
entity.on_block_replaced(self.clone(), *position).await;
self.remove_block_entity(position).await;
}
@@ -3843,6 +3865,32 @@ impl World {
}
}
pub async fn update_from_neighbor_shapes(
self: &Arc<Self>,
state_id: BlockStateId,
pos: &BlockPos,
) -> BlockStateId {
let mut current_state_id = state_id;
let block = Block::from_state_id(state_id);
for direction in BlockDirection::all() {
let neighbor_pos = pos.offset(direction.to_offset());
let neighbor_state_id = self.get_block_state_id(&neighbor_pos).await;
current_state_id = self
.block_registry
.get_state_for_neighbor_update(
self,
block,
current_state_id,
pos,
direction,
&neighbor_pos,
neighbor_state_id,
)
.await;
}
current_state_id
}
pub async fn replace_with_state_for_neighbor_update(
self: &Arc<Self>,
block_pos: &BlockPos,
@@ -3882,12 +3930,11 @@ impl World {
}
}
#[expect(clippy::unused_async)]
pub async fn get_block_entity(&self, block_pos: &BlockPos) -> Option<Arc<dyn BlockEntity>> {
self.level
.get_or_fetch_chunk(block_pos.chunk_position(), |chunk| {
chunk.block_entities.lock().unwrap().get(block_pos).cloned()
})
.await
self.block_entities
.get(block_pos)
.map(|e| e.value().clone())
}
pub async fn add_block_entity(&self, block_entity: Arc<dyn BlockEntity>) {
@@ -3909,32 +3956,22 @@ impl World {
.await;
}
self.block_entities.insert(block_pos, block_entity.clone());
self.level
.get_or_fetch_chunk(chunk_pos, |chunk| {
chunk
.block_entities
.lock()
.unwrap()
.insert(block_pos, block_entity.clone());
chunk.mark_dirty(true);
})
.await;
}
pub async fn remove_block_entity(&self, block_pos: &BlockPos) {
self.level
.get_or_fetch_chunk(block_pos.chunk_position(), |chunk| {
if chunk
.block_entities
.lock()
.unwrap()
.remove(block_pos)
.is_some()
{
if self.block_entities.remove(block_pos).is_some() {
self.level
.get_or_fetch_chunk(block_pos.chunk_position(), |chunk| {
chunk.mark_dirty(true);
}
})
.await;
})
.await;
}
}
pub async fn update_block_entity(&self, block_entity: &Arc<dyn BlockEntity>) {
@@ -4237,178 +4274,6 @@ impl World {
}
}
impl pumpkin_world::world::SimpleWorld for World {
fn set_block_state(
self: Arc<Self>,
position: &BlockPos,
block_state_id: BlockStateId,
flags: BlockFlags,
) -> WorldFuture<'_, BlockStateId> {
Box::pin(async move { Self::set_block_state(&self, position, block_state_id, flags).await })
}
fn update_neighbor<'a>(
self: Arc<Self>,
neighbor_block_pos: &'a BlockPos,
source_block: &'a pumpkin_data::Block,
) -> WorldFuture<'a, ()> {
Box::pin(async move {
Self::update_neighbor(&self, neighbor_block_pos, source_block).await;
})
}
fn update_neighbors(
self: Arc<Self>,
block_pos: &BlockPos,
except: Option<BlockDirection>,
) -> WorldFuture<'_, ()> {
Box::pin(async move {
Self::update_neighbors(&self, block_pos, except).await;
})
}
fn is_space_empty(&self, bounding_box: BoundingBox) -> WorldFuture<'_, bool> {
Box::pin(async move { self.is_space_empty(bounding_box).await })
}
fn add_synced_block_event(&self, pos: BlockPos, r#type: u8, data: u8) -> WorldFuture<'_, ()> {
Box::pin(async move {
self.add_synced_block_event(pos, r#type, data).await;
})
}
fn sync_world_event(
&self,
world_event: WorldEvent,
position: BlockPos,
data: i32,
) -> WorldFuture<'_, ()> {
Box::pin(async move {
self.sync_world_event(world_event, position, data).await;
})
}
fn spawn_from_type(
self: Arc<Self>,
entity_type: &'static EntityType,
position: Vector3<f64>,
) -> WorldFuture<'static, ()> {
Box::pin(async move {
let mob = from_type(entity_type, position, &self, Uuid::new_v4()).await;
self.spawn_entity(mob).await;
})
}
fn remove_block_entity<'a>(&'a self, block_pos: &'a BlockPos) -> WorldFuture<'a, ()> {
Box::pin(async move {
self.remove_block_entity(block_pos).await;
})
}
fn get_block_entity<'a>(
&'a self,
block_pos: &'a BlockPos,
) -> WorldFuture<'a, Option<Arc<dyn BlockEntity>>> {
Box::pin(async move { self.get_block_entity(block_pos).await })
}
fn get_world_age(&self) -> WorldFuture<'_, i64> {
Box::pin(async move {
// Note: MutexGuard must be released before returning the future's result.
let level_time_guard = self.level_time.lock().await;
level_time_guard.world_age
})
}
fn get_time_of_day(&self) -> WorldFuture<'_, i64> {
Box::pin(async move {
let level_time_guard = self.level_time.lock().await;
level_time_guard.query_daytime()
})
}
fn get_level(&self) -> WorldFuture<'_, &Arc<Level>> {
Box::pin(async move { &self.level })
}
fn get_dimension(&self) -> WorldFuture<'_, &Dimension> {
Box::pin(async move { &self.dimension })
}
fn play_sound<'a>(
&'a self,
sound: Sound,
category: SoundCategory,
position: &'a Vector3<f64>,
) -> WorldFuture<'a, ()> {
Box::pin(async move {
self.play_sound(sound, category, position).await;
})
}
fn play_sound_fine<'a>(
&'a self,
sound: Sound,
category: SoundCategory,
position: &'a Vector3<f64>,
volume: f32,
pitch: f32,
) -> WorldFuture<'a, ()> {
Box::pin(async move {
self.play_sound_fine(sound, category, position, volume, pitch)
.await;
})
}
fn scatter_inventory<'a>(
self: Arc<Self>,
position: &'a BlockPos,
inventory: &'a Arc<dyn Inventory>,
) -> WorldFuture<'a, ()> {
Box::pin(async move {
Self::scatter_inventory(&self, position, inventory).await;
})
}
fn spawn_experience_orbs(
self: Arc<Self>,
position: Vector3<f64>,
amount: u32,
) -> WorldFuture<'static, ()> {
Box::pin(async move {
ExperienceOrbEntity::spawn(&self, position, amount).await;
})
}
fn update_from_neighbor_shapes(
self: Arc<Self>,
block_state_id: BlockStateId,
position: &BlockPos,
) -> WorldFuture<'_, BlockStateId> {
Box::pin(async move {
let block = Block::from_state_id(block_state_id);
let mut state_id = block_state_id;
for direction in BlockDirection::update_order() {
let neighbor_pos = position.offset(direction.to_offset());
let neighbor_state_id = self.get_block_state_id(&neighbor_pos).await;
state_id = self
.block_registry
.get_state_for_neighbor_update(
&self,
block,
state_id,
position,
direction,
&neighbor_pos,
neighbor_state_id,
)
.await;
}
state_id
})
}
}
impl BlockAccessor for World {
fn get_block<'a>(
&'a self,