feat: Implement EnderChest (#1163)

* feat: Implement ender chest

* fix: Register block entity

* fix: Fix formatting
This commit is contained in:
Rafael
2025-10-10 21:54:03 +02:00
committed by GitHub
parent e71bccf564
commit cc076f098d
22 changed files with 415 additions and 68 deletions

View File

@@ -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 {

View File

@@ -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 {

View 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();
}
}
}

View File

@@ -1,2 +1,3 @@
pub mod ender_chest_inventory;
pub mod player_inventory;
pub mod player_screen_handler;

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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 {

View File

@@ -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,

View 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;
}
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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 => {

View File

@@ -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();
}

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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

View File

@@ -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
}

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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(