use pumpkin_data::item_stack::ItemStack; use pumpkin_data::sound::{Sound, SoundCategory}; use pumpkin_nbt::compound::NbtCompound; use pumpkin_util::math::position::BlockPos; use std::any::Any; use std::future::Future; use std::pin::Pin; use std::sync::atomic::{AtomicBool, Ordering}; use std::{array::from_fn, sync::Arc}; use tokio::sync::RwLock; use crate::block::entities::BlockEntity; use crate::block::viewer::{ ViewerCountListener, ViewerCountTracker, ViewerCountTrackerExt, ViewerFuture, }; use crate::world::World; use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture, sync_write_items_to_nbt}; pub struct ShulkerBoxBlockEntity { pub position: BlockPos, pub items: RwLock<[ItemStack; Self::INVENTORY_SIZE]>, pub dirty: AtomicBool, // Viewer pub viewers: ViewerCountTracker, } impl BlockEntity for ShulkerBoxBlockEntity { fn resource_location(&self) -> &'static str { Self::ID } fn get_position(&self) -> BlockPos { self.position } fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self where Self: Sized, { let shulker_box = Self { position, items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())), dirty: AtomicBool::new(false), viewers: ViewerCountTracker::new(), }; shulker_box.read_data(nbt, &mut *shulker_box.items.blocking_write()); shulker_box } fn write_nbt<'a>( &'a self, nbt: &'a mut NbtCompound, ) -> Pin + Send + 'a>> { self.write_inventory_nbt(nbt, true) } fn tick<'a>(&'a self, world: &'a Arc) -> Pin + Send + 'a>> { Box::pin(async move { self.viewers .update_viewer_count::(self, world, &self.position) .await; }) } fn on_block_replaced<'a>( self: Arc, _world: Arc, _position: BlockPos, ) -> Pin + Send + 'a>> where Self: 'a, { Box::pin(async move { // Do nothing }) } fn get_inventory(self: Arc) -> Option> { Some(self) } fn is_dirty(&self) -> bool { self.dirty.load(Ordering::Relaxed) } fn clear_dirty(&self) { self.dirty.store(false, Ordering::Relaxed); } fn chunk_data_nbt(&self) -> Option { let mut nbt = NbtCompound::new(); let items = futures::executor::block_on(self.items.read()); sync_write_items_to_nbt(items.as_slice(), &mut nbt); Some(nbt) } fn as_any(&self) -> &dyn std::any::Any { self } } impl ViewerCountListener for ShulkerBoxBlockEntity { fn on_container_open<'a>( &'a self, world: &'a Arc, position: &'a BlockPos, ) -> ViewerFuture<'a, ()> { Box::pin(async move { Self::play_sound(world, position, 1); // TODO: this.world.emitGameEvent(player, GameEvent.CONTAINER_OPEN, this.pos); }) } fn on_container_close<'a>( &'a self, world: &'a Arc, position: &'a BlockPos, ) -> ViewerFuture<'a, ()> { Box::pin(async move { Self::play_sound(world, position, 0); // TODO: this.world.emitGameEvent(player, GameEvent.CONTAINER_CLOSE, this.pos); }) } fn on_viewer_count_update<'a>( &'a self, world: &'a Arc, position: &'a BlockPos, _old: u16, new: u16, ) -> ViewerFuture<'a, ()> { Box::pin(async move { world .add_synced_block_event(*position, Self::OPEN_ANIMATION_EVENT_TYPE, new as u8) .await; }) } } impl ShulkerBoxBlockEntity { pub const INVENTORY_SIZE: usize = 27; pub const OPEN_ANIMATION_EVENT_TYPE: u8 = 1; pub const ID: &'static str = "minecraft:shulker_box"; #[must_use] pub fn new(position: BlockPos) -> Self { Self { position, items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())), dirty: AtomicBool::new(false), viewers: ViewerCountTracker::new(), } } pub fn update_viewers(&self, world: &Arc) { let viewer_count = self.viewers.current.load(Ordering::Relaxed); Self::play_sound(world, &self.position, i32::from(viewer_count)); } fn play_sound(world: &World, position: &BlockPos, viewer_count: i32) { let sound = if viewer_count > 0 { Sound::BlockShulkerBoxOpen } else { Sound::BlockShulkerBoxClose }; world.play_sound(sound, SoundCategory::Blocks, &position.to_f64()); } } impl Inventory for ShulkerBoxBlockEntity { fn size(&self) -> usize { Self::INVENTORY_SIZE } fn is_empty(&self) -> InventoryFuture<'_, bool> { Box::pin(async move { let items = self.items.read().await; items.iter().all(ItemStack::is_empty) }) } fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { let items = self.items.read().await; items[slot].clone() }) } fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { let mut items = self.items.write().await; let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone()); self.mark_dirty(); removed }) } fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { Box::pin(async move { let mut items = self.items.write().await; let res = if !items[slot].is_empty() && amount > 0 { items[slot].split(amount) } else { ItemStack::EMPTY.clone() }; self.mark_dirty(); res }) } fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { Box::pin(async move { let mut items = self.items.write().await; items[slot] = stack; self.mark_dirty(); }) } fn on_open(&self) -> InventoryFuture<'_, ()> { Box::pin(async move { self.viewers.open_container(); }) } fn on_close(&self) -> InventoryFuture<'_, ()> { Box::pin(async move { self.viewers.close_container(); }) } fn mark_dirty(&self) { self.dirty.store(true, Ordering::Relaxed); } fn as_any(&self) -> &dyn Any { self } } impl Clearable for ShulkerBoxBlockEntity { fn clear(&self) -> Pin + Send + '_>> { Box::pin(async move { let mut items = self.items.write().await; items.fill_with(|| ItemStack::EMPTY.clone()); self.mark_dirty(); }) } }