mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
refactor: move block entities from pumpkin-world -> pumpkin
This commit is contained in:
425
Cargo.lock
generated
425
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
|
||||
|
||||
136
pumpkin-inventory/src/beacon_screen_handler.rs
Normal file
136
pumpkin-inventory/src/beacon_screen_handler.rs
Normal 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
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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())?;
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
12
pumpkin-protocol/src/java/server/play/set_beacon.rs
Normal file
12
pumpkin-protocol/src/java/server/play/set_beacon.rs
Normal 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>,
|
||||
}
|
||||
@@ -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
|
||||
|
||||
11
pumpkin-world/src/block/entities.rs
Normal file
11
pumpkin-world/src/block/entities.rs
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 {})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(),
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
69
pumpkin/src/block/blocks/beacon.rs
Normal file
69
pumpkin/src/block/blocks/beacon.rs
Normal 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
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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::{
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) =
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>);
|
||||
|
||||
@@ -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::{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -97,3 +97,5 @@ pub mod tnt;
|
||||
|
||||
// Misc / abstract
|
||||
pub mod abstract_wall_mounting;
|
||||
|
||||
pub mod beacon;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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::{
|
||||
|
||||
@@ -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::{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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::{
|
||||
|
||||
@@ -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};
|
||||
|
||||
|
||||
@@ -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;
|
||||
345
pumpkin/src/block/entities/beacon.rs
Normal file
345
pumpkin/src/block/entities/beacon.rs
Normal 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();
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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,
|
||||
@@ -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();
|
||||
@@ -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(
|
||||
@@ -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 {
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::inventory::Inventory;
|
||||
use pumpkin_data::block_properties::BlockProperties;
|
||||
use pumpkin_world::inventory::Inventory;
|
||||
|
||||
use std::{
|
||||
array::from_fn,
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
@@ -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,
|
||||
@@ -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;
|
||||
207
pumpkin/src/block/entities/mod.rs
Normal file
207
pumpkin/src/block/entities/mod.rs
Normal 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)
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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
|
||||
@@ -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();
|
||||
@@ -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,
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
83
pumpkin/src/block/viewer.rs
Normal file
83
pumpkin/src/block/viewer.rs
Normal 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 {})
|
||||
}
|
||||
}
|
||||
@@ -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.";
|
||||
|
||||
@@ -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"];
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user