Pumpkin/crates/pumpkin/src/block/entities/shulker_box.rs

253 lines
6.9 KiB
Rust

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<Box<dyn Future<Output = ()> + Send + 'a>> {
self.write_inventory_nbt(nbt, true)
}
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)
.await;
})
}
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 {
// Do nothing
})
}
fn get_inventory(self: Arc<Self>) -> Option<Arc<dyn Inventory>> {
Some(self)
}
fn is_dirty(&self) -> bool {
self.dirty.load(Ordering::Relaxed)
}
fn clear_dirty(&self) {
self.dirty.store(false, Ordering::Relaxed);
}
fn chunk_data_nbt(&self) -> Option<NbtCompound> {
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<World>,
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<World>,
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<World>,
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<World>) {
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<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move {
let mut items = self.items.write().await;
items.fill_with(|| ItemStack::EMPTY.clone());
self.mark_dirty();
})
}
}