mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
feat: Implement EnderChest (#1163)
* feat: Implement ender chest * fix: Register block entity * fix: Fix formatting
This commit is contained in:
@@ -72,14 +72,14 @@ impl Inventory for DoubleInventory {
|
||||
self.second.mark_dirty();
|
||||
}
|
||||
|
||||
fn on_open(&self) {
|
||||
self.first.on_open();
|
||||
self.second.on_open();
|
||||
async fn on_open(&self) {
|
||||
self.first.on_open().await;
|
||||
self.second.on_open().await;
|
||||
}
|
||||
|
||||
fn on_close(&self) {
|
||||
self.first.on_close();
|
||||
self.second.on_close();
|
||||
async fn on_close(&self) {
|
||||
self.first.on_close().await;
|
||||
self.second.on_close().await;
|
||||
}
|
||||
|
||||
fn is_valid_slot_for(&self, slot: usize, stack: &ItemStack) -> bool {
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
slot::NormalSlot,
|
||||
};
|
||||
|
||||
pub fn create_generic_9x3(
|
||||
pub async fn create_generic_9x3(
|
||||
sync_id: u8,
|
||||
player_inventory: &Arc<PlayerInventory>,
|
||||
inventory: Arc<dyn Inventory>,
|
||||
@@ -23,9 +23,10 @@ pub fn create_generic_9x3(
|
||||
3,
|
||||
9,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn create_generic_9x6(
|
||||
pub async fn create_generic_9x6(
|
||||
sync_id: u8,
|
||||
player_inventory: &Arc<PlayerInventory>,
|
||||
inventory: Arc<dyn Inventory>,
|
||||
@@ -38,9 +39,10 @@ pub fn create_generic_9x6(
|
||||
6,
|
||||
9,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn create_generic_3x3(
|
||||
pub async fn create_generic_3x3(
|
||||
sync_id: u8,
|
||||
player_inventory: &Arc<PlayerInventory>,
|
||||
inventory: Arc<dyn Inventory>,
|
||||
@@ -53,9 +55,10 @@ pub fn create_generic_3x3(
|
||||
3,
|
||||
3,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn create_hopper(
|
||||
pub async fn create_hopper(
|
||||
sync_id: u8,
|
||||
player_inventory: &Arc<PlayerInventory>,
|
||||
inventory: Arc<dyn Inventory>,
|
||||
@@ -68,6 +71,7 @@ pub fn create_hopper(
|
||||
1,
|
||||
5,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub struct GenericContainerScreenHandler {
|
||||
@@ -78,7 +82,7 @@ pub struct GenericContainerScreenHandler {
|
||||
}
|
||||
|
||||
impl GenericContainerScreenHandler {
|
||||
fn new(
|
||||
async fn new(
|
||||
screen_type: WindowType,
|
||||
sync_id: u8,
|
||||
player_inventory: &Arc<PlayerInventory>,
|
||||
@@ -94,7 +98,8 @@ impl GenericContainerScreenHandler {
|
||||
};
|
||||
|
||||
// TODO: Add player entity as a parameter
|
||||
inventory.on_open();
|
||||
inventory.on_open().await;
|
||||
|
||||
handler.add_inventory_slots();
|
||||
let player_inventory: Arc<dyn Inventory> = player_inventory.clone();
|
||||
handler.add_player_slots(&player_inventory);
|
||||
@@ -118,7 +123,7 @@ impl GenericContainerScreenHandler {
|
||||
impl ScreenHandler for GenericContainerScreenHandler {
|
||||
async fn on_closed(&mut self, player: &dyn InventoryPlayer) {
|
||||
self.default_on_closed(player).await;
|
||||
self.inventory.on_close();
|
||||
self.inventory.on_close().await;
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
|
||||
110
pumpkin-inventory/src/player/ender_chest_inventory.rs
Normal file
110
pumpkin-inventory/src/player/ender_chest_inventory.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
use std::{any::Any, array::from_fn, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use pumpkin_world::{
|
||||
block::viewer::ViewerCountTracker,
|
||||
inventory::{Clearable, Inventory, split_stack},
|
||||
item::ItemStack,
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EnderChestInventory {
|
||||
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE],
|
||||
pub tracker: Mutex<Option<Arc<ViewerCountTracker>>>,
|
||||
}
|
||||
|
||||
impl Default for EnderChestInventory {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl EnderChestInventory {
|
||||
pub const INVENTORY_SIZE: usize = 27;
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
|
||||
tracker: Mutex::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set_tracker(&self, tracker: Arc<ViewerCountTracker>) {
|
||||
self.tracker.lock().await.replace(tracker);
|
||||
}
|
||||
|
||||
pub async fn has_tracker(&self) -> bool {
|
||||
self.tracker.lock().await.is_some()
|
||||
}
|
||||
|
||||
pub async fn is_tracker(&self, tracker: &Arc<ViewerCountTracker>) -> bool {
|
||||
if let Some(value) = self.tracker.lock().await.as_ref() {
|
||||
return Arc::ptr_eq(value, tracker);
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Inventory for EnderChestInventory {
|
||||
fn size(&self) -> usize {
|
||||
self.items.len()
|
||||
}
|
||||
|
||||
async fn is_empty(&self) -> bool {
|
||||
for slot in self.items.iter() {
|
||||
if !slot.lock().await.is_empty() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
async fn get_stack(&self, slot: usize) -> Arc<Mutex<ItemStack>> {
|
||||
self.items[slot].clone()
|
||||
}
|
||||
|
||||
async fn remove_stack(&self, slot: usize) -> ItemStack {
|
||||
let mut removed = ItemStack::EMPTY.clone();
|
||||
let mut guard = self.items[slot].lock().await;
|
||||
std::mem::swap(&mut removed, &mut *guard);
|
||||
removed
|
||||
}
|
||||
|
||||
async fn remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack {
|
||||
split_stack(&self.items, slot, amount).await
|
||||
}
|
||||
|
||||
async fn set_stack(&self, slot: usize, stack: ItemStack) {
|
||||
*self.items[slot].lock().await = stack;
|
||||
}
|
||||
|
||||
async fn on_open(&self) {
|
||||
if let Some(tracker) = self.tracker.lock().await.as_ref() {
|
||||
tracker.open_container();
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_close(&self) {
|
||||
if let Some(tracker) = self.tracker.lock().await.as_ref() {
|
||||
tracker.close_container();
|
||||
}
|
||||
}
|
||||
|
||||
fn mark_dirty(&self) {}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Clearable for EnderChestInventory {
|
||||
async fn clear(&self) {
|
||||
for slot in self.items.iter() {
|
||||
*slot.lock().await = ItemStack::EMPTY.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod ender_chest_inventory;
|
||||
pub mod player_inventory;
|
||||
pub mod player_screen_handler;
|
||||
|
||||
@@ -30,11 +30,11 @@ use super::BlockEntity;
|
||||
#[derive(Debug)]
|
||||
pub struct BarrelBlockEntity {
|
||||
pub position: BlockPos,
|
||||
pub items: [Arc<Mutex<ItemStack>>; 27],
|
||||
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE],
|
||||
pub dirty: AtomicBool,
|
||||
|
||||
// Viewer
|
||||
pub viewers: ViewerCountTracker,
|
||||
viewers: ViewerCountTracker,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -102,7 +102,9 @@ impl ViewerCountListener for BarrelBlockEntity {
|
||||
}
|
||||
|
||||
impl BarrelBlockEntity {
|
||||
pub const INVENTORY_SIZE: usize = 27;
|
||||
pub const ID: &'static str = "minecraft:barrel";
|
||||
|
||||
pub fn new(position: BlockPos) -> Self {
|
||||
Self {
|
||||
position,
|
||||
@@ -186,11 +188,11 @@ impl Inventory for BarrelBlockEntity {
|
||||
*self.items[slot].lock().await = stack;
|
||||
}
|
||||
|
||||
fn on_open(&self) {
|
||||
async fn on_open(&self) {
|
||||
self.viewers.open_container();
|
||||
}
|
||||
|
||||
fn on_close(&self) {
|
||||
async fn on_close(&self) {
|
||||
self.viewers.close_container();
|
||||
}
|
||||
|
||||
|
||||
@@ -31,11 +31,11 @@ use super::BlockEntity;
|
||||
#[derive(Debug)]
|
||||
pub struct ChestBlockEntity {
|
||||
pub position: BlockPos,
|
||||
pub items: [Arc<Mutex<ItemStack>>; 27],
|
||||
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE],
|
||||
pub dirty: AtomicBool,
|
||||
|
||||
// Viewer
|
||||
pub viewers: ViewerCountTracker,
|
||||
viewers: ViewerCountTracker,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -113,6 +113,7 @@ impl ViewerCountListener for ChestBlockEntity {
|
||||
}
|
||||
|
||||
impl ChestBlockEntity {
|
||||
pub const INVENTORY_SIZE: usize = 27;
|
||||
pub const LID_ANIMATION_EVENT_TYPE: u8 = 1;
|
||||
pub const ID: &'static str = "minecraft:chest";
|
||||
|
||||
@@ -194,11 +195,11 @@ impl Inventory for ChestBlockEntity {
|
||||
*self.items[slot].lock().await = stack;
|
||||
}
|
||||
|
||||
fn on_open(&self) {
|
||||
async fn on_open(&self) {
|
||||
self.viewers.open_container();
|
||||
}
|
||||
|
||||
fn on_close(&self) {
|
||||
async fn on_close(&self) {
|
||||
self.viewers.close_container();
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ use crate::{
|
||||
#[derive(Debug)]
|
||||
pub struct ChiseledBookshelfBlockEntity {
|
||||
pub position: BlockPos,
|
||||
pub items: [Arc<Mutex<ItemStack>>; 6],
|
||||
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE],
|
||||
pub last_interacted_slot: AtomicI8,
|
||||
pub dirty: AtomicBool,
|
||||
}
|
||||
@@ -81,6 +81,7 @@ impl BlockEntity for ChiseledBookshelfBlockEntity {
|
||||
}
|
||||
|
||||
impl ChiseledBookshelfBlockEntity {
|
||||
pub const INVENTORY_SIZE: usize = 6;
|
||||
pub const ID: &'static str = "minecraft:chiseled_bookshelf";
|
||||
|
||||
pub fn new(position: BlockPos) -> Self {
|
||||
|
||||
@@ -13,7 +13,7 @@ use tokio::sync::{Mutex, MutexGuard};
|
||||
#[derive(Debug)]
|
||||
pub struct DropperBlockEntity {
|
||||
pub position: BlockPos,
|
||||
pub items: [Arc<Mutex<ItemStack>>; 9],
|
||||
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE],
|
||||
pub dirty: AtomicBool,
|
||||
}
|
||||
|
||||
@@ -62,7 +62,9 @@ impl BlockEntity for DropperBlockEntity {
|
||||
}
|
||||
|
||||
impl DropperBlockEntity {
|
||||
pub const INVENTORY_SIZE: usize = 9;
|
||||
pub const ID: &'static str = "minecraft:dropper";
|
||||
|
||||
pub fn new(position: BlockPos) -> Self {
|
||||
Self {
|
||||
position,
|
||||
|
||||
107
pumpkin-world/src/block/entities/ender_chest.rs
Normal file
107
pumpkin-world/src/block/entities/ender_chest.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
use async_trait::async_trait;
|
||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||
use pumpkin_nbt::compound::NbtCompound;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_util::random::xoroshiro128::Xoroshiro;
|
||||
use pumpkin_util::random::{RandomImpl, get_seed};
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::block::viewer::{ViewerCountListener, ViewerCountTracker};
|
||||
use crate::world::SimpleWorld;
|
||||
|
||||
use super::BlockEntity;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EnderChestBlockEntity {
|
||||
pub position: BlockPos,
|
||||
|
||||
// Viewer
|
||||
viewers: Arc<ViewerCountTracker>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl BlockEntity for EnderChestBlockEntity {
|
||||
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,
|
||||
{
|
||||
Self {
|
||||
position,
|
||||
viewers: Arc::new(ViewerCountTracker::new()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn write_nbt(&self, _nbt: &mut NbtCompound) {}
|
||||
|
||||
async fn tick(&self, world: Arc<dyn SimpleWorld>) {
|
||||
self.viewers
|
||||
.update_viewer_count::<EnderChestBlockEntity>(self, world, &self.position)
|
||||
.await;
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ViewerCountListener for EnderChestBlockEntity {
|
||||
async fn on_container_open(&self, world: &Arc<dyn SimpleWorld>, _position: &BlockPos) {
|
||||
self.play_sound(world, Sound::BlockEnderChestOpen).await;
|
||||
}
|
||||
|
||||
async fn on_container_close(&self, world: &Arc<dyn SimpleWorld>, _position: &BlockPos) {
|
||||
self.play_sound(world, Sound::BlockEnderChestClose).await;
|
||||
}
|
||||
|
||||
async fn on_viewer_count_update(
|
||||
&self,
|
||||
world: &Arc<dyn SimpleWorld>,
|
||||
position: &BlockPos,
|
||||
_old: u16,
|
||||
new: u16,
|
||||
) {
|
||||
world
|
||||
.add_synced_block_event(*position, Self::LID_ANIMATION_EVENT_TYPE, new as u8)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl EnderChestBlockEntity {
|
||||
pub const LID_ANIMATION_EVENT_TYPE: u8 = 1;
|
||||
pub const ID: &'static str = "minecraft:ender_chest";
|
||||
|
||||
pub fn new(position: BlockPos) -> Self {
|
||||
Self {
|
||||
position,
|
||||
viewers: Arc::new(ViewerCountTracker::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_tracker(&self) -> Arc<ViewerCountTracker> {
|
||||
self.viewers.clone()
|
||||
}
|
||||
|
||||
async fn play_sound(&self, world: &Arc<dyn SimpleWorld>, sound: Sound) {
|
||||
let mut rng = Xoroshiro::from_seed(get_seed());
|
||||
|
||||
world
|
||||
.play_sound_fine(
|
||||
sound,
|
||||
SoundCategory::Blocks,
|
||||
&self.position.to_centered_f64(),
|
||||
0.5,
|
||||
rng.next_f32() * 0.1 + 0.9,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ pub struct FurnaceBlockEntity {
|
||||
pub lit_time_remaining: AtomicU16,
|
||||
pub lit_total_time: AtomicU16,
|
||||
|
||||
pub items: [Arc<Mutex<ItemStack>>; 3],
|
||||
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE],
|
||||
}
|
||||
|
||||
impl FurnaceBlockEntity {
|
||||
@@ -347,7 +347,9 @@ impl BlockEntity for FurnaceBlockEntity {
|
||||
}
|
||||
|
||||
impl FurnaceBlockEntity {
|
||||
pub const INVENTORY_SIZE: usize = 3;
|
||||
pub const ID: &'static str = "minecraft:furnace";
|
||||
|
||||
pub fn new(position: BlockPos) -> Self {
|
||||
Self {
|
||||
position,
|
||||
|
||||
@@ -19,7 +19,7 @@ use tokio::sync::Mutex;
|
||||
#[derive(Debug)]
|
||||
pub struct HopperBlockEntity {
|
||||
pub position: BlockPos,
|
||||
pub items: [Arc<Mutex<ItemStack>>; 5],
|
||||
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE],
|
||||
pub dirty: AtomicBool,
|
||||
pub facing: HopperFacing,
|
||||
pub cooldown_time: AtomicI32,
|
||||
@@ -117,7 +117,9 @@ impl BlockEntity for HopperBlockEntity {
|
||||
}
|
||||
|
||||
impl HopperBlockEntity {
|
||||
pub const INVENTORY_SIZE: usize = 5;
|
||||
pub const ID: &'static str = "minecraft:hopper";
|
||||
|
||||
pub fn new(position: BlockPos, facing: HopperFacing) -> Self {
|
||||
Self {
|
||||
position,
|
||||
|
||||
@@ -13,6 +13,7 @@ use pumpkin_nbt::compound::NbtCompound;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use sign::SignBlockEntity;
|
||||
|
||||
use crate::block::entities::ender_chest::EnderChestBlockEntity;
|
||||
use crate::block::entities::hopper::HopperBlockEntity;
|
||||
use crate::block::entities::mob_spawner::MobSpawnerBlockEntity;
|
||||
use crate::block::entities::shulker_box::ShulkerBoxBlockEntity;
|
||||
@@ -29,6 +30,7 @@ pub mod command_block;
|
||||
pub mod comparator;
|
||||
pub mod dropper;
|
||||
pub mod end_portal;
|
||||
pub mod ender_chest;
|
||||
pub mod furnace;
|
||||
pub mod hopper;
|
||||
pub mod mob_spawner;
|
||||
@@ -94,6 +96,9 @@ pub fn block_entity_from_generic<T: BlockEntity>(nbt: &NbtCompound) -> T {
|
||||
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)),
|
||||
EnderChestBlockEntity::ID => {
|
||||
Arc::new(block_entity_from_generic::<EnderChestBlockEntity>(nbt))
|
||||
}
|
||||
SignBlockEntity::ID => Arc::new(block_entity_from_generic::<SignBlockEntity>(nbt)),
|
||||
BedBlockEntity::ID => Arc::new(block_entity_from_generic::<BedBlockEntity>(nbt)),
|
||||
ComparatorBlockEntity::ID => {
|
||||
|
||||
@@ -24,11 +24,11 @@ use super::BlockEntity;
|
||||
#[derive(Debug)]
|
||||
pub struct ShulkerBoxBlockEntity {
|
||||
pub position: BlockPos,
|
||||
pub items: [Arc<Mutex<ItemStack>>; 27],
|
||||
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE],
|
||||
pub dirty: AtomicBool,
|
||||
|
||||
// Viewer
|
||||
pub viewers: ViewerCountTracker,
|
||||
viewers: ViewerCountTracker,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -114,6 +114,7 @@ impl ViewerCountListener for ShulkerBoxBlockEntity {
|
||||
}
|
||||
|
||||
impl ShulkerBoxBlockEntity {
|
||||
pub const INVENTORY_SIZE: usize = 27;
|
||||
pub const OPEN_ANIMATION_EVENT_TYPE: u8 = 1;
|
||||
pub const ID: &'static str = "minecraft:shulker_box"; // TODO support multi IDs
|
||||
|
||||
@@ -176,11 +177,11 @@ impl Inventory for ShulkerBoxBlockEntity {
|
||||
*self.items[slot].lock().await = stack;
|
||||
}
|
||||
|
||||
fn on_open(&self) {
|
||||
async fn on_open(&self) {
|
||||
self.viewers.open_container();
|
||||
}
|
||||
|
||||
fn on_close(&self) {
|
||||
async fn on_close(&self) {
|
||||
self.viewers.close_container();
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::sync::{
|
||||
use async_trait::async_trait;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
|
||||
use crate::{block::entities::BlockEntity, inventory::Inventory, world::SimpleWorld};
|
||||
use crate::{block::entities::BlockEntity, world::SimpleWorld};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ViewerCountTracker {
|
||||
@@ -42,7 +42,7 @@ impl ViewerCountTracker {
|
||||
world: Arc<dyn SimpleWorld>,
|
||||
position: &BlockPos,
|
||||
) where
|
||||
T: BlockEntity + Inventory + ViewerCountListener + 'static,
|
||||
T: BlockEntity + ViewerCountListener + 'static,
|
||||
{
|
||||
let current = self.current.load(Ordering::Relaxed);
|
||||
let old = self.old.swap(current, Ordering::Relaxed);
|
||||
|
||||
@@ -83,8 +83,8 @@ pub trait Inventory: Send + Sync + Debug + Clearable {
|
||||
*/
|
||||
|
||||
// TODO: Add (PlayerEntity player)
|
||||
fn on_open(&self) {}
|
||||
fn on_close(&self) {}
|
||||
async fn on_open(&self) {}
|
||||
async fn on_close(&self) {}
|
||||
|
||||
/// isValid is source
|
||||
fn is_valid_slot_for(&self, _slot: usize, _stack: &ItemStack) -> bool {
|
||||
|
||||
@@ -27,11 +27,9 @@ impl ScreenHandlerFactory for BarrelScreenFactory {
|
||||
player_inventory: &Arc<PlayerInventory>,
|
||||
_player: &dyn InventoryPlayer,
|
||||
) -> Option<Arc<Mutex<dyn ScreenHandler>>> {
|
||||
Some(Arc::new(Mutex::new(create_generic_9x3(
|
||||
sync_id,
|
||||
player_inventory,
|
||||
self.0.clone(),
|
||||
))))
|
||||
Some(Arc::new(Mutex::new(
|
||||
create_generic_9x3(sync_id, player_inventory, self.0.clone()).await,
|
||||
)))
|
||||
}
|
||||
|
||||
fn get_display_name(&self) -> TextComponent {
|
||||
@@ -44,6 +42,12 @@ pub struct BarrelBlock;
|
||||
|
||||
#[async_trait]
|
||||
impl BlockBehaviour for BarrelBlock {
|
||||
async fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut props = BarrelLikeProperties::default(args.block);
|
||||
props.facing = args.player.living_entity.entity.get_facing().opposite();
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
async fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position).await
|
||||
&& let Some(inventory) = block_entity.get_inventory()
|
||||
@@ -56,12 +60,6 @@ impl BlockBehaviour for BarrelBlock {
|
||||
BlockActionResult::Success
|
||||
}
|
||||
|
||||
async fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut props = BarrelLikeProperties::default(args.block);
|
||||
props.facing = args.player.living_entity.entity.get_facing().opposite();
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
async fn placed(&self, args: PlacedArgs<'_>) {
|
||||
let barrel_block_entity = BarrelBlockEntity::new(*args.position);
|
||||
args.world
|
||||
|
||||
@@ -42,9 +42,9 @@ impl ScreenHandlerFactory for ChestScreenFactory {
|
||||
_player: &dyn InventoryPlayer,
|
||||
) -> Option<Arc<Mutex<dyn ScreenHandler>>> {
|
||||
Some(Arc::new(Mutex::new(if self.0.size() > 27 {
|
||||
create_generic_9x6(sync_id, player_inventory, self.0.clone())
|
||||
create_generic_9x6(sync_id, player_inventory, self.0.clone()).await
|
||||
} else {
|
||||
create_generic_9x3(sync_id, player_inventory, self.0.clone())
|
||||
create_generic_9x3(sync_id, player_inventory, self.0.clone()).await
|
||||
})))
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ impl BlockBehaviour for ChestBlock {
|
||||
}
|
||||
|
||||
async fn on_synced_block_event(&self, args: OnSyncedBlockEventArgs<'_>) -> bool {
|
||||
// On the server, we don't need the ChestLidAnimator because the client is responsible for that.
|
||||
// On the server, we don't need to do more because the client is responsible for that.
|
||||
args.r#type == Self::LID_ANIMATION_EVENT_TYPE
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,42 @@
|
||||
use crate::block::{BlockBehaviour, OnPlaceArgs};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::block::{
|
||||
BlockBehaviour, NormalUseArgs, OnPlaceArgs, OnSyncedBlockEventArgs, PlacedArgs,
|
||||
registry::BlockActionResult,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use pumpkin_data::block_properties::{BlockProperties, LadderLikeProperties};
|
||||
use pumpkin_inventory::{
|
||||
generic_container_screen_handler::create_generic_9x3,
|
||||
player::player_inventory::PlayerInventory,
|
||||
screen_handler::{InventoryPlayer, ScreenHandler, ScreenHandlerFactory},
|
||||
};
|
||||
use pumpkin_macros::pumpkin_block;
|
||||
use pumpkin_world::BlockStateId;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
use pumpkin_world::{
|
||||
BlockStateId, block::entities::ender_chest::EnderChestBlockEntity, inventory::Inventory,
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
struct EnderChestScreenFactory(Arc<dyn Inventory>);
|
||||
|
||||
#[async_trait]
|
||||
impl ScreenHandlerFactory for EnderChestScreenFactory {
|
||||
async fn create_screen_handler(
|
||||
&self,
|
||||
sync_id: u8,
|
||||
player_inventory: &Arc<PlayerInventory>,
|
||||
_player: &dyn InventoryPlayer,
|
||||
) -> Option<Arc<Mutex<dyn ScreenHandler>>> {
|
||||
Some(Arc::new(Mutex::new(
|
||||
create_generic_9x3(sync_id, player_inventory, self.0.clone()).await,
|
||||
)))
|
||||
}
|
||||
|
||||
fn get_display_name(&self) -> TextComponent {
|
||||
TextComponent::translate("container.enderchest", &[])
|
||||
}
|
||||
}
|
||||
|
||||
#[pumpkin_block("minecraft:ender_chest")]
|
||||
pub struct EnderChestBlock;
|
||||
@@ -19,4 +53,37 @@ impl BlockBehaviour for EnderChestBlock {
|
||||
.opposite();
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
async fn on_synced_block_event(&self, args: OnSyncedBlockEventArgs<'_>) -> bool {
|
||||
// On the server, we don't need to do more because the client is responsible for that.
|
||||
args.r#type == Self::LID_ANIMATION_EVENT_TYPE
|
||||
}
|
||||
|
||||
async fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position).await
|
||||
&& let Some(block_entity) = block_entity
|
||||
.as_any()
|
||||
.downcast_ref::<EnderChestBlockEntity>()
|
||||
{
|
||||
let inventory = args.player.ender_chest_inventory();
|
||||
inventory.set_tracker(block_entity.get_tracker()).await;
|
||||
args.player
|
||||
.open_handled_screen(&EnderChestScreenFactory(inventory.clone()))
|
||||
.await;
|
||||
|
||||
// TODO: player.incrementStat(Stats.OPEN_ENDERCHEST);
|
||||
// TODO: PiglinBrain.onGuardedBlockInteracted(serverWorld, player, true);
|
||||
}
|
||||
|
||||
BlockActionResult::Success
|
||||
}
|
||||
|
||||
async fn placed(&self, args: PlacedArgs<'_>) {
|
||||
let block_entity = EnderChestBlockEntity::new(*args.position);
|
||||
args.world.add_block_entity(Arc::new(block_entity)).await;
|
||||
}
|
||||
}
|
||||
|
||||
impl EnderChestBlock {
|
||||
pub const LID_ANIMATION_EVENT_TYPE: u8 = 1;
|
||||
}
|
||||
|
||||
@@ -32,11 +32,9 @@ impl ScreenHandlerFactory for HopperBlockScreenFactory {
|
||||
player_inventory: &Arc<PlayerInventory>,
|
||||
_player: &dyn InventoryPlayer,
|
||||
) -> Option<Arc<Mutex<dyn ScreenHandler>>> {
|
||||
Some(Arc::new(Mutex::new(create_hopper(
|
||||
sync_id,
|
||||
player_inventory,
|
||||
self.0.clone(),
|
||||
))))
|
||||
Some(Arc::new(Mutex::new(
|
||||
create_hopper(sync_id, player_inventory, self.0.clone()).await,
|
||||
)))
|
||||
}
|
||||
|
||||
fn get_display_name(&self) -> TextComponent {
|
||||
|
||||
@@ -38,11 +38,9 @@ impl ScreenHandlerFactory for DropperScreenFactory {
|
||||
player_inventory: &Arc<PlayerInventory>,
|
||||
_player: &dyn InventoryPlayer,
|
||||
) -> Option<Arc<Mutex<dyn ScreenHandler>>> {
|
||||
Some(Arc::new(Mutex::new(create_generic_3x3(
|
||||
sync_id,
|
||||
player_inventory,
|
||||
self.0.clone(),
|
||||
))))
|
||||
Some(Arc::new(Mutex::new(
|
||||
create_generic_3x3(sync_id, player_inventory, self.0.clone()).await,
|
||||
)))
|
||||
}
|
||||
|
||||
fn get_display_name(&self) -> TextComponent {
|
||||
|
||||
@@ -27,11 +27,9 @@ impl ScreenHandlerFactory for ShulkerBoxScreenFactory {
|
||||
player_inventory: &Arc<PlayerInventory>,
|
||||
_player: &dyn InventoryPlayer,
|
||||
) -> Option<Arc<Mutex<dyn ScreenHandler>>> {
|
||||
Some(Arc::new(Mutex::new(create_generic_9x3(
|
||||
sync_id,
|
||||
player_inventory,
|
||||
self.0.clone(),
|
||||
))))
|
||||
Some(Arc::new(Mutex::new(
|
||||
create_generic_9x3(sync_id, player_inventory, self.0.clone()).await,
|
||||
)))
|
||||
}
|
||||
|
||||
fn get_display_name(&self) -> TextComponent {
|
||||
|
||||
@@ -11,6 +11,7 @@ use async_trait::async_trait;
|
||||
use crossbeam::atomic::AtomicCell;
|
||||
use crossbeam::channel::Receiver;
|
||||
use log::warn;
|
||||
use pumpkin_inventory::player::ender_chest_inventory::EnderChestInventory;
|
||||
use pumpkin_protocol::bedrock::client::level_chunk::CLevelChunk;
|
||||
use pumpkin_protocol::bedrock::client::set_time::CSetTime;
|
||||
use pumpkin_protocol::bedrock::client::update_abilities::{
|
||||
@@ -320,6 +321,8 @@ pub struct Player {
|
||||
pub client: ClientPlatform,
|
||||
/// The player's inventory.
|
||||
pub inventory: Arc<PlayerInventory>,
|
||||
/// The player's `EnderChest` inventory.
|
||||
pub ender_chest_inventory: Arc<EnderChestInventory>,
|
||||
/// The player's configuration settings. Changes when the player changes their settings.
|
||||
pub config: RwLock<PlayerConfig>,
|
||||
/// The player's current gamemode (e.g., Survival, Creative, Adventure).
|
||||
@@ -430,6 +433,8 @@ impl Player {
|
||||
living_entity.equipment_slots.clone(),
|
||||
));
|
||||
|
||||
let ender_chest_inventory = Arc::new(EnderChestInventory::new());
|
||||
|
||||
let player_screen_handler = Arc::new(Mutex::new(
|
||||
PlayerScreenHandler::new(&inventory, None, 0).await,
|
||||
));
|
||||
@@ -479,7 +484,7 @@ impl Player {
|
||||
|op| AtomicCell::new(op.level),
|
||||
),
|
||||
inventory,
|
||||
// TODO: enderChestInventory
|
||||
ender_chest_inventory,
|
||||
experience_level: AtomicI32::new(0),
|
||||
experience_progress: AtomicCell::new(0.0),
|
||||
experience_points: AtomicI32::new(0),
|
||||
@@ -520,6 +525,10 @@ impl Player {
|
||||
&self.inventory
|
||||
}
|
||||
|
||||
pub fn ender_chest_inventory(&self) -> &Arc<EnderChestInventory> {
|
||||
&self.ender_chest_inventory
|
||||
}
|
||||
|
||||
/// Removes the [`Player`] out of the current [`World`].
|
||||
pub async fn remove(self: &Arc<Self>) {
|
||||
let world = self.world();
|
||||
@@ -2082,6 +2091,7 @@ impl NBTStorage for Player {
|
||||
nbt.put_int("DataVersion", DATA_VERSION);
|
||||
self.living_entity.write_nbt(nbt).await;
|
||||
self.inventory.write_nbt(nbt).await;
|
||||
self.ender_chest_inventory.write_nbt(nbt).await;
|
||||
|
||||
self.abilities.lock().await.write_nbt(nbt).await;
|
||||
|
||||
@@ -2111,6 +2121,7 @@ impl NBTStorage for Player {
|
||||
async fn read_nbt(&mut self, nbt: &mut NbtCompound) {
|
||||
self.living_entity.read_nbt(nbt).await;
|
||||
self.inventory.read_nbt_non_mut(nbt).await;
|
||||
self.ender_chest_inventory.read_nbt_non_mut(nbt).await;
|
||||
self.abilities.lock().await.read_nbt(nbt).await;
|
||||
|
||||
self.gamemode.store(
|
||||
@@ -2150,7 +2161,7 @@ impl NBTStorage for PlayerInventory {
|
||||
nbt.put_int("SelectedItemSlot", i32::from(self.get_selected_slot()));
|
||||
|
||||
// Create inventory list with the correct capacity (inventory size)
|
||||
let mut vec: Vec<NbtTag> = Vec::with_capacity(41);
|
||||
let mut items: Vec<NbtTag> = Vec::with_capacity(41);
|
||||
for (i, item) in self.main_inventory.iter().enumerate() {
|
||||
let stack = item.lock().await;
|
||||
if !stack.is_empty() {
|
||||
@@ -2158,7 +2169,7 @@ impl NBTStorage for PlayerInventory {
|
||||
item_compound.put_byte("Slot", i as i8);
|
||||
stack.write_item_stack(&mut item_compound);
|
||||
drop(stack);
|
||||
vec.push(NbtTag::Compound(item_compound));
|
||||
items.push(NbtTag::Compound(item_compound));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2193,7 +2204,7 @@ impl NBTStorage for PlayerInventory {
|
||||
}
|
||||
}
|
||||
nbt.put_component("equipment", equipment_compound);
|
||||
nbt.put("Inventory", NbtTag::List(vec));
|
||||
nbt.put("Inventory", NbtTag::List(items));
|
||||
}
|
||||
|
||||
async fn read_nbt_non_mut(&self, nbt: &NbtCompound) {
|
||||
@@ -2249,6 +2260,44 @@ impl NBTStorage for PlayerInventory {
|
||||
|
||||
impl NBTStorageInit for PlayerInventory {}
|
||||
|
||||
#[async_trait]
|
||||
impl NBTStorage for EnderChestInventory {
|
||||
async fn write_nbt(&self, nbt: &mut NbtCompound) {
|
||||
// Create item list with the correct capacity (inventory size)
|
||||
let mut items: Vec<NbtTag> = Vec::with_capacity(Self::INVENTORY_SIZE);
|
||||
for (i, item) in self.items.iter().enumerate() {
|
||||
let stack = item.lock().await;
|
||||
if !stack.is_empty() {
|
||||
let mut item_compound = NbtCompound::new();
|
||||
item_compound.put_byte("Slot", i as i8);
|
||||
stack.write_item_stack(&mut item_compound);
|
||||
drop(stack);
|
||||
items.push(NbtTag::Compound(item_compound));
|
||||
}
|
||||
}
|
||||
|
||||
nbt.put("EnderItems", NbtTag::List(items));
|
||||
}
|
||||
|
||||
async fn read_nbt_non_mut(&self, nbt: &NbtCompound) {
|
||||
// Process item list
|
||||
if let Some(item_list) = nbt.get_list("EnderItems") {
|
||||
for tag in item_list {
|
||||
if let Some(item_compound) = tag.extract_compound()
|
||||
&& let Some(slot_byte) = item_compound.get_byte("Slot")
|
||||
{
|
||||
let slot = slot_byte as usize;
|
||||
if let Some(item_stack) = ItemStack::read_item_stack(item_compound) {
|
||||
self.set_stack(slot, item_stack).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NBTStorageInit for EnderChestInventory {}
|
||||
|
||||
#[async_trait]
|
||||
impl EntityBase for Player {
|
||||
async fn damage_with_context(
|
||||
|
||||
Reference in New Issue
Block a user