refactor(inventory): from async to sync Part 3

This commit is contained in:
Alexander Medvedev 2026-08-26 20:33:08 +02:00
parent 66a5f53408
commit 52038e3356
103 changed files with 5616 additions and 6220 deletions

View File

@ -219,6 +219,16 @@ impl ItemStack {
}
}
pub fn set_data_component<T: DataComponentImpl + 'static>(&mut self, component: T) {
let to_set_id = T::get_enum();
let boxed = Some(Box::new(component) as _);
if let Some((_, c)) = self.patch.iter_mut().find(|(id, _)| *id == to_set_id) {
*c = boxed;
} else {
self.patch.push((to_set_id, boxed));
}
}
pub fn add_lore(&mut self, line: pumpkin_util::text::TextComponent) {
let mut lines = self
.get_data_component::<crate::data_component_impl::LoreImpl>()

View File

@ -6,10 +6,7 @@ use pumpkin_world::inventory::Inventory;
use crate::{
player::player_inventory::PlayerInventory,
screen_handler::{
InventoryPlayer, ItemStackFuture, ScreenHandler, ScreenHandlerBehaviour,
ScreenHandlerFuture, offer_or_drop_stack,
},
screen_handler::{InventoryPlayer, ScreenHandler, ScreenHandlerBehaviour, offer_or_drop_stack},
slot::NormalSlot,
window_property::{Anvil, WindowProperty},
};
@ -46,22 +43,22 @@ impl AnvilScreenHandler {
handler
}
pub async fn update_item_name(&mut self, name: String) {
pub fn update_item_name(&mut self, name: String) {
self.rename_text = name;
self.update_result_slot().await;
self.send_content_updates().await;
self.update_result_slot();
self.send_content_updates();
}
pub async fn update_result_slot(&mut self) {
let input_a = self.inventory.get_stack(0).await;
pub fn update_result_slot(&mut self) {
let input_a = self.inventory.get_stack(0);
if input_a.is_empty() {
self.inventory.set_stack(2, ItemStack::EMPTY.clone()).await;
self.set_repair_cost(0).await;
self.inventory.set_stack(2, ItemStack::EMPTY.clone());
self.set_repair_cost(0);
return;
}
let mut result_item = input_a.clone();
let mut result_item = input_a;
let mut cost = 0;
// Basic renaming logic for now
@ -73,22 +70,24 @@ impl AnvilScreenHandler {
// If combining with another item... we'll skip complex anvil logic for now
// and just support renaming.
if cost > 0 {
self.inventory.set_stack(2, result_item).await;
self.set_repair_cost(cost).await;
self.inventory.set_stack(2, result_item);
self.set_repair_cost(cost);
} else {
self.inventory.set_stack(2, ItemStack::EMPTY.clone()).await;
self.set_repair_cost(0).await;
self.inventory.set_stack(2, ItemStack::EMPTY.clone());
self.set_repair_cost(0);
}
}
pub async fn set_repair_cost(&mut self, cost: i16) {
pub fn set_repair_cost(&mut self, cost: i16) {
self.repair_cost = cost;
if let Some(sync_handler) = self.behaviour.sync_handler.as_ref() {
let (property_id, property_value) =
WindowProperty::new(Anvil::RepairCost, cost).into_tuple();
sync_handler
.update_property(&self.behaviour, property_id as i32, property_value as i32)
.await;
sync_handler.update_property(
&self.behaviour,
property_id as i32,
property_value as i32,
);
}
}
}
@ -110,104 +109,88 @@ impl ScreenHandler for AnvilScreenHandler {
&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;
// Drop inputs from anvil
for i in 0..2 {
let stack = self.inventory.remove_stack(i).await;
if !stack.is_empty() {
offer_or_drop_stack(player, stack).await;
}
fn on_closed(&mut self, player: &dyn InventoryPlayer) {
self.default_on_closed(player);
self.inventory.on_close();
// Drop inputs from anvil
for i in 0..2 {
let stack = self.inventory.remove_stack(i);
if !stack.is_empty() {
offer_or_drop_stack(player, stack);
}
self.inventory.set_stack(2, ItemStack::EMPTY.clone()).await;
})
}
self.inventory.set_stack(2, ItemStack::EMPTY.clone());
}
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();
fn quick_move(&mut self, player: &dyn InventoryPlayer, slot_index: i32) -> ItemStack {
let mut stack_left = ItemStack::EMPTY.clone();
let slot = self.get_behaviour().slots[slot_index as usize].clone();
if slot.has_stack().await {
let mut slot_stack = slot.get_stack().await;
stack_left = slot_stack.clone();
if slot.has_stack() {
let mut slot_stack = slot.get_stack();
stack_left = slot_stack.clone();
if slot_index < 3 {
// From anvil to player
if !self.insert_item(&mut slot_stack, 3, 39, true).await {
return ItemStack::EMPTY.clone();
}
slot.on_quick_move_crafted(slot_stack.clone(), stack_left.clone())
.await;
} else {
// From player to anvil
if !self.insert_item(&mut slot_stack, 0, 2, false).await {
return ItemStack::EMPTY.clone();
}
}
if slot_stack.item_count == stack_left.item_count {
if slot_index < 3 {
// From anvil to player
if !self.insert_item(&mut slot_stack, 3, 39, true) {
return ItemStack::EMPTY.clone();
}
slot.on_quick_move_crafted(slot_stack.clone(), stack_left.clone());
} else {
// From player to anvil
if !self.insert_item(&mut slot_stack, 0, 2, false) {
return ItemStack::EMPTY.clone();
}
slot.set_stack_prev(slot_stack.clone(), stack_left.clone())
.await;
slot.on_take_item(player, &slot_stack).await;
slot.mark_dirty().await;
}
stack_left
})
if slot_stack.item_count == stack_left.item_count {
return ItemStack::EMPTY.clone();
}
slot.set_stack_prev(slot_stack.clone(), stack_left.clone());
slot.on_take_item(player, &slot_stack);
slot.mark_dirty();
}
stack_left
}
fn on_slot_click<'a>(
&'a mut self,
fn on_slot_click(
&mut self,
slot_index: i32,
button: i32,
action_type: pumpkin_protocol::java::server::play::SlotActionType,
player: &'a dyn InventoryPlayer,
) -> ScreenHandlerFuture<'a, ()> {
Box::pin(async move {
if slot_index == 2 {
// Taking from output slot
let result_slot = self.get_behaviour().slots[2].clone();
if result_slot.has_stack().await {
let result_stack = result_slot.get_cloned_stack().await;
if !result_stack.is_empty() {
if player.experience_level() >= self.repair_cost as i32
|| player.is_creative()
{
// Consume experience
if !player.is_creative() {
player
.add_experience_levels(-(self.repair_cost as i32))
.await;
}
// Consume inputs
self.inventory.set_stack(0, ItemStack::EMPTY.clone()).await;
self.get_behaviour().slots[0].mark_dirty().await;
} else {
// Cancel click
self.send_content_updates().await;
return;
player: &dyn InventoryPlayer,
) {
if slot_index == 2 {
// Taking from output slot
let result_slot = self.get_behaviour().slots[2].clone();
if result_slot.has_stack() {
let result_stack = result_slot.get_cloned_stack();
if !result_stack.is_empty() {
if player.experience_level() >= self.repair_cost as i32 || player.is_creative()
{
// Consume experience
if !player.is_creative() {
player.add_experience_levels(-(self.repair_cost as i32));
}
// Consume inputs
self.inventory.set_stack(0, ItemStack::EMPTY.clone());
self.get_behaviour().slots[0].mark_dirty();
} else {
// Cancel click
self.send_content_updates();
return;
}
}
}
}
self.internal_on_slot_click(slot_index, button, action_type, player)
.await;
if slot_index == 0 || slot_index == 1 || slot_index == 2 {
self.update_result_slot().await;
self.send_content_updates().await;
}
})
self.internal_on_slot_click(slot_index, button, action_type, player);
if slot_index == 0 || slot_index == 1 || slot_index == 2 {
self.update_result_slot();
self.send_content_updates();
}
}
}

View File

@ -5,22 +5,19 @@ use pumpkin_world::inventory::Inventory;
use crate::{
player::player_inventory::PlayerInventory,
screen_handler::{
InventoryPlayer, ItemStackFuture, ScreenHandler, ScreenHandlerBehaviour,
ScreenHandlerFuture,
},
screen_handler::{InventoryPlayer, ScreenHandler, ScreenHandlerBehaviour},
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(
pub fn create_beacon_handler(
sync_id: u8,
player_inventory: &Arc<PlayerInventory>,
inventory: Arc<dyn Inventory>,
) -> BeaconScreenHandler {
BeaconScreenHandler::new(sync_id, player_inventory, inventory).await
BeaconScreenHandler::new(sync_id, player_inventory, inventory)
}
/// Screen handler specifically for Beacon blocks.
@ -33,17 +30,17 @@ pub struct BeaconScreenHandler {
impl BeaconScreenHandler {
/// Creates a new beacon screen handler.
async fn new(
fn new(
sync_id: u8,
player_inventory: &Arc<PlayerInventory>,
inventory: Arc<dyn Inventory>,
) -> Self {
let mut handler = Self {
inventory: inventory.clone(),
inventory,
behaviour: ScreenHandlerBehaviour::new(sync_id, Some(WindowType::Beacon)),
};
inventory.on_open().await;
handler.inventory.on_open();
// Add the single payment slot for the beacon (slot 0)
handler.add_slot(Arc::new(NormalSlot::new(handler.inventory.clone(), 0)));
@ -73,58 +70,47 @@ impl ScreenHandler for BeaconScreenHandler {
&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;
})
fn on_closed(&mut self, player: &dyn InventoryPlayer) {
self.default_on_closed(player);
self.inventory.on_close();
}
/// 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();
fn quick_move(&mut self, _player: &dyn InventoryPlayer, slot_index: i32) -> ItemStack {
let mut stack_left = ItemStack::EMPTY.clone();
let slot = self.get_behaviour().slots[slot_index as usize].clone();
if slot.has_stack().await {
let mut slot_stack = slot.get_stack().await;
stack_left = slot_stack.clone();
if slot.has_stack() {
let mut slot_stack = slot.get_stack();
stack_left = slot_stack.clone();
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,
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, 0, 1, false).await {
return ItemStack::EMPTY.clone();
}
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,
1,
self.get_behaviour().slots.len() as i32,
true,
) {
return ItemStack::EMPTY.clone();
}
if slot_stack.is_empty() {
slot.set_stack(ItemStack::EMPTY.clone()).await;
} else {
slot.set_stack(slot_stack).await;
} else {
// Move from player inventory into the beacon payment slot (slot 0)
if !self.insert_item(&mut slot_stack, 0, 1, false) {
return ItemStack::EMPTY.clone();
}
}
stack_left
})
if slot_stack.is_empty() {
slot.set_stack(ItemStack::EMPTY.clone());
} else {
slot.set_stack(slot_stack);
}
}
stack_left
}
}

View File

@ -10,7 +10,7 @@
//! - Brew time (0-400): Progress of the current brewing operation
//! - Fuel time (0-20): Amount of fuel remaining
use std::{any::Any, pin::Pin, sync::Arc};
use std::{any::Any, sync::Arc};
use pumpkin_data::tag::Taggable;
use pumpkin_data::{screen::WindowType, tag};
@ -18,7 +18,7 @@ use pumpkin_world::{block::entities::PropertyDelegate, inventory::Inventory};
use crate::{
player::player_inventory::PlayerInventory,
screen_handler::{ScreenHandler, ScreenHandlerBehaviour, ScreenHandlerFuture, ScreenProperty},
screen_handler::{ScreenHandler, ScreenHandlerBehaviour, ScreenProperty},
};
use pumpkin_data::item_stack::ItemStack;
@ -46,27 +46,23 @@ impl BrewingScreenHandler {
/// - `player_inventory` - The player's inventory
/// - `inventory` - The brewing stand's inventory (5 slots)
/// - `property_delegate` - Delegate for accessing brew time and fuel properties
pub async fn new(
pub fn new(
sync_id: u8,
player_inventory: Arc<PlayerInventory>,
player_inventory: &Arc<PlayerInventory>,
inventory: Arc<dyn Inventory>,
property_delegate: Arc<dyn PropertyDelegate>,
property_delegate: &Arc<dyn PropertyDelegate>,
) -> Self {
struct BrewingScreenListener;
impl crate::screen_handler::ScreenHandlerListener for BrewingScreenListener {
fn on_property_update<'a>(
&'a self,
screen_handler: &'a ScreenHandlerBehaviour,
fn on_property_update(
&self,
screen_handler: &ScreenHandlerBehaviour,
property: u8,
value: i32,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
if let Some(sync_handler) = screen_handler.sync_handler.as_ref() {
sync_handler
.update_property(screen_handler, i32::from(property), value)
.await;
}
})
) {
if let Some(sync_handler) = screen_handler.sync_handler.as_ref() {
sync_handler.update_property(screen_handler, i32::from(property), value);
}
}
}
@ -80,7 +76,7 @@ impl BrewingScreenHandler {
handler.add_property(ScreenProperty::new(property_delegate.clone(), 0));
handler.add_property(ScreenProperty::new(property_delegate.clone(), 1));
handler.add_listener(Arc::new(BrewingScreenListener)).await;
handler.add_listener(Arc::new(BrewingScreenListener));
// Add all 5 brewing stand slots: 0-2 = potions, 3 = ingredient, 4 = fuel
for i in 0..5 {
@ -115,11 +111,8 @@ impl ScreenHandler for BrewingScreenHandler {
&mut self.behaviour
}
fn on_closed<'a>(
&'a mut self,
player: &'a dyn crate::screen_handler::InventoryPlayer,
) -> ScreenHandlerFuture<'a, ()> {
Box::pin(async move { self.default_on_closed(player).await })
fn on_closed(&mut self, player: &dyn crate::screen_handler::InventoryPlayer) {
self.default_on_closed(player);
}
/// Quick move logic for brewing stand.
@ -128,76 +121,73 @@ impl ScreenHandler for BrewingScreenHandler {
/// - Ingredient (3) -> Player inventory
/// - Fuel (4) -> Player inventory
/// - From player: potions -> potion slots, fuel -> fuel slot, else -> ingredient slot
fn quick_move<'a>(
&'a mut self,
_player: &'a dyn crate::screen_handler::InventoryPlayer,
fn quick_move(
&mut self,
_player: &dyn crate::screen_handler::InventoryPlayer,
slot_index: i32,
) -> Pin<Box<dyn Future<Output = ItemStack> + Send + 'a>> {
Box::pin(async move {
let mut stack_left = ItemStack::EMPTY.clone();
) -> ItemStack {
let mut stack_left = ItemStack::EMPTY.clone();
let slot = self.get_behaviour().slots[slot_index as usize].clone();
let slot = self.get_behaviour().slots[slot_index as usize].clone();
if !slot.has_stack().await {
return stack_left;
}
if !slot.has_stack() {
return stack_left;
}
let mut stack = slot.get_stack().await;
stack_left = stack.clone();
let mut stack = slot.get_stack();
stack_left = stack.clone();
let success = if slot_index < 5 {
// Moving from brewing stand to player inventory
self.insert_item(&mut stack, 5, self.get_behaviour().slots.len() as i32, true)
.await
let success = if slot_index < 5 {
// Moving from brewing stand to player inventory
self.insert_item(&mut stack, 5, self.get_behaviour().slots.len() as i32, true)
} else {
// Moving from player inventory to brewing stand
// Check item type to determine target slot
// Check if item has potion contents (for slots 0-2)
let has_potion_contents = stack
.get_data_component::<pumpkin_data::data_component_impl::PotionContentsImpl>()
.is_some();
// Check if item is brewing fuel (for slot 4)
let is_fuel = stack.get_item().has_tag(&tag::Item::MINECRAFT_BREWING_FUEL);
if has_potion_contents {
// Try to insert into potion slots (0-2)
self.insert_item(&mut stack, 0, 3, false)
} else if is_fuel {
// Try to insert into fuel slot (4)
self.insert_item(&mut stack, 4, 5, false)
} else {
// Moving from player inventory to brewing stand
// Check item type to determine target slot
// Check if item has potion contents (for slots 0-2)
let has_potion_contents = stack
.get_data_component::<pumpkin_data::data_component_impl::PotionContentsImpl>()
.is_some();
// Check if item is brewing fuel (for slot 4)
let is_fuel = stack.get_item().has_tag(&tag::Item::MINECRAFT_BREWING_FUEL);
if has_potion_contents {
// Try to insert into potion slots (0-2)
self.insert_item(&mut stack, 0, 3, false).await
} else if is_fuel {
// Try to insert into fuel slot (4)
self.insert_item(&mut stack, 4, 5, false).await
} else {
// Try to insert into ingredient slot (3)
self.insert_item(&mut stack, 3, 4, false).await
}
};
if !success {
return ItemStack::EMPTY.clone();
// Try to insert into ingredient slot (3)
self.insert_item(&mut stack, 3, 4, false)
}
};
if stack.is_empty() {
slot.set_stack(ItemStack::EMPTY.clone()).await;
} else {
slot.set_stack(stack).await;
}
if !success {
return ItemStack::EMPTY.clone();
}
stack_left
})
if stack.is_empty() {
slot.set_stack(ItemStack::EMPTY.clone());
} else {
slot.set_stack(stack);
}
stack_left
}
}
/// Creates a new brewing stand screen handler.
///
/// Factory function used by the server when a player opens a brewing stand.
pub async fn create_brewing(
pub fn create_brewing(
sync_id: u8,
player_inventory: Arc<PlayerInventory>,
player_inventory: &Arc<PlayerInventory>,
inventory: Arc<dyn Inventory>,
property_delegate: Arc<dyn PropertyDelegate>,
) -> Option<impl ScreenHandler> {
property_delegate: &Arc<dyn PropertyDelegate>,
) -> Option<BrewingScreenHandler> {
let handler =
BrewingScreenHandler::new(sync_id, player_inventory, inventory, property_delegate).await;
BrewingScreenHandler::new(sync_id, player_inventory, inventory, property_delegate);
Some(handler)
}

View File

@ -2,9 +2,7 @@ use std::any::Any;
use std::sync::Arc;
use crate::player::player_inventory::PlayerInventory;
use crate::screen_handler::{
InventoryPlayer, ScreenHandler, ScreenHandlerBehaviour, ScreenHandlerFuture,
};
use crate::screen_handler::{InventoryPlayer, ScreenHandler, ScreenHandlerBehaviour};
use crate::slot::NormalSlot;
use pumpkin_data::item_stack::ItemStack;
@ -66,48 +64,39 @@ impl ScreenHandler for CartographyTableScreenHandler {
self
}
fn on_slot_click<'a>(
&'a mut self,
fn on_slot_click(
&mut self,
slot_index: i32,
button: i32,
action_type: SlotActionType,
player: &'a dyn InventoryPlayer,
) -> ScreenHandlerFuture<'a, ()> {
Box::pin(async move {
self.internal_on_slot_click(slot_index, button, action_type, player)
.await;
})
player: &dyn InventoryPlayer,
) {
self.internal_on_slot_click(slot_index, button, action_type, player);
}
fn quick_move<'a>(
&'a mut self,
_player: &'a dyn InventoryPlayer,
slot_index: i32,
) -> ScreenHandlerFuture<'a, ItemStack> {
Box::pin(async move {
let mut stack = ItemStack::EMPTY.clone();
let slot = self.get_behaviour().slots.get(slot_index as usize).cloned();
fn quick_move(&mut self, _player: &dyn InventoryPlayer, slot_index: i32) -> ItemStack {
let mut stack = ItemStack::EMPTY.clone();
let slot = self.get_behaviour().slots.get(slot_index as usize).cloned();
if let Some(slot) = slot {
let mut slot_stack = slot.get_cloned_stack().await;
if !slot_stack.is_empty() {
stack = slot_stack.clone();
if slot_index < 3 {
if !self.insert_item(&mut slot_stack, 3, 39, true).await {
return ItemStack::EMPTY.clone();
}
} else if !self.insert_item(&mut slot_stack, 0, 2, false).await {
if let Some(slot) = slot {
let mut slot_stack = slot.get_cloned_stack();
if !slot_stack.is_empty() {
stack = slot_stack.clone();
if slot_index < 3 {
if !self.insert_item(&mut slot_stack, 3, 39, true) {
return ItemStack::EMPTY.clone();
}
} else if !self.insert_item(&mut slot_stack, 0, 2, false) {
return ItemStack::EMPTY.clone();
}
if slot_stack.is_empty() {
slot.set_stack(ItemStack::EMPTY.clone()).await;
} else {
slot.set_stack(slot_stack).await;
}
if slot_stack.is_empty() {
slot.set_stack(ItemStack::EMPTY.clone());
} else {
slot.set_stack(slot_stack);
}
}
stack
})
}
stack
}
}

View File

@ -9,12 +9,11 @@
//! Unlike regular inventories, crafting grids are typically cleared when
//! the container closes, and their contents are used up when crafting.
use std::{any::Any, pin::Pin};
use std::any::Any;
use std::sync::RwLock;
use pumpkin_data::item_stack::ItemStack;
use tokio::sync::RwLock;
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture};
use pumpkin_world::inventory::{Clearable, Inventory};
use super::recipes::RecipeInputInventory;
@ -71,52 +70,57 @@ impl Inventory for CraftingInventory {
(self.width as usize) * (self.height as usize)
}
fn is_empty(&self) -> InventoryFuture<'_, bool> {
Box::pin(async move {
let items = self.items.read().await;
items.iter().all(ItemStack::is_empty)
})
fn is_empty(&self) -> bool {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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
.get(slot)
.cloned()
.unwrap_or_else(|| ItemStack::EMPTY.clone())
})
fn get_stack(&self, slot: usize) -> ItemStack {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items
.get(slot)
.cloned()
.unwrap_or_else(|| ItemStack::EMPTY.clone())
}
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move {
let mut items = self.items.write().await;
if slot < items.len() {
std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone())
} else {
ItemStack::EMPTY.clone()
}
})
fn remove_stack(&self, slot: usize) -> ItemStack {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if slot < items.len() {
std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone())
} else {
ItemStack::EMPTY.clone()
}
}
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move {
let mut items = self.items.write().await;
if slot < items.len() && !items[slot].is_empty() && amount > 0 {
items[slot].split(amount)
} else {
ItemStack::EMPTY.clone()
}
})
fn remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if slot < items.len() && !items[slot].is_empty() && amount > 0 {
items[slot].split(amount)
} else {
ItemStack::EMPTY.clone()
}
}
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
Box::pin(async move {
let mut items = self.items.write().await;
if slot < items.len() {
items[slot] = stack;
}
})
fn set_stack(&self, slot: usize, stack: ItemStack) {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if slot < items.len() {
items[slot] = stack;
}
}
fn as_any(&self) -> &dyn Any {
@ -135,10 +139,11 @@ impl RecipeInputInventory for CraftingInventory {
}
impl Clearable for CraftingInventory {
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());
})
fn clear(&self) {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items.fill_with(|| ItemStack::EMPTY.clone());
}
}

View File

@ -15,19 +15,17 @@
//! - Special recipes (like decorated pots)
use std::any::Any;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::{Arc, Mutex};
use super::recipe_provider::{GenericRecipe, RecipeProvider};
use super::recipes::{RecipeFinderScreenHandler, RecipeInputInventory};
use crate::crafting::crafting_inventory::CraftingInventory;
use crate::player::player_inventory::PlayerInventory;
use crate::screen_handler::{
InventoryPlayer, ItemStackFuture, ScreenHandler, ScreenHandlerBehaviour, ScreenHandlerFuture,
ScreenHandlerListener,
InventoryPlayer, ScreenHandler, ScreenHandlerBehaviour, ScreenHandlerListener,
};
use crate::slot::{BoxFuture, NormalSlot, Slot};
use crate::slot::{NormalSlot, Slot};
use pumpkin_data::item_stack::ItemStack;
use pumpkin_data::recipes::{CraftingRecipeTypes, RECIPES_CRAFTING};
@ -37,7 +35,6 @@ use pumpkin_data::tag;
use pumpkin_data::tag::Taggable;
use pumpkin_protocol::codec::recipe::{DynamicRecipe, OwnedCraftingRecipe};
use pumpkin_world::inventory::Inventory;
use tokio::sync::Mutex;
/// The result slot in a crafting screen.
pub struct ResultSlot {
@ -74,7 +71,7 @@ fn is_symmetrical_horizontally(pattern: &[&str]) -> bool {
/// Checks if a crafting recipe matches the current inventory state.
#[expect(clippy::too_many_lines)]
async fn recipe_matches(
fn recipe_matches(
recipe: GenericRecipe<'_>,
input_height: usize,
input_width: usize,
@ -113,8 +110,7 @@ async fn recipe_matches(
'outer: for (y, row_str) in pattern.iter().enumerate() {
for (x, current_key) in row_str.chars().enumerate() {
let slot = inventory
.get_stack((y + y_offset) * inventory.get_width() + (x + x_offset))
.await;
.get_stack((y + y_offset) * inventory.get_width() + (x + x_offset));
if current_key == ' ' {
if !slot.is_empty() {
matched = false;
@ -146,12 +142,10 @@ async fn recipe_matches(
matched = false;
break 'outer;
};
let slot = inventory
.get_stack(
(y + y_offset) * inventory.get_height()
+ (x_offset + input_width - 1 - x),
)
.await;
let slot = inventory.get_stack(
(y + y_offset) * inventory.get_height()
+ (x_offset + input_width - 1 - x),
);
if current_key == ' ' {
if !slot.is_empty() {
matched = false;
@ -189,7 +183,7 @@ async fn recipe_matches(
}
let mut ingredient_used = vec![false; ingredients.len()];
'next_slot: for i in 0..inventory.size() {
let slot = inventory.get_stack(i).await;
let slot = inventory.get_stack(i);
if slot.is_empty() {
continue 'next_slot;
}
@ -216,7 +210,7 @@ async fn recipe_matches(
return None;
}
'item_stack: for i in 0..inventory.size() {
let slot = inventory.get_stack(i).await;
let slot = inventory.get_stack(i);
if slot.is_empty() {
continue 'item_stack;
}
@ -234,7 +228,7 @@ async fn recipe_matches(
return None;
}
for position in (1..=7).step_by(2) {
let slot = inventory.get_stack(position).await;
let slot = inventory.get_stack(position);
if slot.is_empty()
|| !slot
.item
@ -274,8 +268,7 @@ async fn recipe_matches(
'outer: for (y, row_str) in pattern.iter().enumerate() {
for (x, current_key) in row_str.chars().enumerate() {
let slot = inventory
.get_stack((y + y_offset) * inventory.get_width() + (x + x_offset))
.await;
.get_stack((y + y_offset) * inventory.get_width() + (x + x_offset));
if current_key == ' ' {
if !slot.is_empty() {
matched = false;
@ -310,7 +303,7 @@ async fn recipe_matches(
}
let mut ingredient_used = vec![false; ingredients.len()];
'next_slot: for i in 0..inventory.size() {
let slot = inventory.get_stack(i).await;
let slot = inventory.get_stack(i);
if slot.is_empty() {
continue 'next_slot;
}
@ -344,7 +337,7 @@ impl ResultSlot {
}
}
async fn match_recipe(&self) -> Option<RecipeResult> {
fn match_recipe(&self) -> Option<RecipeResult> {
let mut count: usize = 0;
let inventory_width = self.inventory.get_width();
let mut top_x = 9;
@ -354,7 +347,7 @@ impl ResultSlot {
for i in 0..self.inventory.size() {
let x = i % inventory_width;
let y = i / inventory_width;
let slot = self.inventory.get_stack(i).await;
let slot = self.inventory.get_stack(i);
if !slot.is_empty() {
top_x = top_x.min(x);
top_y = top_y.min(y);
@ -378,15 +371,13 @@ impl ResultSlot {
top_y,
count,
&*self.inventory,
)
.await
{
) {
return Some(result);
}
}
if let Some(provider) = &self.recipe_provider {
let dynamic = provider.get_dynamic_recipes().await;
let dynamic = provider.get_dynamic_recipes();
for recipe in &dynamic {
if let DynamicRecipe::Crafting(crafting) = recipe
&& let Some(result) = recipe_matches(
@ -398,7 +389,6 @@ impl ResultSlot {
count,
&*self.inventory,
)
.await
{
return Some(result);
}
@ -407,8 +397,8 @@ impl ResultSlot {
None
}
async fn refill_output(&self) -> ItemStack {
let result = if let Some(matched) = self.match_recipe().await {
fn refill_output(&self) -> ItemStack {
let result = if let Some(matched) = self.match_recipe() {
let key = matched
.item_id
.strip_prefix("minecraft:")
@ -419,7 +409,10 @@ impl ResultSlot {
} else {
ItemStack::EMPTY.clone()
};
*self.result.lock().await = result.clone();
*self
.result
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = result.clone();
result
}
}
@ -434,128 +427,106 @@ impl Slot for ResultSlot {
fn set_id(&self, id: usize) {
self.id.store(id as u8, Ordering::Relaxed);
}
fn on_quick_move_crafted(
&self,
_stack: ItemStack,
_stack_prev: ItemStack,
) -> BoxFuture<'_, ()> {
Box::pin(async move {
self.refill_output().await;
})
fn on_quick_move_crafted(&self, _stack: ItemStack, _stack_prev: ItemStack) {
self.refill_output();
}
fn on_take_item<'a>(
&'a self,
player: &'a dyn InventoryPlayer,
stack: &'a ItemStack,
) -> BoxFuture<'a, ()> {
Box::pin(async move {
player
.increment_stat(
StatisticCategory::Crafted,
stack.item.id as i32,
stack.item_count as i32,
)
.await;
for i in 0..self.inventory.size() {
self.inventory.remove_stack_specific(i, 1).await;
fn on_take_item(&self, player: &dyn InventoryPlayer, stack: &ItemStack) {
player.increment_stat(
StatisticCategory::Crafted,
stack.item.id as i32,
stack.item_count as i32,
);
for i in 0..self.inventory.size() {
self.inventory.remove_stack_specific(i, 1);
}
self.mark_dirty();
}
fn can_insert(&self, _stack: &ItemStack) -> bool {
false
}
fn get_stack(&self) -> ItemStack {
self.result
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
}
fn get_cloned_stack(&self) -> ItemStack {
self.result
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
}
fn has_stack(&self) -> bool {
!self
.result
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.is_empty()
}
fn set_stack(&self, _stack: ItemStack) {
self.refill_output();
}
fn set_stack_prev(&self, _stack: ItemStack, _previous_stack: ItemStack) {
self.refill_output();
}
fn mark_dirty(&self) {
self.inventory.mark_dirty();
}
fn get_max_item_count(&self) -> u8 {
let mut count = u8::MAX;
for i in 0..self.inventory.size() {
let slot = self.inventory.get_stack(i);
if !slot.is_empty() {
count = count.min(slot.item_count);
}
self.mark_dirty().await;
})
}
count
}
fn can_insert(&self, _stack: &ItemStack) -> BoxFuture<'_, bool> {
Box::pin(async move { false })
}
fn get_stack(&self) -> BoxFuture<'_, ItemStack> {
Box::pin(async move { self.result.lock().await.clone() })
}
fn get_cloned_stack(&self) -> BoxFuture<'_, ItemStack> {
Box::pin(async move { self.result.lock().await.clone() })
}
fn has_stack(&self) -> BoxFuture<'_, bool> {
Box::pin(async move { !self.result.lock().await.is_empty() })
}
fn set_stack(&self, _stack: ItemStack) -> BoxFuture<'_, ()> {
Box::pin(async move {
self.refill_output().await;
})
}
fn set_stack_prev(&self, _stack: ItemStack, _previous_stack: ItemStack) -> BoxFuture<'_, ()> {
Box::pin(async move {
self.refill_output().await;
})
}
fn mark_dirty(&self) -> BoxFuture<'_, ()> {
Box::pin(async move {
self.inventory.mark_dirty();
})
}
fn get_max_item_count(&self) -> BoxFuture<'_, u8> {
Box::pin(async move {
let mut count = u8::MAX;
for i in 0..self.inventory.size() {
let slot = self.inventory.get_stack(i).await;
if !slot.is_empty() {
count = count.min(slot.item_count);
}
}
count
})
}
fn take_stack(&self, _amount: u8) -> BoxFuture<'_, ItemStack> {
Box::pin(async move {
if self.has_stack().await {
self.result.lock().await.clone()
} else {
ItemStack::EMPTY.clone()
}
})
fn take_stack(&self, _amount: u8) -> ItemStack {
if self.has_stack() {
self.result
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
} else {
ItemStack::EMPTY.clone()
}
}
}
impl ScreenHandlerListener for ResultSlot {
fn on_slot_update<'a>(
&'a self,
screen_handler: &'a ScreenHandlerBehaviour,
slot: u8,
_stack: ItemStack,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
if (0..=(self.inventory.get_width() * self.inventory.get_height()))
.contains(&(slot as usize))
{
let result = self.refill_output().await;
let next_revision = screen_handler.next_revision();
if let Some(sync_handler) = screen_handler.sync_handler.as_ref() {
sync_handler
.update_slot(screen_handler, 0, &result, next_revision)
.await;
}
fn on_slot_update(&self, screen_handler: &ScreenHandlerBehaviour, slot: u8, _stack: ItemStack) {
if (0..=(self.inventory.get_width() * self.inventory.get_height()))
.contains(&(slot as usize))
{
let result = self.refill_output();
let next_revision = screen_handler.next_revision();
if let Some(sync_handler) = screen_handler.sync_handler.as_ref() {
sync_handler.update_slot(screen_handler, 0, &result, next_revision);
}
})
}
}
}
pub trait CraftingScreenHandler<I: RecipeInputInventory>:
RecipeFinderScreenHandler + ScreenHandler
{
fn add_recipe_slots<'a>(
&'a mut self,
fn add_recipe_slots(
&mut self,
crafing_inventory: Arc<dyn RecipeInputInventory>,
provider: Option<Arc<dyn RecipeProvider>>,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
let result_slot = Arc::new(ResultSlot::new(crafing_inventory.clone(), provider));
self.add_slot(result_slot.clone());
let width = crafing_inventory.get_width();
let height = crafing_inventory.get_height();
for i in 0..width {
for j in 0..height {
let input_slot = NormalSlot::new(crafing_inventory.clone(), j + i * width);
self.add_slot(Arc::new(input_slot));
}
) {
let result_slot = Arc::new(ResultSlot::new(crafing_inventory.clone(), provider));
self.add_slot(result_slot.clone());
let width = crafing_inventory.get_width();
let height = crafing_inventory.get_height();
for i in 0..width {
for j in 0..height {
let input_slot = NormalSlot::new(crafing_inventory.clone(), j + i * width);
self.add_slot(Arc::new(input_slot));
}
self.add_listener(result_slot).await;
})
}
self.add_listener(result_slot);
}
}
@ -565,7 +536,7 @@ pub struct CraftingTableScreenHandler {
}
impl CraftingTableScreenHandler {
pub async fn new(
pub fn new(
sync_id: u8,
player_inventory: &Arc<PlayerInventory>,
provider: Option<Arc<dyn RecipeProvider>>,
@ -576,9 +547,7 @@ impl CraftingTableScreenHandler {
behaviour: ScreenHandlerBehaviour::new(sync_id, Some(WindowType::Crafting)),
crafting_inventory: crafting_inventory.clone(),
};
crafting_table_handler
.add_recipe_slots(crafting_inventory, provider)
.await;
crafting_table_handler.add_recipe_slots(crafting_inventory, provider);
let player_inventory: Arc<dyn Inventory> = player_inventory.clone();
crafting_table_handler.add_player_slots(&player_inventory);
crafting_table_handler
@ -600,71 +569,60 @@ impl ScreenHandler for CraftingTableScreenHandler {
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.drop_inventory(player, self.crafting_inventory.clone())
.await;
})
fn on_closed(&mut self, player: &dyn InventoryPlayer) {
self.default_on_closed(player);
self.drop_inventory(player, self.crafting_inventory.clone());
}
fn quick_move<'a>(
&'a mut self,
player: &'a dyn InventoryPlayer,
slot_index: i32,
) -> ItemStackFuture<'a> {
Box::pin(async move {
let slot = self.get_behaviour().slots[slot_index as usize].clone();
if slot.has_stack().await {
let mut slot_stack = slot.get_stack().await;
let stack_prev = slot_stack.clone();
if slot_index == 0 {
if !self.insert_item(&mut slot_stack, 10, 46, true).await {
return ItemStack::EMPTY.clone();
}
} else if (1..=9).contains(&slot_index) {
if !self.insert_item(&mut slot_stack, 10, 46, false).await {
return ItemStack::EMPTY.clone();
}
} else if (10..46).contains(&slot_index) {
if !self.insert_item(&mut slot_stack, 1, 10, false).await {
if slot_index < 37 {
if !self.insert_item(&mut slot_stack, 37, 46, false).await {
return ItemStack::EMPTY.clone();
}
} else if !self.insert_item(&mut slot_stack, 10, 37, false).await {
fn quick_move(&mut self, player: &dyn InventoryPlayer, slot_index: i32) -> ItemStack {
let slot = self.get_behaviour().slots[slot_index as usize].clone();
if slot.has_stack() {
let mut slot_stack = slot.get_stack();
let stack_prev = slot_stack.clone();
if slot_index == 0 {
if !self.insert_item(&mut slot_stack, 10, 46, true) {
return ItemStack::EMPTY.clone();
}
} else if (1..=9).contains(&slot_index) {
if !self.insert_item(&mut slot_stack, 10, 46, false) {
return ItemStack::EMPTY.clone();
}
} else if (10..46).contains(&slot_index) {
if !self.insert_item(&mut slot_stack, 1, 10, false) {
if slot_index < 37 {
if !self.insert_item(&mut slot_stack, 37, 46, false) {
return ItemStack::EMPTY.clone();
}
}
} else if !self.insert_item(&mut slot_stack, 10, 46, false).await {
return ItemStack::EMPTY.clone();
}
let stack = slot_stack.clone();
drop(slot_stack);
if stack.is_empty() {
slot.set_stack_prev(ItemStack::EMPTY.clone(), stack_prev.clone())
.await;
} else {
slot.mark_dirty().await;
}
if stack.item_count == stack_prev.item_count {
return ItemStack::EMPTY.clone();
}
let mut taken_stack = stack_prev.clone();
taken_stack.set_count(stack_prev.item_count - stack.item_count);
slot.on_take_item(player, &taken_stack).await;
if slot_index == 0 {
slot.on_quick_move_crafted(stack.clone(), stack_prev.clone())
.await;
if !stack.is_empty() {
player.drop_item(stack, false).await;
} else if !self.insert_item(&mut slot_stack, 10, 37, false) {
return ItemStack::EMPTY.clone();
}
}
return stack_prev;
} else if !self.insert_item(&mut slot_stack, 10, 46, false) {
return ItemStack::EMPTY.clone();
}
ItemStack::EMPTY.clone()
})
let stack = slot_stack.clone();
drop(slot_stack);
if stack.is_empty() {
slot.set_stack_prev(ItemStack::EMPTY.clone(), stack_prev.clone());
} else {
slot.mark_dirty();
}
if stack.item_count == stack_prev.item_count {
return ItemStack::EMPTY.clone();
}
let mut taken_stack = stack_prev.clone();
taken_stack.set_count(stack_prev.item_count - stack.item_count);
slot.on_take_item(player, &taken_stack);
if slot_index == 0 {
slot.on_quick_move_crafted(stack.clone(), stack_prev.clone());
if !stack.is_empty() {
player.drop_item(stack, false);
}
}
return stack_prev;
}
ItemStack::EMPTY.clone()
}
}

View File

@ -1,15 +1,16 @@
use crate::slot::BoxFuture;
use pumpkin_protocol::codec::recipe::DynamicRecipe;
pub trait RecipeProvider: Send + Sync {
fn get_dynamic_recipes(&self) -> BoxFuture<'_, Vec<DynamicRecipe>>;
fn get_dynamic_recipes(&self) -> Vec<DynamicRecipe>;
}
#[derive(Clone, Copy)]
pub enum GenericRecipe<'a> {
Vanilla(&'a pumpkin_data::recipes::CraftingRecipeTypes),
Dynamic(&'a pumpkin_protocol::codec::recipe::OwnedCraftingRecipe),
}
#[derive(Clone, Copy)]
pub enum IngredientRef<'a> {
Vanilla(&'a pumpkin_data::recipes::RecipeIngredientTypes),
Dynamic(&'a pumpkin_protocol::codec::recipe::OwnedRecipeIngredient),

View File

@ -8,10 +8,10 @@
//! slots. Operations are delegated to the appropriate underlying inventory
//! based on the slot index.
use std::{any::Any, pin::Pin, sync::Arc};
use std::{any::Any, sync::Arc};
use pumpkin_data::item_stack::ItemStack;
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture};
use pumpkin_world::inventory::{Clearable, Inventory};
/// A composite inventory combining two inventories.
///
@ -44,64 +44,51 @@ impl Inventory for DoubleInventory {
self.first.size() + self.second.size()
}
fn is_empty(&self) -> InventoryFuture<'_, bool> {
Box::pin(async move { self.first.is_empty().await && self.second.is_empty().await })
fn is_empty(&self) -> bool {
self.first.is_empty() && self.second.is_empty()
}
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move {
if slot >= self.first.size() {
self.second.get_stack(slot - self.first.size()).await
} else {
self.first.get_stack(slot).await
}
})
fn get_stack(&self, slot: usize) -> ItemStack {
if slot >= self.first.size() {
self.second.get_stack(slot - self.first.size())
} else {
self.first.get_stack(slot)
}
}
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move {
if slot >= self.first.size() {
self.second.remove_stack(slot - self.first.size()).await
} else {
self.first.remove_stack(slot).await
}
})
fn remove_stack(&self, slot: usize) -> ItemStack {
if slot >= self.first.size() {
self.second.remove_stack(slot - self.first.size())
} else {
self.first.remove_stack(slot)
}
}
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move {
if slot >= self.first.size() {
self.second
.remove_stack_specific(slot - self.first.size(), amount)
.await
} else {
self.first.remove_stack_specific(slot, amount).await
}
})
fn remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack {
if slot >= self.first.size() {
self.second
.remove_stack_specific(slot - self.first.size(), amount)
} else {
self.first.remove_stack_specific(slot, amount)
}
}
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
Box::pin(async move {
if slot >= self.first.size() {
self.second.set_stack(slot - self.first.size(), stack).await;
} else {
self.first.set_stack(slot, stack).await;
}
})
fn set_stack(&self, slot: usize, stack: ItemStack) {
if slot >= self.first.size() {
self.second.set_stack(slot - self.first.size(), stack);
} else {
self.first.set_stack(slot, stack);
}
}
fn on_open(&self) -> InventoryFuture<'_, ()> {
Box::pin(async move {
self.first.on_open().await;
self.second.on_open().await;
})
fn on_open(&self) {
self.first.on_open();
self.second.on_open();
}
fn on_close(&self) -> InventoryFuture<'_, ()> {
Box::pin(async move {
self.first.on_close().await;
self.second.on_close().await;
})
fn on_close(&self) {
self.first.on_close();
self.second.on_close();
}
fn get_max_count_per_stack(&self) -> u8 {
@ -128,10 +115,8 @@ impl Inventory for DoubleInventory {
}
impl Clearable for DoubleInventory {
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move {
self.first.clear().await;
self.second.clear().await;
})
fn clear(&self) {
self.first.clear();
self.second.clear();
}
}

View File

@ -13,11 +13,8 @@ use pumpkin_world::inventory::Inventory;
use crate::{
player::player_inventory::PlayerInventory,
screen_handler::{
InventoryPlayer, ItemStackFuture, ScreenHandler, ScreenHandlerBehaviour,
ScreenHandlerFuture, offer_or_drop_stack,
},
slot::{BoxFuture, NormalSlot, Slot},
screen_handler::{InventoryPlayer, ScreenHandler, ScreenHandlerBehaviour, offer_or_drop_stack},
slot::{NormalSlot, Slot},
window_property::{EnchantmentTable, WindowProperty},
};
@ -46,12 +43,12 @@ impl Slot for LapisSlot {
self.0.set_id(id);
}
fn can_insert<'a>(&'a self, stack: &'a ItemStack) -> BoxFuture<'a, bool> {
Box::pin(async move { is_lapis(stack) })
fn can_insert(&self, stack: &ItemStack) -> bool {
is_lapis(stack)
}
fn mark_dirty(&self) -> BoxFuture<'_, ()> {
self.0.mark_dirty()
fn mark_dirty(&self) {
self.0.mark_dirty();
}
}
@ -93,8 +90,8 @@ impl EnchantingTableScreenHandler {
handler
}
pub async fn update_enchantments(&mut self, player: &dyn InventoryPlayer) {
let item = self.inventory.get_stack(0).await;
pub fn update_enchantments(&mut self, player: &dyn InventoryPlayer) {
let item = self.inventory.get_stack(0);
if item.is_empty() || item.has_enchantments() {
for i in 0..3 {
@ -146,16 +143,13 @@ impl EnchantingTableScreenHandler {
}
}
if player
.fire_prepare_item_enchant_event(
&item,
&mut self.level_requirements,
&mut self.enchantment_id,
&mut self.enchantment_level,
self.bookshelf_count,
)
.await
{
if player.fire_prepare_item_enchant_event(
&item,
&mut self.level_requirements,
&mut self.enchantment_id,
&mut self.enchantment_level,
self.bookshelf_count,
) {
for i in 0..3 {
self.level_requirements[i] = 0;
self.enchantment_id[i] = -1;
@ -164,7 +158,7 @@ impl EnchantingTableScreenHandler {
}
}
}
self.send_property_updates().await;
self.send_property_updates();
}
fn calculate_level_requirement(
@ -276,7 +270,7 @@ impl EnchantingTableScreenHandler {
result
}
async fn send_property_updates(&self) {
fn send_property_updates(&self) {
if let Some(sync_handler) = self.behaviour.sync_handler.as_ref() {
for i in 0..3 {
let (id, val) = WindowProperty::new(
@ -284,27 +278,21 @@ impl EnchantingTableScreenHandler {
self.level_requirements[i] as i16,
)
.into_tuple();
sync_handler
.update_property(&self.behaviour, id as i32, val as i32)
.await;
sync_handler.update_property(&self.behaviour, id as i32, val as i32);
let (id, val) = WindowProperty::new(
EnchantmentTable::EnchantmentId { slot: i as u8 },
self.enchantment_id[i] as i16,
)
.into_tuple();
sync_handler
.update_property(&self.behaviour, id as i32, val as i32)
.await;
sync_handler.update_property(&self.behaviour, id as i32, val as i32);
let (id, val) = WindowProperty::new(
EnchantmentTable::EnchantmentLevel { slot: i as u8 },
self.enchantment_level[i] as i16,
)
.into_tuple();
sync_handler
.update_property(&self.behaviour, id as i32, val as i32)
.await;
sync_handler.update_property(&self.behaviour, id as i32, val as i32);
}
let (id, val) = WindowProperty::new(
@ -312,9 +300,7 @@ impl EnchantingTableScreenHandler {
(self.enchantment_seed & 0xFFFF) as i16,
)
.into_tuple();
sync_handler
.update_property(&self.behaviour, id as i32, val as i32)
.await;
sync_handler.update_property(&self.behaviour, id as i32, val as i32);
}
}
}
@ -336,165 +322,141 @@ impl ScreenHandler for EnchantingTableScreenHandler {
&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;
// Return items to player
for i in 0..2 {
let stack = self.inventory.remove_stack(i).await;
if !stack.is_empty() {
offer_or_drop_stack(player, stack).await;
fn on_closed(&mut self, player: &dyn InventoryPlayer) {
self.default_on_closed(player);
self.inventory.on_close();
// Return items to player
for i in 0..2 {
let stack = self.inventory.remove_stack(i);
if !stack.is_empty() {
offer_or_drop_stack(player, stack);
}
}
}
fn on_button_click(&mut self, player: &dyn InventoryPlayer, id: i32) -> bool {
if !(0..3).contains(&id) {
return false;
}
let level_req = self.level_requirements[id as usize];
if player.experience_level() < level_req && !player.is_creative() {
return false;
}
let mut lapis_stack = self.inventory.get_stack(1);
let lapis_cost = (id + 1) as u8;
if !player.is_creative()
&& (lapis_stack.is_empty()
|| !is_lapis(&lapis_stack)
|| lapis_stack.item_count < lapis_cost)
{
return false;
}
// Perform enchantment
let mut item_stack = self.inventory.get_stack(0);
if item_stack.is_empty() || item_stack.has_enchantments() {
return false;
}
let mut random = self.create_enchantment_random(id as usize);
let mut enchantments =
Self::get_enchantment_list(&mut random, &item_stack, id as usize, level_req);
if enchantments.is_empty() {
return false;
}
if player.fire_enchant_item_event(&item_stack, id, level_req, &mut enchantments)
|| enchantments.is_empty()
{
return false;
}
if !player.is_creative() {
player.add_experience_levels(-(id + 1));
lapis_stack.decrement(lapis_cost);
self.inventory.set_stack(1, lapis_stack);
}
for (enchant, level) in enchantments {
item_stack.add_enchantment(enchant, level as u16);
}
self.inventory.set_stack(0, item_stack);
// Update seed
player.set_enchantment_seed(rand::random());
self.enchantment_seed = player.enchantment_seed();
self.update_enchantments(player);
self.send_content_updates();
player.increment_stat(
StatisticCategory::Custom,
CustomStatistic::EnchantItem as i32,
1,
);
true
}
fn quick_move(&mut self, player: &dyn InventoryPlayer, slot_index: i32) -> ItemStack {
let mut stack_left = ItemStack::EMPTY.clone();
let slot = self.get_behaviour().slots[slot_index as usize].clone();
if slot.has_stack() {
let mut slot_stack = slot.get_stack();
stack_left = slot_stack.clone();
if slot_index < 2 {
// From enchanting to player
if !self.insert_item(
&mut slot_stack,
2,
self.get_behaviour().slots.len() as i32,
true,
) {
return ItemStack::EMPTY.clone();
}
}
})
}
fn on_button_click<'a>(
&'a mut self,
player: &'a dyn InventoryPlayer,
id: i32,
) -> ScreenHandlerFuture<'a, bool> {
Box::pin(async move {
if !(0..3).contains(&id) {
return false;
}
let level_req = self.level_requirements[id as usize];
if player.experience_level() < level_req && !player.is_creative() {
return false;
}
let mut lapis_stack = self.inventory.get_stack(1).await;
let lapis_cost = (id + 1) as u8;
if !player.is_creative()
&& (lapis_stack.is_empty()
|| !is_lapis(&lapis_stack)
|| lapis_stack.item_count < lapis_cost)
{
return false;
}
// Perform enchantment
let mut item_stack = self.inventory.get_stack(0).await;
if item_stack.is_empty() || item_stack.has_enchantments() {
return false;
}
let mut random = self.create_enchantment_random(id as usize);
let mut enchantments =
Self::get_enchantment_list(&mut random, &item_stack, id as usize, level_req);
if enchantments.is_empty() {
return false;
}
if player
.fire_enchant_item_event(&item_stack, id, level_req, &mut enchantments)
.await
|| enchantments.is_empty()
{
return false;
}
if !player.is_creative() {
player.add_experience_levels(-(id + 1)).await;
lapis_stack.decrement(lapis_cost);
self.inventory.set_stack(1, lapis_stack).await;
}
for (enchant, level) in enchantments {
item_stack.add_enchantment(enchant, level as u16);
}
self.inventory.set_stack(0, item_stack).await;
// Update seed
player.set_enchantment_seed(rand::random()).await;
self.enchantment_seed = player.enchantment_seed();
self.update_enchantments(player).await;
self.send_content_updates().await;
player
.increment_stat(
StatisticCategory::Custom,
CustomStatistic::EnchantItem as i32,
1,
)
.await;
true
})
}
fn quick_move<'a>(
&'a mut self,
player: &'a dyn InventoryPlayer, // FIX: Changed _player to player
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 mut slot_stack = slot.get_stack().await;
stack_left = slot_stack.clone();
if slot_index < 2 {
// From enchanting to player
if !self
.insert_item(
&mut slot_stack,
2,
self.get_behaviour().slots.len() as i32,
true,
)
.await
{
return ItemStack::EMPTY.clone();
}
} else {
// From player to enchanting
// Lapis check
if slot_stack.item == &Item::LAPIS_LAZULI {
if !self.insert_item(&mut slot_stack, 1, 2, false).await {
return ItemStack::EMPTY.clone();
}
} else if !self.insert_item(&mut slot_stack, 0, 1, false).await {
} else {
// From player to enchanting
// Lapis check
if slot_stack.item == &Item::LAPIS_LAZULI {
if !self.insert_item(&mut slot_stack, 1, 2, false) {
return ItemStack::EMPTY.clone();
}
} else if !self.insert_item(&mut slot_stack, 0, 1, false) {
return ItemStack::EMPTY.clone();
}
if slot_stack.is_empty() {
slot.set_stack(ItemStack::EMPTY.clone()).await;
} else {
slot.set_stack(slot_stack).await;
}
// CRITICAL FIX: Ensure the client is notified when shift-clicking items into the slots
self.update_enchantments(player).await;
}
stack_left
})
if slot_stack.is_empty() {
slot.set_stack(ItemStack::EMPTY.clone());
} else {
slot.set_stack(slot_stack);
}
// CRITICAL FIX: Ensure the client is notified when shift-clicking items into the slots
self.update_enchantments(player);
}
stack_left
}
fn on_slot_click<'a>(
&'a mut self,
fn on_slot_click(
&mut self,
slot_index: i32,
button: i32,
action_type: pumpkin_protocol::java::server::play::SlotActionType,
player: &'a dyn InventoryPlayer,
) -> ScreenHandlerFuture<'a, ()> {
Box::pin(async move {
self.internal_on_slot_click(slot_index, button, action_type, player)
.await;
if slot_index == 0 || slot_index == 1 {
self.update_enchantments(player).await;
}
})
player: &dyn InventoryPlayer,
) {
self.internal_on_slot_click(slot_index, button, action_type, player);
if slot_index == 0 || slot_index == 1 {
self.update_enchantments(player);
}
}
}

View File

@ -16,7 +16,7 @@
//! - Property 2: Progress arrow (cooking/smelt time)
//! - Property 3: Maximum progress (typically 200 ticks for furnace)
use std::{any::Any, pin::Pin, sync::Arc};
use std::{any::Any, sync::Arc};
use pumpkin_data::{fuels::is_fuel, item_stack::ItemStack, screen::WindowType};
use pumpkin_world::{
@ -27,8 +27,8 @@ use pumpkin_world::{
use crate::{
player::player_inventory::PlayerInventory,
screen_handler::{
InventoryPlayer, ItemStackFuture, ScreenHandler, ScreenHandlerBehaviour,
ScreenHandlerFuture, ScreenHandlerListener, ScreenProperty,
InventoryPlayer, ScreenHandler, ScreenHandlerBehaviour, ScreenHandlerListener,
ScreenProperty,
},
};
use tracing::debug;
@ -60,29 +60,25 @@ impl FurnaceLikeScreenHandler {
/// - `property_delegate` - Delegate for accessing furnace properties
/// - `experience_container` - Container that tracks smelting experience
/// - `window_type` - The window type (Furnace, Smoker, or `BlastFurnace`)
pub async fn new(
pub fn new(
sync_id: u8,
player_inventory: &Arc<PlayerInventory>,
inventory: Arc<dyn Inventory>,
property_delegate: Arc<dyn PropertyDelegate>,
property_delegate: &Arc<dyn PropertyDelegate>,
experience_container: Arc<dyn ExperienceContainer>,
window_type: WindowType,
) -> Self {
struct FurnaceLikeScreenListener;
impl ScreenHandlerListener for FurnaceLikeScreenListener {
fn on_property_update<'a>(
&'a self,
screen_handler: &'a ScreenHandlerBehaviour,
fn on_property_update(
&self,
screen_handler: &ScreenHandlerBehaviour,
property: u8,
value: i32,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
if let Some(sync_handler) = screen_handler.sync_handler.as_ref() {
sync_handler
.update_property(screen_handler, i32::from(property), value)
.await;
}
})
) {
if let Some(sync_handler) = screen_handler.sync_handler.as_ref() {
sync_handler.update_property(screen_handler, i32::from(property), value);
}
}
}
let mut handler = Self {
@ -99,9 +95,7 @@ impl FurnaceLikeScreenHandler {
handler.add_property(ScreenProperty::new(property_delegate.clone(), i));
}
handler
.add_listener(Arc::new(FurnaceLikeScreenListener))
.await;
handler.add_listener(Arc::new(FurnaceLikeScreenListener));
handler.add_inventory_slots();
let player_inventory: Arc<dyn Inventory> = player_inventory.clone();
handler.add_player_slots(&player_inventory);
@ -148,11 +142,9 @@ impl ScreenHandler for FurnaceLikeScreenHandler {
&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;
// TODO: self.inventory.on_closed(player).await;
})
fn on_closed(&mut self, player: &dyn InventoryPlayer) {
self.default_on_closed(player);
// TODO: self.inventory.on_closed(player);
}
/// Quick move logic for furnace-like containers.
@ -160,60 +152,53 @@ impl ScreenHandler for FurnaceLikeScreenHandler {
/// - From furnace slots (0-2): Move to player inventory
/// - Fuel items: Move to fuel slot (1)
/// - Other items: Move to input slot (0)
fn quick_move<'a>(
&'a mut self,
player: &'a dyn InventoryPlayer,
slot_index: i32,
) -> ItemStackFuture<'a> {
Box::pin(async move {
const FUEL_SLOT: i32 = 1; // Note: Slots 0, 1, 2 are Furnace slots.
const OUTPUT_SLOT: i32 = 2;
fn quick_move(&mut self, player: &dyn InventoryPlayer, slot_index: i32) -> ItemStack {
const FUEL_SLOT: i32 = 1; // Note: Slots 0, 1, 2 are Furnace slots.
const OUTPUT_SLOT: i32 = 2;
debug!("FurnaceLikeScreenHandler::quick_move slot_index={slot_index}");
debug!("FurnaceLikeScreenHandler::quick_move slot_index={slot_index}");
let mut stack_left = ItemStack::EMPTY.clone();
let mut stack_left = ItemStack::EMPTY.clone();
let slot = self.get_behaviour().slots[slot_index as usize].clone();
let slot = self.get_behaviour().slots[slot_index as usize].clone();
if !slot.has_stack().await {
return stack_left;
}
if !slot.has_stack() {
return stack_left;
}
let mut stack = slot.get_stack().await;
stack_left = stack.clone();
let mut stack = slot.get_stack();
stack_left = stack.clone();
let success = if slot_index < 3 {
// If clicked slot is one of the Furnace slots (0, 1, 2):
// Try to move to player inventory (slots 3 onwards, starting from the end)
self.insert_item(&mut stack, 3, self.get_behaviour().slots.len() as i32, true)
.await
} else if is_fuel(stack.item.id) {
// If clicked slot is in the player inventory (3+) and contains fuel:
// Try to move to the Furnace's Fuel slot (slot 1)
self.insert_item(&mut stack, FUEL_SLOT, 3, false).await
} else {
// If clicked slot is in the player inventory (3+) and NOT fuel (must be a smeltable item):
// Try to move to the Furnace's Input/Smelting slot (slot 0)
self.insert_item(&mut stack, 0, 3, false).await
};
let success = if slot_index < 3 {
// If clicked slot is one of the Furnace slots (0, 1, 2):
// Try to move to player inventory (slots 3 onwards, starting from the end)
self.insert_item(&mut stack, 3, self.get_behaviour().slots.len() as i32, true)
} else if is_fuel(stack.item.id) {
// If clicked slot is in the player inventory (3+) and contains fuel:
// Try to move to the Furnace's Fuel slot (slot 1)
self.insert_item(&mut stack, FUEL_SLOT, 3, false)
} else {
// If clicked slot is in the player inventory (3+) and NOT fuel (must be a smeltable item):
// Try to move to the Furnace's Input/Smelting slot (slot 0)
self.insert_item(&mut stack, 0, 3, false)
};
if !success {
return ItemStack::EMPTY.clone();
}
if !success {
return ItemStack::EMPTY.clone();
}
if stack.is_empty() {
slot.set_stack(ItemStack::EMPTY.clone()).await;
} else {
slot.set_stack(stack).await;
}
if stack.is_empty() {
slot.set_stack(ItemStack::EMPTY.clone());
} else {
slot.set_stack(stack);
}
// Award XP when taking from output slot (slot 2)
if slot_index == OUTPUT_SLOT {
debug!("quick_move: taking from output slot, calling on_take_item");
slot.on_take_item(player, &stack_left).await;
}
// Award XP when taking from output slot (slot 2)
if slot_index == OUTPUT_SLOT {
debug!("quick_move: taking from output slot, calling on_take_item");
slot.on_take_item(player, &stack_left);
}
stack_left
})
stack_left
}
}

View File

@ -13,10 +13,7 @@ use pumpkin_world::{block::entities::ExperienceContainer, inventory::Inventory};
use tracing::debug;
use crate::{
screen_handler::InventoryPlayer,
slot::{BoxFuture, Slot},
};
use crate::{screen_handler::InventoryPlayer, slot::Slot};
/// Type of furnace slot.
#[derive(Debug, Clone, Copy)]
@ -72,24 +69,17 @@ impl Slot for FurnaceLikeSlot {
///
/// - Top slot: accepts any item (smeltables)
/// - Bottom slot: only accepts fuel items and buckets
fn can_insert<'a>(
&'a self,
stack: &'a pumpkin_data::item_stack::ItemStack,
) -> BoxFuture<'a, bool> {
Box::pin(async move {
match self.slot_type {
FurnaceLikeSlotType::Top => true,
FurnaceLikeSlotType::Bottom => {
is_fuel(stack.item.id) || stack.item.id == Item::BUCKET.id
}
fn can_insert(&self, stack: &pumpkin_data::item_stack::ItemStack) -> bool {
match self.slot_type {
FurnaceLikeSlotType::Top => true,
FurnaceLikeSlotType::Bottom => {
is_fuel(stack.item.id) || stack.item.id == Item::BUCKET.id
}
})
}
}
fn mark_dirty(&self) -> BoxFuture<'_, ()> {
Box::pin(async move {
self.inventory.mark_dirty();
})
fn mark_dirty(&self) {
self.inventory.mark_dirty();
}
}
@ -137,43 +127,34 @@ impl Slot for FurnaceOutputSlot {
}
/// Awards experience when items are taken from this slot.
fn on_take_item<'a>(
&'a self,
player: &'a dyn InventoryPlayer,
stack: &'a pumpkin_data::item_stack::ItemStack,
) -> BoxFuture<'a, ()> {
Box::pin(async move {
debug!("FurnaceOutputSlot: on_take_item called");
player
.increment_stat(
StatisticCategory::Crafted,
stack.item.id as i32,
stack.item_count as i32,
)
.await;
// Extract accumulated experience and award to player
let experience = self.experience_container.extract_experience();
debug!("FurnaceOutputSlot: extracted experience = {experience}");
if experience > 0 {
debug!("FurnaceOutputSlot: awarding {experience} xp to player");
player.award_experience(experience).await;
}
self.mark_dirty().await;
})
fn on_take_item(
&self,
player: &dyn InventoryPlayer,
stack: &pumpkin_data::item_stack::ItemStack,
) {
debug!("FurnaceOutputSlot: on_take_item called");
player.increment_stat(
StatisticCategory::Crafted,
stack.item.id as i32,
stack.item_count as i32,
);
// Extract accumulated experience and award to player
let experience = self.experience_container.extract_experience();
debug!("FurnaceOutputSlot: extracted experience = {experience}");
if experience > 0 {
debug!("FurnaceOutputSlot: awarding {experience} xp to player");
player.award_experience(experience);
}
self.mark_dirty();
}
/// Output slot cannot receive inserted items.
fn can_insert<'a>(
&'a self,
_stack: &'a pumpkin_data::item_stack::ItemStack,
) -> BoxFuture<'a, bool> {
fn can_insert(&self, _stack: &pumpkin_data::item_stack::ItemStack) -> bool {
// Cannot insert items into the output slot
Box::pin(async move { false })
false
}
fn mark_dirty(&self) -> BoxFuture<'_, ()> {
Box::pin(async move {
self.inventory.mark_dirty();
})
fn mark_dirty(&self) {
self.inventory.mark_dirty();
}
}

View File

@ -16,17 +16,14 @@ use pumpkin_world::inventory::Inventory;
use crate::{
player::player_inventory::PlayerInventory,
screen_handler::{
InventoryPlayer, ItemStackFuture, ScreenHandler, ScreenHandlerBehaviour,
ScreenHandlerFuture,
},
screen_handler::{InventoryPlayer, ScreenHandler, ScreenHandlerBehaviour},
slot::NormalSlot,
};
/// Creates a generic 9x3 container (single chest).
///
/// Used for single chests, ender chests, and similar containers.
pub async fn create_generic_9x3(
pub fn create_generic_9x3(
sync_id: u8,
player_inventory: &Arc<PlayerInventory>,
inventory: Arc<dyn Inventory>,
@ -39,13 +36,12 @@ pub async fn create_generic_9x3(
3,
9,
)
.await
}
/// Creates a generic 9x6 container (double chest).
///
/// Used for double chests and similar large containers.
pub async fn create_generic_9x6(
pub fn create_generic_9x6(
sync_id: u8,
player_inventory: &Arc<PlayerInventory>,
inventory: Arc<dyn Inventory>,
@ -58,13 +54,12 @@ pub async fn create_generic_9x6(
6,
9,
)
.await
}
/// Creates a generic 3x3 container.
///
/// Used for dispensers, droppers, and similar containers.
pub async fn create_generic_3x3(
pub fn create_generic_3x3(
sync_id: u8,
player_inventory: &Arc<PlayerInventory>,
inventory: Arc<dyn Inventory>,
@ -77,11 +72,10 @@ pub async fn create_generic_3x3(
3,
3,
)
.await
}
/// Creates a crafter container (9 slots, 3x3 layout).
pub async fn create_crafter_3x3(
pub fn create_crafter_3x3(
sync_id: u8,
player_inventory: &Arc<PlayerInventory>,
inventory: Arc<dyn Inventory>,
@ -94,13 +88,12 @@ pub async fn create_crafter_3x3(
3,
3,
)
.await
}
/// Creates a hopper container (5 slots).
///
/// Hoppers have a single row of 5 slots.
pub async fn create_hopper(
pub fn create_hopper(
sync_id: u8,
player_inventory: &Arc<PlayerInventory>,
inventory: Arc<dyn Inventory>,
@ -113,7 +106,6 @@ pub async fn create_hopper(
1,
5,
)
.await
}
/// Generic container screen handler.
@ -141,7 +133,7 @@ impl GenericContainerScreenHandler {
/// - `inventory` - The container's inventory
/// - `rows` - Number of rows in the container
/// - `columns` - Number of columns in the container
async fn new(
fn new(
screen_type: WindowType,
sync_id: u8,
player_inventory: &Arc<PlayerInventory>,
@ -150,14 +142,14 @@ impl GenericContainerScreenHandler {
columns: u8,
) -> Self {
let mut handler = Self {
inventory: inventory.clone(),
inventory,
rows,
columns,
behaviour: ScreenHandlerBehaviour::new(sync_id, Some(screen_type)),
};
// TODO: Add player entity as a parameter
inventory.on_open().await;
handler.inventory.on_open();
handler.add_inventory_slots();
let player_inventory: Arc<dyn Inventory> = player_inventory.clone();
@ -196,61 +188,47 @@ impl ScreenHandler for GenericContainerScreenHandler {
&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;
})
fn on_closed(&mut self, player: &dyn InventoryPlayer) {
self.default_on_closed(player);
self.inventory.on_close();
}
/// Quick move logic for generic containers.
///
/// - From container: Move to player inventory (end first)
/// - From player inventory: Move to container (start first)
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();
// Assuming bounds check passed for slot_index by caller or within quick_move spec
let slot = self.get_behaviour().slots[slot_index as usize].clone();
fn quick_move(&mut self, _player: &dyn InventoryPlayer, slot_index: i32) -> ItemStack {
let mut stack_left = ItemStack::EMPTY.clone();
// Assuming bounds check passed for slot_index by caller or within quick_move spec
let slot = self.get_behaviour().slots[slot_index as usize].clone();
if slot.has_stack().await {
let mut slot_stack = slot.get_stack().await;
stack_left = slot_stack.clone();
if slot.has_stack() {
let mut slot_stack = slot.get_stack();
stack_left = slot_stack.clone();
if slot_index < i32::from(self.rows * 9) {
// Move from inventory to player area (end)
if !self
.insert_item(
&mut slot_stack,
(self.rows * 9).into(),
self.get_behaviour().slots.len() as i32,
true,
)
.await
{
return ItemStack::EMPTY.clone();
}
} else if !self
.insert_item(&mut slot_stack, 0, (self.rows * 9).into(), false)
.await
{
// Move from player area to inventory (start)
if slot_index < i32::from(self.rows * 9) {
// Move from inventory to player area (end)
if !self.insert_item(
&mut slot_stack,
(self.rows * 9).into(),
self.get_behaviour().slots.len() as i32,
true,
) {
return ItemStack::EMPTY.clone();
}
// Check the resulting state of the slot stack after insert_item
if slot_stack.is_empty() {
slot.set_stack(ItemStack::EMPTY.clone()).await;
} else {
slot.set_stack(slot_stack).await;
}
} else if !self.insert_item(&mut slot_stack, 0, (self.rows * 9).into(), false) {
// Move from player area to inventory (start)
return ItemStack::EMPTY.clone();
}
stack_left
})
// Check the resulting state of the slot stack after insert_item
if slot_stack.is_empty() {
slot.set_stack(ItemStack::EMPTY.clone());
} else {
slot.set_stack(slot_stack);
}
}
stack_left
}
}

View File

@ -1,9 +1,6 @@
use crate::{
player::player_inventory::PlayerInventory,
screen_handler::{
InventoryPlayer, ItemStackFuture, ScreenHandler, ScreenHandlerBehaviour,
ScreenHandlerFuture,
},
screen_handler::{InventoryPlayer, ScreenHandler, ScreenHandlerBehaviour},
slot::NormalSlot,
};
use pumpkin_data::{item_stack::ItemStack, screen::WindowType};
@ -59,11 +56,7 @@ impl GUIBuilder {
}
/// Builds the `GUIScreenHandler`.
pub async fn build(
self,
sync_id: u8,
player_inventory: &Arc<PlayerInventory>,
) -> GUIScreenHandler {
pub fn build(self, sync_id: u8, player_inventory: &Arc<PlayerInventory>) -> GUIScreenHandler {
let mut behaviour = ScreenHandlerBehaviour::new(sync_id, Some(self.screen_type));
behaviour.allow_grab_items = self.allow_grab_items;
behaviour.allow_put_items = self.allow_put_items;
@ -76,7 +69,7 @@ impl GUIBuilder {
behaviour,
};
self.inventory.on_open().await;
self.inventory.on_open();
handler.add_inventory_slots();
let player_inventory_trait: Arc<dyn Inventory> = player_inventory.clone();
@ -123,65 +116,51 @@ impl ScreenHandler for GUIScreenHandler {
&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;
})
fn on_closed(&mut self, player: &dyn InventoryPlayer) {
self.default_on_closed(player);
self.inventory.on_close();
}
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();
fn quick_move(&mut self, _player: &dyn InventoryPlayer, slot_index: i32) -> ItemStack {
let mut stack_left = ItemStack::EMPTY.clone();
let slot = self.get_behaviour().slots[slot_index as usize].clone();
if slot.has_stack().await {
let mut slot_stack = slot.get_stack().await;
stack_left = slot_stack.clone();
let container_slots = i32::from(self.rows * self.columns);
if slot.has_stack() {
let mut slot_stack = slot.get_stack();
stack_left = slot_stack.clone();
let container_slots = i32::from(self.rows * self.columns);
if slot_index < container_slots {
// From container to player
if !self.get_behaviour().allow_grab_items {
return ItemStack::EMPTY.clone();
}
if !self
.insert_item(
&mut slot_stack,
container_slots,
self.get_behaviour().slots.len() as i32,
true,
)
.await
{
return ItemStack::EMPTY.clone();
}
} else {
// From player to container
if !self.get_behaviour().allow_put_items {
return ItemStack::EMPTY.clone();
}
if !self
.insert_item(&mut slot_stack, 0, container_slots, false)
.await
{
// Move from player area to inventory (start)
return ItemStack::EMPTY.clone();
}
if slot_index < container_slots {
// From container to player
if !self.get_behaviour().allow_grab_items {
return ItemStack::EMPTY.clone();
}
if slot_stack.is_empty() {
slot.set_stack(ItemStack::EMPTY.clone()).await;
} else {
slot.set_stack(slot_stack).await;
if !self.insert_item(
&mut slot_stack,
container_slots,
self.get_behaviour().slots.len() as i32,
true,
) {
return ItemStack::EMPTY.clone();
}
} else {
// From player to container
if !self.get_behaviour().allow_put_items {
return ItemStack::EMPTY.clone();
}
if !self.insert_item(&mut slot_stack, 0, container_slots, false) {
// Move from player area to inventory (start)
return ItemStack::EMPTY.clone();
}
}
stack_left
})
if slot_stack.is_empty() {
slot.set_stack(ItemStack::EMPTY.clone());
} else {
slot.set_stack(slot_stack);
}
}
stack_left
}
}

View File

@ -2,8 +2,7 @@ use std::any::Any;
use std::sync::Arc;
use crate::screen_handler::{
InventoryPlayer, ItemStackFuture, ScreenHandler, ScreenHandlerBehaviour, ScreenHandlerFuture,
ScreenProperty, offer_or_drop_stack,
InventoryPlayer, ScreenHandler, ScreenHandlerBehaviour, ScreenProperty, offer_or_drop_stack,
};
use crate::slot::NormalSlot;
@ -19,10 +18,10 @@ pub trait LecternController: Send + Sync {
fn current_page(&self) -> i32;
/// Clamps and persists `page`, emitting a redstone pulse when it changes.
fn set_page(&self, page: i32) -> ScreenHandlerFuture<'_, ()>;
fn set_page(&self, page: i32);
/// Restores the bookless block state after the book was taken.
fn on_book_taken(&self) -> ScreenHandlerFuture<'_, ()>;
fn on_book_taken(&self);
}
/// Exposes the current page as container property 0 (see `window_property::Lectern`).
@ -91,53 +90,37 @@ impl ScreenHandler for LecternScreenHandler {
&mut self.behaviour
}
fn on_button_click<'a>(
&'a mut self,
player: &'a dyn InventoryPlayer,
id: i32,
) -> ScreenHandlerFuture<'a, bool> {
Box::pin(async move {
match id {
Self::PREVIOUS_PAGE_BUTTON_ID => {
self.controller
.set_page(self.controller.current_page() - 1)
.await;
true
}
Self::NEXT_PAGE_BUTTON_ID => {
self.controller
.set_page(self.controller.current_page() + 1)
.await;
true
}
Self::TAKE_BOOK_BUTTON_ID => {
let stack = self.inventory.remove_stack(0).await;
if stack.is_empty() {
return false;
}
self.inventory.mark_dirty();
self.controller.on_book_taken().await;
offer_or_drop_stack(player, stack).await;
self.send_content_updates().await;
true
}
_ if id >= Self::JUMP_TO_PAGE_OFFSET => {
self.controller
.set_page(id - Self::JUMP_TO_PAGE_OFFSET)
.await;
true
}
_ => false,
fn on_button_click(&mut self, player: &dyn InventoryPlayer, id: i32) -> bool {
match id {
Self::PREVIOUS_PAGE_BUTTON_ID => {
self.controller.set_page(self.controller.current_page() - 1);
true
}
})
Self::NEXT_PAGE_BUTTON_ID => {
self.controller.set_page(self.controller.current_page() + 1);
true
}
Self::TAKE_BOOK_BUTTON_ID => {
let stack = self.inventory.remove_stack(0);
if stack.is_empty() {
return false;
}
self.inventory.mark_dirty();
self.controller.on_book_taken();
offer_or_drop_stack(player, stack);
self.send_content_updates();
true
}
_ if id >= Self::JUMP_TO_PAGE_OFFSET => {
self.controller.set_page(id - Self::JUMP_TO_PAGE_OFFSET);
true
}
_ => false,
}
}
fn quick_move<'a>(
&'a mut self,
_player: &'a dyn InventoryPlayer,
_slot_index: i32,
) -> ItemStackFuture<'a> {
fn quick_move(&mut self, _player: &dyn InventoryPlayer, _slot_index: i32) -> ItemStack {
// The lectern screen has no player slots, so nothing can be shift-clicked.
Box::pin(async move { ItemStack::EMPTY.clone() })
ItemStack::EMPTY.clone()
}
}

View File

@ -2,9 +2,7 @@ use std::any::Any;
use std::sync::Arc;
use crate::player::player_inventory::PlayerInventory;
use crate::screen_handler::{
InventoryPlayer, ScreenHandler, ScreenHandlerBehaviour, ScreenHandlerFuture,
};
use crate::screen_handler::{InventoryPlayer, ScreenHandler, ScreenHandlerBehaviour};
use crate::slot::NormalSlot;
use pumpkin_data::item_stack::ItemStack;
@ -66,48 +64,39 @@ impl ScreenHandler for LoomScreenHandler {
self
}
fn on_slot_click<'a>(
&'a mut self,
fn on_slot_click(
&mut self,
slot_index: i32,
button: i32,
action_type: SlotActionType,
player: &'a dyn InventoryPlayer,
) -> ScreenHandlerFuture<'a, ()> {
Box::pin(async move {
self.internal_on_slot_click(slot_index, button, action_type, player)
.await;
})
player: &dyn InventoryPlayer,
) {
self.internal_on_slot_click(slot_index, button, action_type, player);
}
fn quick_move<'a>(
&'a mut self,
_player: &'a dyn InventoryPlayer,
slot_index: i32,
) -> ScreenHandlerFuture<'a, ItemStack> {
Box::pin(async move {
let mut stack = ItemStack::EMPTY.clone();
let slot = self.get_behaviour().slots.get(slot_index as usize).cloned();
fn quick_move(&mut self, _player: &dyn InventoryPlayer, slot_index: i32) -> ItemStack {
let mut stack = ItemStack::EMPTY.clone();
let slot = self.get_behaviour().slots.get(slot_index as usize).cloned();
if let Some(slot) = slot {
let mut slot_stack = slot.get_cloned_stack().await;
if !slot_stack.is_empty() {
stack = slot_stack.clone();
if slot_index < 4 {
if !self.insert_item(&mut slot_stack, 4, 40, true).await {
return ItemStack::EMPTY.clone();
}
} else if !self.insert_item(&mut slot_stack, 0, 3, false).await {
if let Some(slot) = slot {
let mut slot_stack = slot.get_cloned_stack();
if !slot_stack.is_empty() {
stack = slot_stack.clone();
if slot_index < 4 {
if !self.insert_item(&mut slot_stack, 4, 40, true) {
return ItemStack::EMPTY.clone();
}
} else if !self.insert_item(&mut slot_stack, 0, 3, false) {
return ItemStack::EMPTY.clone();
}
if slot_stack.is_empty() {
slot.set_stack(ItemStack::EMPTY.clone()).await;
} else {
slot.set_stack(slot_stack).await;
}
if slot_stack.is_empty() {
slot.set_stack(ItemStack::EMPTY.clone());
} else {
slot.set_stack(slot_stack);
}
}
stack
})
}
stack
}
}

View File

@ -10,13 +10,13 @@
//! Ender chests track when players open and close them to properly
//! manage the viewer count for animation purposes.
use std::sync::{Mutex, RwLock};
use std::{any::Any, pin::Pin, sync::Arc};
use std::any::Any;
use std::sync::{Arc, Mutex, RwLock};
use pumpkin_data::item_stack::ItemStack;
use pumpkin_world::{
block::viewer::ViewerCountTracker,
inventory::{Clearable, Inventory, InventoryFuture},
inventory::{Clearable, Inventory},
};
/// A player's ender chest inventory.
@ -93,93 +93,79 @@ impl Inventory for EnderChestInventory {
Self::INVENTORY_SIZE
}
fn is_empty(&self) -> InventoryFuture<'_, bool> {
Box::pin(async move {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items.iter().all(ItemStack::is_empty)
})
fn is_empty(&self) -> bool {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items.iter().all(ItemStack::is_empty)
}
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items
.get(slot)
.cloned()
.unwrap_or_else(|| ItemStack::EMPTY.clone())
})
fn get_stack(&self, slot: usize) -> ItemStack {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items
.get(slot)
.cloned()
.unwrap_or_else(|| ItemStack::EMPTY.clone())
}
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if slot < Self::INVENTORY_SIZE {
std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone())
} else {
ItemStack::EMPTY.clone()
}
})
fn remove_stack(&self, slot: usize) -> ItemStack {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if slot < Self::INVENTORY_SIZE {
std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone())
} else {
ItemStack::EMPTY.clone()
}
}
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if slot < Self::INVENTORY_SIZE && !items[slot].is_empty() && amount > 0 {
items[slot].split(amount)
} else {
ItemStack::EMPTY.clone()
}
})
fn remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if slot < Self::INVENTORY_SIZE && !items[slot].is_empty() && amount > 0 {
items[slot].split(amount)
} else {
ItemStack::EMPTY.clone()
}
}
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
Box::pin(async move {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if slot < Self::INVENTORY_SIZE {
items[slot] = stack;
}
})
fn set_stack(&self, slot: usize, stack: ItemStack) {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if slot < Self::INVENTORY_SIZE {
items[slot] = stack;
}
}
fn on_open(&self) -> InventoryFuture<'_, ()> {
Box::pin(async move {
if let Some(tracker) = self
.tracker
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.as_ref()
{
tracker.open_container();
}
})
fn on_open(&self) {
if let Some(tracker) = self
.tracker
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.as_ref()
{
tracker.open_container();
}
}
fn on_close(&self) -> InventoryFuture<'_, ()> {
Box::pin(async move {
let tracker = self
.tracker
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.take();
if let Some(tracker) = tracker {
tracker.close_container();
}
})
fn on_close(&self) {
let tracker = self
.tracker
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.take();
if let Some(tracker) = tracker {
tracker.close_container();
}
}
fn mark_dirty(&self) {}
@ -190,14 +176,12 @@ impl Inventory for EnderChestInventory {
}
impl Clearable for EnderChestInventory {
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items.fill_with(|| ItemStack::EMPTY.clone());
})
fn clear(&self) {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items.fill_with(|| ItemStack::EMPTY.clone());
}
}
@ -206,57 +190,57 @@ mod tests {
use super::*;
use pumpkin_data::item::Item;
#[tokio::test]
async fn new_inventory() {
#[test]
fn new_inventory() {
let ec = EnderChestInventory::new();
assert_eq!(ec.size(), 27);
assert!(ec.is_empty().await);
assert!(ec.is_empty());
assert!(!ec.has_tracker());
}
#[tokio::test]
async fn set_and_get_stack() {
#[test]
fn set_and_get_stack() {
let ec = EnderChestInventory::new();
let stack = ItemStack::new(1, &Item::DIRT);
ec.set_stack(0, stack.clone()).await;
assert_eq!(ec.get_stack(0).await.item.id, Item::DIRT.id);
assert_eq!(ec.get_stack(0).await.item_count, 1);
assert!(!ec.is_empty().await);
ec.set_stack(0, stack.clone());
assert_eq!(ec.get_stack(0).item.id, Item::DIRT.id);
assert_eq!(ec.get_stack(0).item_count, 1);
assert!(!ec.is_empty());
// Out of bounds shouldn't panic
ec.set_stack(100, stack).await;
assert!(ec.get_stack(100).await.is_empty());
ec.set_stack(100, stack);
assert!(ec.get_stack(100).is_empty());
}
#[tokio::test]
async fn remove_stack() {
#[test]
fn remove_stack() {
let ec = EnderChestInventory::new();
let stack = ItemStack::new(5, &Item::DIAMOND);
ec.set_stack(10, stack).await;
ec.set_stack(10, stack);
let removed_specific = ec.remove_stack_specific(10, 2).await;
let removed_specific = ec.remove_stack_specific(10, 2);
assert_eq!(removed_specific.item_count, 2);
assert_eq!(ec.get_stack(10).await.item_count, 3);
assert_eq!(ec.get_stack(10).item_count, 3);
let removed_all = ec.remove_stack(10).await;
let removed_all = ec.remove_stack(10);
assert_eq!(removed_all.item_count, 3);
assert!(ec.get_stack(10).await.is_empty());
assert!(ec.is_empty().await);
assert!(ec.get_stack(10).is_empty());
assert!(ec.is_empty());
}
#[tokio::test]
async fn clear_inventory() {
#[test]
fn clear_inventory() {
let ec = EnderChestInventory::new();
ec.set_stack(0, ItemStack::new(1, &Item::STONE)).await;
ec.set_stack(26, ItemStack::new(1, &Item::OAK_LOG)).await;
assert!(!ec.is_empty().await);
ec.set_stack(0, ItemStack::new(1, &Item::STONE));
ec.set_stack(26, ItemStack::new(1, &Item::OAK_LOG));
assert!(!ec.is_empty());
ec.clear().await;
assert!(ec.is_empty().await);
ec.clear();
assert!(ec.is_empty());
}
#[tokio::test]
async fn tracker_lifecycle() {
#[test]
fn tracker_lifecycle() {
let ec = EnderChestInventory::new();
let tracker1 = Arc::new(ViewerCountTracker::new());
let tracker2 = Arc::new(ViewerCountTracker::new());
@ -266,7 +250,7 @@ mod tests {
assert!(ec.is_tracker(&tracker1));
assert!(!ec.is_tracker(&tracker2));
ec.on_open().await;
ec.on_open();
assert_eq!(tracker1.get_viewer_count(), 1);
// Setting a new tracker while one is open should close the old tracker
@ -274,10 +258,10 @@ mod tests {
assert_eq!(tracker1.get_viewer_count(), 0);
assert!(ec.is_tracker(&tracker2));
ec.on_open().await;
ec.on_open();
assert_eq!(tracker2.get_viewer_count(), 1);
ec.on_close().await;
ec.on_close();
assert_eq!(tracker2.get_viewer_count(), 0);
assert!(!ec.has_tracker());
}

View File

@ -15,10 +15,9 @@ use pumpkin_data::item::Item;
use pumpkin_data::item_stack::ItemStack;
use pumpkin_protocol::java::client::play::CSetPlayerInventory;
use pumpkin_util::Hand;
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture};
use pumpkin_world::inventory::{Clearable, Inventory};
use std::any::Any;
use std::collections::HashMap;
use std::pin::Pin;
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::{Arc, Mutex, RwLock};
@ -439,8 +438,8 @@ impl PlayerInventory {
}
/// Gives a stack to the player or drops it if inventory is full.
pub async fn offer_or_drop_stack(&self, stack: ItemStack, player: &dyn InventoryPlayer) {
self.offer(stack, true, player).await;
pub fn offer_or_drop_stack(&self, stack: ItemStack, player: &dyn InventoryPlayer) {
self.offer(stack, true, player);
}
/// Gives a stack to the player, optionally notifying the client.
@ -449,7 +448,7 @@ impl PlayerInventory {
/// - `stack` - The stack to give
/// - `notify_client` - Whether to send inventory update packets
/// - `player` - The player to give the stack to
pub async fn offer(&self, stack: ItemStack, notify_client: bool, player: &dyn InventoryPlayer) {
pub fn offer(&self, stack: ItemStack, notify_client: bool, player: &dyn InventoryPlayer) {
let mut stack = stack;
while !stack.is_empty() {
let mut room_for_stack = self.get_occupied_slot_with_room_for_stack(&stack);
@ -458,37 +457,33 @@ impl PlayerInventory {
}
if room_for_stack == -1 {
player.drop_item(stack, false).await;
player.drop_item(stack, false);
break;
}
let items_fit = stack.get_max_stack_size()
- self.get_stack(room_for_stack as usize).await.item_count;
let items_fit =
stack.get_max_stack_size() - self.get_stack(room_for_stack as usize).item_count;
if self.insert_stack(room_for_stack, &mut stack.split(items_fit)) && notify_client {
player
.enqueue_slot_set_packet(&CSetPlayerInventory::new(
i32::from(room_for_stack).into(),
&self.get_stack(room_for_stack as usize).await.into(),
))
.await;
player.enqueue_slot_set_packet(&CSetPlayerInventory::new(
i32::from(room_for_stack).into(),
&self.get_stack(room_for_stack as usize).into(),
));
}
}
}
}
impl Clearable for PlayerInventory {
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move {
let mut inv = self
.main_inventory
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
inv.fill_with(|| ItemStack::EMPTY.clone());
self.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clear();
})
fn clear(&self) {
let mut inv = self
.main_inventory
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
inv.fill_with(|| ItemStack::EMPTY.clone());
self.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clear();
}
}
@ -497,118 +492,108 @@ impl Inventory for PlayerInventory {
Self::MAIN_SIZE + self.equipment_slots.len()
}
fn is_empty(&self) -> InventoryFuture<'_, bool> {
Box::pin(async move {
fn is_empty(&self) -> bool {
let inv = self
.main_inventory
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if inv.iter().any(|s| !s.is_empty()) {
return false;
}
for slot in self.equipment_slots.values() {
let eq_item = self
.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.get(slot);
if !eq_item.is_empty() {
return false;
}
}
true
}
fn get_stack(&self, slot: usize) -> ItemStack {
if slot < Self::MAIN_SIZE {
let inv = self
.main_inventory
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if inv.iter().any(|s| !s.is_empty()) {
return false;
}
for slot in self.equipment_slots.values() {
let eq_item = self
.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.get(slot);
if !eq_item.is_empty() {
return false;
}
}
true
})
inv[slot].clone()
} else if let Some(slot) = self.equipment_slots.get(&slot) {
self.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.get(slot)
} else {
ItemStack::EMPTY.clone()
}
}
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move {
if slot < Self::MAIN_SIZE {
let inv = self
.main_inventory
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
inv[slot].clone()
} else if let Some(slot) = self.equipment_slots.get(&slot) {
self.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.get(slot)
fn remove_stack(&self, slot: usize) -> ItemStack {
if slot < Self::MAIN_SIZE {
let mut inv = self
.main_inventory
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
std::mem::replace(&mut inv[slot], ItemStack::EMPTY.clone())
} else if let Some(slot) = self.equipment_slots.get(&slot) {
self.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.put(slot, ItemStack::EMPTY.clone())
} else {
ItemStack::EMPTY.clone()
}
}
fn remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack {
if slot < Self::MAIN_SIZE {
let mut inv = self
.main_inventory
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if !inv[slot].is_empty() && amount > 0 {
inv[slot].split(amount)
} else {
ItemStack::EMPTY.clone()
}
})
}
} else if let Some(slot) = self.equipment_slots.get(&slot) {
let mut equipment = self
.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let mut stack = equipment.get(slot);
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move {
if slot < Self::MAIN_SIZE {
let mut inv = self
.main_inventory
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
std::mem::replace(&mut inv[slot], ItemStack::EMPTY.clone())
} else if let Some(slot) = self.equipment_slots.get(&slot) {
self.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.put(slot, ItemStack::EMPTY.clone())
if !stack.is_empty() && amount > 0 {
let split = stack.split(amount);
equipment.put(slot, stack);
split
} else {
ItemStack::EMPTY.clone()
}
})
} else {
ItemStack::EMPTY.clone()
}
}
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move {
if slot < Self::MAIN_SIZE {
let mut inv = self
.main_inventory
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if !inv[slot].is_empty() && amount > 0 {
inv[slot].split(amount)
} else {
ItemStack::EMPTY.clone()
}
} else if let Some(slot) = self.equipment_slots.get(&slot) {
let mut equipment = self
.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let mut stack = equipment.get(slot);
if !stack.is_empty() && amount > 0 {
let split = stack.split(amount);
equipment.put(slot, stack);
split
} else {
ItemStack::EMPTY.clone()
}
} else {
ItemStack::EMPTY.clone()
}
})
}
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
Box::pin(async move {
if slot < Self::MAIN_SIZE {
let mut inv = self
.main_inventory
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
inv[slot] = stack;
} else if let Some(slot) = self.equipment_slots.get(&slot) {
self.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.put(slot, stack);
} else {
warn!("Failed to get Equipment Slot at {slot}");
}
})
fn set_stack(&self, slot: usize, stack: ItemStack) {
if slot < Self::MAIN_SIZE {
let mut inv = self
.main_inventory
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
inv[slot] = stack;
} else if let Some(slot) = self.equipment_slots.get(&slot) {
self.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.put(slot, stack);
} else {
warn!("Failed to get Equipment Slot at {slot}");
}
}
fn mark_dirty(&self) {}

View File

@ -22,9 +22,7 @@ use super::player_inventory::PlayerInventory;
use crate::crafting::crafting_inventory::CraftingInventory;
use crate::crafting::crafting_screen_handler::CraftingScreenHandler;
use crate::crafting::recipes::{RecipeFinderScreenHandler, RecipeInputInventory};
use crate::screen_handler::{
InventoryPlayer, ItemStackFuture, ScreenHandler, ScreenHandlerBehaviour, ScreenHandlerFuture,
};
use crate::screen_handler::{InventoryPlayer, ScreenHandler, ScreenHandlerBehaviour};
use crate::slot::{ArmorSlot, NormalSlot, Slot};
use pumpkin_data::data_component_impl::{EquipmentSlot, EquipmentType, EquippableImpl};
use pumpkin_data::item_stack::ItemStack;
@ -77,7 +75,7 @@ impl PlayerScreenHandler {
/// - `player_inventory` - The player's inventory
/// - `window_type` - The window type (usually None for player inventory)
/// - `sync_id` - The synchronization ID
pub async fn new(
pub fn new(
player_inventory: &Arc<PlayerInventory>,
window_type: Option<WindowType>,
sync_id: u8,
@ -91,9 +89,7 @@ impl PlayerScreenHandler {
crafting_inventory: crafting_inventory.clone(),
};
player_screen_handler
.add_recipe_slots(crafting_inventory, provider)
.await;
player_screen_handler.add_recipe_slots(crafting_inventory, provider);
// Add armor slots (head, chest, legs, feet)
for i in 0..4 {
@ -134,13 +130,10 @@ impl ScreenHandler for PlayerScreenHandler {
&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;
//TODO: this.craftingResultInventory.clear();
self.drop_inventory(player, self.crafting_inventory.clone())
.await;
})
fn on_closed(&mut self, player: &dyn InventoryPlayer) {
self.default_on_closed(player);
//TODO: this.craftingResultInventory.clear();
self.drop_inventory(player, self.crafting_inventory.clone());
}
/// Performs quick move (shift-click) for the given slot.
@ -153,116 +146,100 @@ impl ScreenHandler for PlayerScreenHandler {
/// - Offhand items -> Offhand slot if empty
/// - Main inventory (9-35) -> Hotbar
/// - Hotbar (36-44) -> Main inventory
fn quick_move<'a>(
&'a mut self,
player: &'a dyn InventoryPlayer,
slot_index: i32,
) -> ItemStackFuture<'a> {
Box::pin(async move {
let slot = self.get_behaviour().slots[slot_index as usize].clone();
fn quick_move(&mut self, player: &dyn InventoryPlayer, slot_index: i32) -> ItemStack {
let slot = self.get_behaviour().slots[slot_index as usize].clone();
// TODO: Equippable component
// TODO: Equippable component
if slot.has_stack().await {
let mut slot_stack = slot.get_stack().await;
let stack_prev = slot_stack.clone();
if slot.has_stack() {
let mut slot_stack = slot.get_stack();
let stack_prev = slot_stack.clone();
let equipment_slot = slot_stack
.get_data_component::<EquippableImpl>()
.map_or(&EquipmentSlot::MAIN_HAND, |equippable| equippable.slot);
let equipment_slot = slot_stack
.get_data_component::<EquippableImpl>()
.map_or(&EquipmentSlot::MAIN_HAND, |equippable| equippable.slot);
// Quick move logic based on source slot
let success = if slot_index == 0 {
// From crafting result slot (0) -> Player Inventory (9-45, from end)
self.insert_item(&mut slot_stack, 9, 45, true).await
} else if (1..5).contains(&slot_index) {
// From craft ingredient slots (1-4) -> Player Inventory (9-45, from start)
self.insert_item(&mut slot_stack, 9, 45, false).await
} else if (5..9).contains(&slot_index) {
// From armour slots (5-8) -> Player Inventory (9-45, from start)
let result = self.insert_item(&mut slot_stack, 9, 45, false).await;
// Quick move logic based on source slot
let success = if slot_index == 0 {
// From crafting result slot (0) -> Player Inventory (9-45, from end)
self.insert_item(&mut slot_stack, 9, 45, true)
} else if (1..5).contains(&slot_index) {
// From craft ingredient slots (1-4) -> Player Inventory (9-45, from start)
self.insert_item(&mut slot_stack, 9, 45, false)
} else if (5..9).contains(&slot_index) {
// From armour slots (5-8) -> Player Inventory (9-45, from start)
let result = self.insert_item(&mut slot_stack, 9, 45, false);
if result {
player
.enqueue_equipment_change(equipment_slot, ItemStack::EMPTY)
.await;
}
result
} else if equipment_slot.slot_type() == EquipmentType::HumanoidArmor
&& self
.get_slot((8 - equipment_slot.get_entity_slot_id()) as usize)
.get_cloned_stack()
.await
.is_empty()
{
// Into empty armour slots (5-8)
let index = 8 - equipment_slot.get_entity_slot_id();
let result = self
.insert_item(&mut slot_stack, index, index + 1, false)
.await;
if result {
player
.enqueue_equipment_change(equipment_slot, &stack_prev)
.await;
}
result
} else if matches!(equipment_slot, EquipmentSlot::OffHand(_))
&& slot_index != 45
&& self.get_slot(45).get_cloned_stack().await.is_empty()
{
// Into empty offhand slot (45)
let index = 45;
self.insert_item(&mut slot_stack, index, index + 1, false)
.await
} else if (9..36).contains(&slot_index) {
// From main inventory (9-35) -> Hotbar (36-44)
self.insert_item(&mut slot_stack, 36, 45, false).await
} else if (36..45).contains(&slot_index) {
// From hotbar (36-44) -> Main inventory (9-35)
self.insert_item(&mut slot_stack, 9, 36, false).await
} else {
// Fallback to moving into the player inventory area
self.insert_item(&mut slot_stack, 9, 45, false).await
};
if !success {
return ItemStack::EMPTY.clone();
if result {
player.enqueue_equipment_change(equipment_slot, ItemStack::EMPTY);
}
result
} else if equipment_slot.slot_type() == EquipmentType::HumanoidArmor
&& self
.get_slot((8 - equipment_slot.get_entity_slot_id()) as usize)
.get_cloned_stack()
.is_empty()
{
// Into empty armour slots (5-8)
let index = 8 - equipment_slot.get_entity_slot_id();
let result = self.insert_item(&mut slot_stack, index, index + 1, false);
let stack = slot_stack.clone();
if stack.is_empty() {
slot.set_stack_prev(ItemStack::EMPTY.clone(), stack_prev.clone())
.await;
} else {
slot.set_stack(stack.clone()).await;
if result {
player.enqueue_equipment_change(equipment_slot, &stack_prev);
}
result
} else if matches!(equipment_slot, EquipmentSlot::OffHand(_))
&& slot_index != 45
&& self.get_slot(45).get_cloned_stack().is_empty()
{
// Into empty offhand slot (45)
let index = 45;
self.insert_item(&mut slot_stack, index, index + 1, false)
} else if (9..36).contains(&slot_index) {
// From main inventory (9-35) -> Hotbar (36-44)
self.insert_item(&mut slot_stack, 36, 45, false)
} else if (36..45).contains(&slot_index) {
// From hotbar (36-44) -> Main inventory (9-35)
self.insert_item(&mut slot_stack, 9, 36, false)
} else {
// Fallback to moving into the player inventory area
self.insert_item(&mut slot_stack, 9, 45, false)
};
if stack.item_count == stack_prev.item_count {
return ItemStack::EMPTY.clone();
}
let mut taken_stack = stack_prev.clone();
taken_stack.set_count(stack_prev.item_count - stack.item_count);
slot.on_take_item(player, &taken_stack).await;
if slot_index == 0 {
// From crafting result slot (0)
// Notify the result slot to refill
slot.on_quick_move_crafted(stack.clone(), stack_prev.clone())
.await;
// For crafting result slot, drop any remaining items
if !stack.is_empty() {
player.drop_item(stack, false).await;
}
}
return stack_prev;
if !success {
return ItemStack::EMPTY.clone();
}
// Nothing changed
ItemStack::EMPTY.clone()
})
let stack = slot_stack.clone();
if stack.is_empty() {
slot.set_stack_prev(ItemStack::EMPTY.clone(), stack_prev.clone());
} else {
slot.set_stack(stack.clone());
}
if stack.item_count == stack_prev.item_count {
return ItemStack::EMPTY.clone();
}
let mut taken_stack = stack_prev.clone();
taken_stack.set_count(stack_prev.item_count - stack.item_count);
slot.on_take_item(player, &taken_stack);
if slot_index == 0 {
// From crafting result slot (0)
// Notify the result slot to refill
slot.on_quick_move_crafted(stack.clone(), stack_prev.clone());
// For crafting result slot, drop any remaining items
if !stack.is_empty() {
player.drop_item(stack, false);
}
}
return stack_prev;
}
// Nothing changed
ItemStack::EMPTY.clone()
}
}

File diff suppressed because it is too large Load Diff

View File

@ -18,12 +18,9 @@
//! - Marking the slot as changed (dirty)
//! - Callbacks for slot interaction events
use std::{
pin::Pin,
sync::{
Arc,
atomic::{AtomicU8, Ordering},
},
use std::sync::{
Arc,
atomic::{AtomicU8, Ordering},
};
use crate::screen_handler::InventoryPlayer;
@ -33,9 +30,6 @@ use pumpkin_data::item::Item;
use pumpkin_data::item_stack::ItemStack;
use pumpkin_world::inventory::Inventory;
/// Type alias for async slot operations.
pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
/// A slot in an inventory.
///
/// The slot trait defines how individual inventory positions behave.
@ -62,140 +56,98 @@ pub trait Slot: Send + Sync {
/// any locks to avoid deadlocks.
///
/// Also see: [`ScreenHandler::quick_move`](crate::screen_handler::ScreenHandler::quick_move)
fn on_quick_move_crafted(
&self,
_stack: ItemStack,
_stack_prev: ItemStack,
) -> BoxFuture<'_, ()> {
Box::pin(async {}) // Default implementation
}
fn on_quick_move_crafted(&self, _stack: ItemStack, _stack_prev: ItemStack) {}
/// Callback for when an item is taken from this slot.
///
/// Also see: [`safe_take`]
fn on_take_item<'a>(
&'a self,
_player: &'a dyn InventoryPlayer,
_stack: &'a ItemStack,
) -> BoxFuture<'a, ()> {
// Default implementation logic:
Box::pin(async move {
self.mark_dirty().await;
})
fn on_take_item(&self, _player: &dyn InventoryPlayer, _stack: &ItemStack) {
self.mark_dirty();
}
/// Plugin callback for slot clicks.
///
/// Called when a player clicks on this slot. Can be used by
/// plugins to intercept or modify click behavior.
fn on_click(&self, _player: &dyn InventoryPlayer) -> BoxFuture<'_, ()> {
Box::pin(async {}) // Default implementation
}
fn on_click(&self, _player: &dyn InventoryPlayer) {}
/// Checks if the given stack can be inserted into this slot.
fn can_insert<'a>(&'a self, _stack: &'a ItemStack) -> BoxFuture<'a, bool> {
// Default implementation logic:
Box::pin(async move { true })
fn can_insert(&self, _stack: &ItemStack) -> bool {
true
}
/// Gets the stack in this slot.
fn get_stack(&self) -> BoxFuture<'_, ItemStack> {
// Default implementation logic:
Box::pin(async move { self.get_inventory().get_stack(self.get_index()).await })
fn get_stack(&self) -> ItemStack {
self.get_inventory().get_stack(self.get_index())
}
/// Gets a copy of the stack in this slot.
fn get_cloned_stack(&self) -> BoxFuture<'_, ItemStack> {
// Default implementation logic:
Box::pin(async move { self.get_stack().await })
fn get_cloned_stack(&self) -> ItemStack {
self.get_stack()
}
/// Checks if this slot has a non-empty stack.
fn has_stack(&self) -> BoxFuture<'_, bool> {
// Default implementation logic:
Box::pin(async move { !self.get_stack().await.is_empty() })
fn has_stack(&self) -> bool {
!self.get_stack().is_empty()
}
/// Sets the stack in this slot.
///
/// # Note
/// Make sure to drop any locks to the slot stack before calling this.
fn set_stack(&self, stack: ItemStack) -> BoxFuture<'_, ()> {
// Default implementation logic:
Box::pin(async move {
self.set_stack_no_callbacks(stack).await;
})
fn set_stack(&self, stack: ItemStack) {
self.set_stack_no_callbacks(stack);
}
/// Sets the stack with previous stack reference.
///
/// Some slots (like armor) need to know the previous stack for callbacks.
fn set_stack_prev(&self, stack: ItemStack, _previous_stack: ItemStack) -> BoxFuture<'_, ()> {
// Default implementation logic:
Box::pin(async move {
self.set_stack_no_callbacks(stack).await;
})
fn set_stack_prev(&self, stack: ItemStack, _previous_stack: ItemStack) {
self.set_stack_no_callbacks(stack);
}
/// Sets the stack without calling callbacks.
fn set_stack_no_callbacks(&self, stack: ItemStack) -> BoxFuture<'_, ()> {
// Default implementation logic:
Box::pin(async move {
let inv = self.get_inventory();
inv.set_stack(self.get_index(), stack).await;
self.mark_dirty().await;
})
fn set_stack_no_callbacks(&self, stack: ItemStack) {
let inv = self.get_inventory();
inv.set_stack(self.get_index(), stack);
self.mark_dirty();
}
/// Marks this slot as changed.
///
/// Must be implemented by concrete types.
fn mark_dirty(&self) -> BoxFuture<'_, ()>;
fn mark_dirty(&self);
/// Gets the maximum item count for this slot.
fn get_max_item_count(&self) -> BoxFuture<'_, u8> {
// Default implementation logic:
Box::pin(async move { self.get_inventory().get_max_count_per_stack() })
fn get_max_item_count(&self) -> u8 {
self.get_inventory().get_max_count_per_stack()
}
/// Gets the maximum item count for the given stack in this slot.
fn get_max_item_count_for_stack<'a>(&'a self, stack: &'a ItemStack) -> BoxFuture<'a, u8> {
// Default implementation logic:
Box::pin(async move {
self.get_max_item_count()
.await
.min(stack.get_max_stack_size())
})
fn get_max_item_count_for_stack(&self, stack: &ItemStack) -> u8 {
self.get_max_item_count().min(stack.get_max_stack_size())
}
/// Removes a specific amount of items from this slot.
///
/// Mojang name: `remove`
fn take_stack(&self, amount: u8) -> BoxFuture<'_, ItemStack> {
// Default implementation logic:
Box::pin(async move {
let inv = self.get_inventory();
inv.remove_stack_specific(self.get_index(), amount).await
})
fn take_stack(&self, amount: u8) -> ItemStack {
let inv = self.get_inventory();
inv.remove_stack_specific(self.get_index(), amount)
}
/// Checks if the player can take items from this slot.
///
/// Mojang name: `mayPickup`
fn can_take_items(&self, _player: &dyn InventoryPlayer) -> BoxFuture<'_, bool> {
// Default implementation logic:
Box::pin(async move { true })
fn can_take_items(&self, _player: &dyn InventoryPlayer) -> bool {
true
}
/// Checks if this slot can be modified by the player.
///
/// Mojang name: `allowModification`
fn allow_modification<'a>(&'a self, player: &'a dyn InventoryPlayer) -> BoxFuture<'a, bool> {
// Default implementation logic:
Box::pin(async move {
self.can_insert(&self.get_cloned_stack().await).await
&& self.can_take_items(player).await
})
fn allow_modification(&self, player: &dyn InventoryPlayer) -> bool {
self.can_insert(&self.get_cloned_stack()) && self.can_take_items(player)
}
/// Tries to take a stack in the given range.
@ -204,37 +156,31 @@ pub trait Slot: Send + Sync {
/// For result slots, cannot take partial stacks.
///
/// Mojang name: `tryRemove`
fn try_take_stack_range<'a>(
&'a self,
fn try_take_stack_range(
&self,
min: u8,
max: u8,
player: &'a dyn InventoryPlayer,
) -> BoxFuture<'a, Option<ItemStack>> {
// Default implementation logic:
Box::pin(async move {
if !self.can_take_items(player).await {
return None;
}
if !self.allow_modification(player).await
&& self.get_cloned_stack().await.item_count > max
{
// If the slot is not allowed to be modified, we cannot take a partial stack from it.
return None;
}
let min = min.min(max);
let stack = self.take_stack(min).await;
player: &dyn InventoryPlayer,
) -> Option<ItemStack> {
if !self.can_take_items(player) {
return None;
}
if !self.allow_modification(player) && self.get_cloned_stack().item_count > max {
// If the slot is not allowed to be modified, we cannot take a partial stack from it.
return None;
}
let min = min.min(max);
let stack = self.take_stack(min);
if stack.is_empty() {
None
} else {
if self.get_cloned_stack().await.is_empty() {
self.set_stack_prev(ItemStack::EMPTY.clone(), stack.clone())
.await;
}
Some(stack)
if stack.is_empty() {
None
} else {
if self.get_cloned_stack().is_empty() {
self.set_stack_prev(ItemStack::EMPTY.clone(), stack.clone());
}
})
Some(stack)
}
}
/// Safely tries to take a stack of items from the slot.
@ -242,62 +188,49 @@ pub trait Slot: Send + Sync {
/// Returns an empty stack if can't take. Triggers callbacks.
///
/// Mojang name: `safeTake`
fn safe_take<'a>(
&'a self,
min: u8,
max: u8,
player: &'a dyn InventoryPlayer,
) -> BoxFuture<'a, ItemStack> {
Box::pin(async move {
let stack = self.try_take_stack_range(min, max, player).await;
fn safe_take(&self, min: u8, max: u8, player: &dyn InventoryPlayer) -> ItemStack {
let stack = self.try_take_stack_range(min, max, player);
if let Some(stack) = &stack {
self.on_take_item(player, stack).await;
}
if let Some(stack) = &stack {
self.on_take_item(player, stack);
}
stack.unwrap_or_else(|| ItemStack::EMPTY.clone())
})
stack.unwrap_or_else(|| ItemStack::EMPTY.clone())
}
/// Inserts a stack into this slot.
///
/// Returns any leftover items that couldn't fit.
fn insert_stack(&self, stack: ItemStack) -> BoxFuture<'_, ItemStack> {
// Default implementation logic:
Box::pin(async move {
let stack_item_count = stack.item_count;
self.insert_stack_count(stack, stack_item_count).await
})
fn insert_stack(&self, stack: ItemStack) -> ItemStack {
let stack_item_count = stack.item_count;
self.insert_stack_count(stack, stack_item_count)
}
/// Inserts a specific count from a stack.
///
/// Returns any leftover items.
fn insert_stack_count(&self, mut stack: ItemStack, count: u8) -> BoxFuture<'_, ItemStack> {
// Default implementation logic:
Box::pin(async move {
if !stack.is_empty() && self.can_insert(&stack).await {
let mut stack_self = self.get_stack().await;
let min_count = count
.min(stack.item_count)
.min(self.get_max_item_count_for_stack(&stack).await - stack_self.item_count);
fn insert_stack_count(&self, mut stack: ItemStack, count: u8) -> ItemStack {
if !stack.is_empty() && self.can_insert(&stack) {
let mut stack_self = self.get_stack();
let min_count = count
.min(stack.item_count)
.min(self.get_max_item_count_for_stack(&stack) - stack_self.item_count);
if min_count != 0 {
if stack_self.is_empty() {
self.set_stack(stack.split(min_count)).await;
} else if stack.are_items_and_components_equal(&stack_self) {
stack.decrement(min_count);
stack_self.increment(min_count);
self.set_stack(stack_self).await;
}
if min_count != 0 {
if stack_self.is_empty() {
self.set_stack(stack.split(min_count));
} else if stack.are_items_and_components_equal(&stack_self) {
stack.decrement(min_count);
stack_self.increment(min_count);
self.set_stack(stack_self);
}
}
if stack.is_empty() {
ItemStack::EMPTY.clone()
} else {
stack
}
})
}
if stack.is_empty() {
ItemStack::EMPTY.clone()
} else {
stack
}
}
}
@ -342,10 +275,8 @@ impl Slot for NormalSlot {
self.id.store(id as u8, Ordering::Relaxed);
}
fn mark_dirty(&self) -> BoxFuture<'_, ()> {
Box::pin(async move {
self.inventory.mark_dirty();
})
fn mark_dirty(&self) {
self.inventory.mark_dirty();
}
}
@ -399,43 +330,33 @@ impl Slot for ArmorSlot {
}
/// Restricts inserts to appropriate armor types.
fn can_insert<'a>(&'a self, stack: &'a ItemStack) -> BoxFuture<'a, bool> {
Box::pin(async move {
match self.equipment_slot {
EquipmentSlot::Head(_) => {
stack.is_helmet() || stack.is_skull() || stack.item == &Item::CARVED_PUMPKIN
}
EquipmentSlot::Chest(_) => stack.is_chestplate() || stack.item == &Item::ELYTRA,
EquipmentSlot::Legs(_) => stack.is_leggings(),
EquipmentSlot::Feet(_) => stack.is_boots(),
_ => true,
fn can_insert(&self, stack: &ItemStack) -> bool {
match self.equipment_slot {
EquipmentSlot::Head(_) => {
stack.is_helmet() || stack.is_skull() || stack.item == &Item::CARVED_PUMPKIN
}
})
EquipmentSlot::Chest(_) => stack.is_chestplate() || stack.item == &Item::ELYTRA,
EquipmentSlot::Legs(_) => stack.is_leggings(),
EquipmentSlot::Feet(_) => stack.is_boots(),
_ => true,
}
}
fn set_stack_prev(&self, stack: ItemStack, _previous_stack: ItemStack) -> BoxFuture<'_, ()> {
Box::pin(async move {
//TODO: this.entity.onEquipStack(this.equipmentSlot, previousStack, stack);
self.set_stack_no_callbacks(stack).await;
})
fn set_stack_prev(&self, stack: ItemStack, _previous_stack: ItemStack) {
self.set_stack_no_callbacks(stack);
}
fn mark_dirty(&self) -> BoxFuture<'_, ()> {
Box::pin(async move {
self.inventory.mark_dirty();
})
fn mark_dirty(&self) {
self.inventory.mark_dirty();
}
/// Armor slots can only hold one item.
fn get_max_item_count(&self) -> BoxFuture<'_, u8> {
Box::pin(async move { 1 })
fn get_max_item_count(&self) -> u8 {
1
}
/// TODO: Check for curse of binding enchantment.
fn can_take_items(&self, _player: &dyn InventoryPlayer) -> BoxFuture<'_, bool> {
Box::pin(async move {
// TODO: Check enchantments
true
})
fn can_take_items(&self, _player: &dyn InventoryPlayer) -> bool {
true
}
}

View File

@ -2,9 +2,7 @@ use std::any::Any;
use std::sync::Arc;
use crate::player::player_inventory::PlayerInventory;
use crate::screen_handler::{
InventoryPlayer, ScreenHandler, ScreenHandlerBehaviour, ScreenHandlerFuture,
};
use crate::screen_handler::{InventoryPlayer, ScreenHandler, ScreenHandlerBehaviour};
use crate::slot::NormalSlot;
use pumpkin_data::item_stack::ItemStack;
@ -66,48 +64,39 @@ impl ScreenHandler for SmithingTableScreenHandler {
self
}
fn on_slot_click<'a>(
&'a mut self,
fn on_slot_click(
&mut self,
slot_index: i32,
button: i32,
action_type: SlotActionType,
player: &'a dyn InventoryPlayer,
) -> ScreenHandlerFuture<'a, ()> {
Box::pin(async move {
self.internal_on_slot_click(slot_index, button, action_type, player)
.await;
})
player: &dyn InventoryPlayer,
) {
self.internal_on_slot_click(slot_index, button, action_type, player);
}
fn quick_move<'a>(
&'a mut self,
_player: &'a dyn InventoryPlayer,
slot_index: i32,
) -> ScreenHandlerFuture<'a, ItemStack> {
Box::pin(async move {
let mut stack = ItemStack::EMPTY.clone();
let slot = self.get_behaviour().slots.get(slot_index as usize).cloned();
fn quick_move(&mut self, _player: &dyn InventoryPlayer, slot_index: i32) -> ItemStack {
let mut stack = ItemStack::EMPTY.clone();
let slot = self.get_behaviour().slots.get(slot_index as usize).cloned();
if let Some(slot) = slot {
let mut slot_stack = slot.get_cloned_stack().await;
if !slot_stack.is_empty() {
stack = slot_stack.clone();
if slot_index < 4 {
if !self.insert_item(&mut slot_stack, 4, 40, true).await {
return ItemStack::EMPTY.clone();
}
} else if !self.insert_item(&mut slot_stack, 0, 3, false).await {
if let Some(slot) = slot {
let mut slot_stack = slot.get_cloned_stack();
if !slot_stack.is_empty() {
stack = slot_stack.clone();
if slot_index < 4 {
if !self.insert_item(&mut slot_stack, 4, 40, true) {
return ItemStack::EMPTY.clone();
}
} else if !self.insert_item(&mut slot_stack, 0, 3, false) {
return ItemStack::EMPTY.clone();
}
if slot_stack.is_empty() {
slot.set_stack(ItemStack::EMPTY.clone()).await;
} else {
slot.set_stack(slot_stack).await;
}
if slot_stack.is_empty() {
slot.set_stack(ItemStack::EMPTY.clone());
} else {
slot.set_stack(slot_stack);
}
}
stack
})
}
stack
}
}

View File

@ -3,10 +3,8 @@ use std::sync::Arc;
use std::sync::atomic::{AtomicU8, Ordering};
use crate::player::player_inventory::PlayerInventory;
use crate::screen_handler::{
InventoryPlayer, ScreenHandler, ScreenHandlerBehaviour, ScreenHandlerFuture,
};
use crate::slot::{BoxFuture, NormalSlot, Slot};
use crate::screen_handler::{InventoryPlayer, ScreenHandler, ScreenHandlerBehaviour};
use crate::slot::{NormalSlot, Slot};
use pumpkin_data::item::Item;
use pumpkin_data::item_stack::ItemStack;
@ -54,13 +52,11 @@ impl StonecutterScreenHandler {
handler
}
async fn update_output(&self) {
let input_lock = self.input_inventory.get_stack(0).await;
fn update_output(&self) {
let input_lock = self.input_inventory.get_stack(0);
if input_lock.is_empty() {
self.output_inventory
.set_stack(0, ItemStack::EMPTY.clone())
.await;
self.output_inventory.set_stack(0, ItemStack::EMPTY.clone());
self.selected_recipe.store(u8::MAX, Ordering::Relaxed);
return;
}
@ -72,11 +68,9 @@ impl StonecutterScreenHandler {
let recipe = available_recipes[recipe_index as usize];
let item = Item::from_registry_key(recipe.result.id).unwrap_or(&Item::AIR);
let result = ItemStack::new(recipe.result.count, item);
self.output_inventory.set_stack(0, result).await;
self.output_inventory.set_stack(0, result);
} else {
self.output_inventory
.set_stack(0, ItemStack::EMPTY.clone())
.await;
self.output_inventory.set_stack(0, ItemStack::EMPTY.clone());
}
}
@ -106,59 +100,49 @@ impl ScreenHandler for StonecutterScreenHandler {
self
}
fn on_slot_click<'a>(
&'a mut self,
fn on_slot_click(
&mut self,
slot_index: i32,
button: i32,
action_type: SlotActionType,
player: &'a dyn InventoryPlayer,
) -> ScreenHandlerFuture<'a, ()> {
Box::pin(async move {
self.internal_on_slot_click(slot_index, button, action_type, player)
.await;
if slot_index == 0 {
self.update_output().await;
}
})
player: &dyn InventoryPlayer,
) {
self.internal_on_slot_click(slot_index, button, action_type, player);
if slot_index == 0 {
self.update_output();
}
}
fn quick_move<'a>(
&'a mut self,
_player: &'a dyn InventoryPlayer,
slot_index: i32,
) -> ScreenHandlerFuture<'a, ItemStack> {
Box::pin(async move {
let mut stack = ItemStack::EMPTY.clone();
let slot = self.get_behaviour().slots.get(slot_index as usize).cloned();
fn quick_move(&mut self, _player: &dyn InventoryPlayer, slot_index: i32) -> ItemStack {
let mut stack = ItemStack::EMPTY.clone();
let slot = self.get_behaviour().slots.get(slot_index as usize).cloned();
if let Some(slot) = slot {
let mut slot_stack = slot.get_cloned_stack().await;
if !slot_stack.is_empty() {
stack = slot_stack.clone();
if slot_index < 2 {
// From Stonecutter to Player
if !self.insert_item(&mut slot_stack, 2, 38, true).await {
return ItemStack::EMPTY.clone();
}
slot.on_quick_move_crafted(slot_stack.clone(), stack.clone())
.await;
} else {
// From Player to Stonecutter
// Try input slot (0)
if !self.insert_item(&mut slot_stack, 0, 1, false).await {
return ItemStack::EMPTY.clone();
}
if let Some(slot) = slot {
let mut slot_stack = slot.get_cloned_stack();
if !slot_stack.is_empty() {
stack = slot_stack.clone();
if slot_index < 2 {
// From Stonecutter to Player
if !self.insert_item(&mut slot_stack, 2, 38, true) {
return ItemStack::EMPTY.clone();
}
if slot_stack.is_empty() {
slot.set_stack(ItemStack::EMPTY.clone()).await;
} else {
slot.set_stack(slot_stack).await;
slot.on_quick_move_crafted(slot_stack.clone(), stack.clone());
} else {
// From Player to Stonecutter
// Try input slot (0)
if !self.insert_item(&mut slot_stack, 0, 1, false) {
return ItemStack::EMPTY.clone();
}
}
if slot_stack.is_empty() {
slot.set_stack(ItemStack::EMPTY.clone());
} else {
slot.set_stack(slot_stack);
}
}
stack
})
}
stack
}
}
@ -197,55 +181,41 @@ impl Slot for StonecutterOutputSlot {
self.id.store(id as u8, Ordering::Relaxed);
}
fn on_take_item<'a>(
&'a self,
player: &'a dyn InventoryPlayer,
stack: &'a ItemStack,
) -> BoxFuture<'a, ()> {
Box::pin(async move {
player
.increment_stat(
StatisticCategory::Crafted,
stack.item.id as i32,
stack.item_count as i32,
)
.await;
self.input_inventory.remove_stack_specific(0, 1).await;
self.mark_dirty().await;
})
fn on_take_item(&self, player: &dyn InventoryPlayer, stack: &ItemStack) {
player.increment_stat(
StatisticCategory::Crafted,
stack.item.id as i32,
stack.item_count as i32,
);
self.input_inventory.remove_stack_specific(0, 1);
self.mark_dirty();
}
fn can_insert(&self, _stack: &ItemStack) -> BoxFuture<'_, bool> {
Box::pin(async move { false })
fn can_insert(&self, _stack: &ItemStack) -> bool {
false
}
fn get_stack(&self) -> BoxFuture<'_, ItemStack> {
Box::pin(async move { self.inventory.get_stack(self.index).await })
fn get_stack(&self) -> ItemStack {
self.inventory.get_stack(self.index)
}
fn get_cloned_stack(&self) -> BoxFuture<'_, ItemStack> {
Box::pin(async move { self.inventory.get_stack(self.index).await })
fn get_cloned_stack(&self) -> ItemStack {
self.inventory.get_stack(self.index)
}
fn has_stack(&self) -> BoxFuture<'_, bool> {
Box::pin(async move { !self.inventory.get_stack(self.index).await.is_empty() })
fn has_stack(&self) -> bool {
!self.inventory.get_stack(self.index).is_empty()
}
fn set_stack(&self, stack: ItemStack) -> BoxFuture<'_, ()> {
Box::pin(async move {
self.inventory.set_stack(self.index, stack).await;
})
fn set_stack(&self, stack: ItemStack) {
self.inventory.set_stack(self.index, stack);
}
fn set_stack_prev(&self, _stack: ItemStack, _previous_stack: ItemStack) -> BoxFuture<'_, ()> {
Box::pin(async move {
// Do nothing
})
fn set_stack_prev(&self, _stack: ItemStack, _previous_stack: ItemStack) {
// Do nothing
}
fn mark_dirty(&self) -> BoxFuture<'_, ()> {
Box::pin(async move {
self.inventory.mark_dirty();
})
fn mark_dirty(&self) {
self.inventory.mark_dirty();
}
}

View File

@ -18,7 +18,7 @@
//! and server stay in sync. If the client detects a desync, it can request a full
//! resynchronization.
use std::sync::Arc;
use std::sync::{Arc, Mutex};
use pumpkin_data::item_stack::ItemStack;
use pumpkin_protocol::{
@ -30,7 +30,6 @@ use pumpkin_protocol::{
CSetContainerContent, CSetContainerProperty, CSetContainerSlot, CSetCursorItem,
},
};
use tokio::sync::Mutex;
use crate::screen_handler::{InventoryPlayer, ScreenHandlerBehaviour};
@ -67,8 +66,11 @@ impl SyncHandler {
/// Stores the player to synchronize with.
///
/// Must be called before any sync operations.
pub async fn store_player(&self, player: Arc<dyn InventoryPlayer>) {
self.player.lock().await.replace(player);
pub fn store_player(&self, player: Arc<dyn InventoryPlayer>) {
self.player
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.replace(player);
}
/// Sends a full container content update.
@ -82,39 +84,40 @@ impl SyncHandler {
/// - `cursor_stack` - The item held by the cursor
/// - `properties` - Container property values
/// - `next_revision` - The new revision number
pub async fn update_state(
pub fn update_state(
&self,
screen_handler: &ScreenHandlerBehaviour,
stacks: &[ItemStack],
cursor_stack: &ItemStack,
properties: Vec<i32>,
properties: &[i32],
next_revision: u32,
) {
if let Some(player) = self.player.lock().await.as_ref() {
player
.enqueue_inventory_packet(
&CSetContainerContent::new(
VarInt(screen_handler.sync_id.into()),
VarInt(next_revision as i32),
stacks
.iter()
.map(|stack| ItemStackSerializer::from(stack.clone()))
.collect::<Vec<_>>()
.as_slice(),
&ItemStackSerializer::from(cursor_stack.clone()),
),
screen_handler.window_type,
)
.await;
if let Some(player) = self
.player
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.as_ref()
{
player.enqueue_inventory_packet(
&CSetContainerContent::new(
VarInt(screen_handler.sync_id.into()),
VarInt(next_revision as i32),
stacks
.iter()
.map(|stack| ItemStackSerializer::from(stack.clone()))
.collect::<Vec<_>>()
.as_slice(),
&ItemStackSerializer::from(cursor_stack.clone()),
),
screen_handler.window_type,
);
for (i, property) in properties.iter().enumerate() {
player
.enqueue_property_packet(&CSetContainerProperty::new(
VarInt(screen_handler.sync_id.into()),
i as i16,
*property as i16,
))
.await;
player.enqueue_property_packet(&CSetContainerProperty::new(
VarInt(screen_handler.sync_id.into()),
i as i16,
*property as i16,
));
}
}
}
@ -128,26 +131,29 @@ impl SyncHandler {
/// - `slot` - The slot index that changed
/// - `stack` - The new stack in that slot
/// - `next_revision` - The new revision number
pub async fn update_slot(
pub fn update_slot(
&self,
screen_handler: &ScreenHandlerBehaviour,
slot: usize,
stack: &ItemStack,
next_revision: u32,
) {
if let Some(player) = self.player.lock().await.as_ref() {
player
.enqueue_slot_packet(
&CSetContainerSlot::new(
screen_handler.sync_id as i8,
next_revision as i32,
slot as i16,
&ItemStackSerializer::from(stack.clone()),
),
screen_handler.window_type,
screen_handler.slots.len(),
)
.await;
if let Some(player) = self
.player
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.as_ref()
{
player.enqueue_slot_packet(
&CSetContainerSlot::new(
screen_handler.sync_id as i8,
next_revision as i32,
slot as i16,
&ItemStackSerializer::from(stack.clone()),
),
screen_handler.window_type,
screen_handler.slots.len(),
);
}
}
@ -158,17 +164,16 @@ impl SyncHandler {
/// # Arguments
/// - `screen_handler` - The screen handler
/// - `stack` - The new cursor item
pub async fn update_cursor_stack(
&self,
_screen_handler: &ScreenHandlerBehaviour,
stack: &ItemStack,
) {
if let Some(player) = self.player.lock().await.as_ref() {
player
.enqueue_cursor_packet(&CSetCursorItem::new(&ItemStackSerializer::from(
stack.clone(),
)))
.await;
pub fn update_cursor_stack(&self, _screen_handler: &ScreenHandlerBehaviour, stack: &ItemStack) {
if let Some(player) = self
.player
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.as_ref()
{
player.enqueue_cursor_packet(&CSetCursorItem::new(&ItemStackSerializer::from(
stack.clone(),
)));
}
}
@ -180,20 +185,23 @@ impl SyncHandler {
/// - `screen_handler` - The screen handler
/// - `property` - The property index
/// - `value` - The new property value
pub async fn update_property(
pub fn update_property(
&self,
screen_handler: &ScreenHandlerBehaviour,
property: i32,
value: i32,
) {
if let Some(player) = self.player.lock().await.as_ref() {
player
.enqueue_property_packet(&CSetContainerProperty::new(
VarInt(screen_handler.sync_id.into()),
property as i16,
value as i16,
))
.await;
if let Some(player) = self
.player
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.as_ref()
{
player.enqueue_property_packet(&CSetContainerProperty::new(
VarInt(screen_handler.sync_id.into()),
property as i16,
value as i16,
));
}
}
}

View File

@ -2,98 +2,73 @@ use pumpkin_data::item::Item;
use pumpkin_data::item_stack::ItemStack;
use pumpkin_nbt::{compound::NbtCompound, tag::NbtTag};
use std::any::Any;
use std::pin::Pin;
use std::{
hash::{Hash, Hasher},
sync::Arc,
};
pub type InventoryFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
pub trait Inventory: Send + Sync + Clearable {
fn size(&self) -> usize;
fn is_empty(&self) -> InventoryFuture<'_, bool>;
fn is_empty(&self) -> bool;
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack>;
fn get_stack(&self, slot: usize) -> ItemStack;
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack>;
fn remove_stack(&self, slot: usize) -> ItemStack;
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack>;
fn remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack;
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()>;
fn set_stack(&self, slot: usize, stack: ItemStack);
fn on_open(&self) -> InventoryFuture<'_, ()> {
Box::pin(async {})
}
fn on_close(&self) -> InventoryFuture<'_, ()> {
Box::pin(async {})
}
fn on_open(&self) {}
fn on_close(&self) {}
fn count<'a>(&'a self, item: &'a Item) -> InventoryFuture<'a, u8> {
Box::pin(async move {
let mut count = 0;
fn count(&self, item: &Item) -> u8 {
let mut count = 0;
for i in 0..self.size() {
let stack = self.get_stack(i).await;
if stack.get_item().id == item.id {
count += stack.item_count;
}
for i in 0..self.size() {
let stack = self.get_stack(i);
if stack.get_item().id == item.id {
count += stack.item_count;
}
}
count
})
count
}
fn contains_any_predicate<'a>(
&'a self,
predicate: &'a (dyn Fn(&ItemStack) -> bool + Sync),
) -> InventoryFuture<'a, bool> {
Box::pin(async move {
for i in 0..self.size() {
let stack = self.get_stack(i).await;
if predicate(&stack) {
return true;
}
fn contains_any_predicate(&self, predicate: &(dyn Fn(&ItemStack) -> bool + Sync)) -> bool {
for i in 0..self.size() {
let stack = self.get_stack(i);
if predicate(&stack) {
return true;
}
}
false
})
false
}
fn contains_any<'a>(&'a self, items: &'a [Item]) -> InventoryFuture<'a, bool> {
Box::pin(async move {
self.contains_any_predicate(&|stack| {
!stack.is_empty() && items.contains(stack.get_item())
})
.await
})
fn contains_any(&self, items: &[Item]) -> bool {
self.contains_any_predicate(&|stack| !stack.is_empty() && items.contains(stack.get_item()))
}
fn write_inventory_nbt<'a>(
&'a self,
nbt: &'a mut NbtCompound,
include_empty: bool,
) -> InventoryFuture<'a, ()> {
Box::pin(async move {
let mut slots = Vec::new();
let size = self.size();
fn write_inventory_nbt(&self, nbt: &mut NbtCompound, include_empty: bool) {
let mut slots = Vec::new();
let size = self.size();
for i in 0..size {
let stack = self.get_stack(i).await;
for i in 0..size {
let stack = self.get_stack(i);
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);
slots.push(NbtTag::Compound(item_compound));
}
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);
slots.push(NbtTag::Compound(item_compound));
}
}
if include_empty || !slots.is_empty() {
nbt.put("Items", NbtTag::List(slots));
}
})
if include_empty || !slots.is_empty() {
nbt.put("Items", NbtTag::List(slots));
}
}
fn get_max_count_per_stack(&self) -> u8 {
@ -123,7 +98,7 @@ pub trait Inventory: Send + Sync + Clearable {
}
pub trait Clearable {
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>;
fn clear(&self);
}
pub fn sync_read_items_from_nbt(nbt: &NbtCompound, stacks: &mut [ItemStack]) {

View File

@ -1,7 +1,7 @@
use crate::inventory::{Clearable, Inventory, InventoryFuture};
use crate::inventory::{Clearable, Inventory};
use pumpkin_data::item_stack::ItemStack;
use std::any::Any;
use tokio::sync::RwLock;
use std::sync::RwLock;
pub struct SimpleInventory {
pub stacks: RwLock<Vec<ItemStack>>,
@ -19,11 +19,12 @@ impl SimpleInventory {
}
impl Clearable for SimpleInventory {
fn clear(&self) -> InventoryFuture<'_, ()> {
Box::pin(async move {
let mut stacks = self.stacks.write().await;
stacks.fill_with(|| ItemStack::EMPTY.clone());
})
fn clear(&self) {
let mut stacks = self
.stacks
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
stacks.fill_with(|| ItemStack::EMPTY.clone());
}
}
@ -32,52 +33,57 @@ impl Inventory for SimpleInventory {
self.size
}
fn is_empty(&self) -> InventoryFuture<'_, bool> {
Box::pin(async move {
let stacks = self.stacks.read().await;
stacks.iter().all(ItemStack::is_empty)
})
fn is_empty(&self) -> bool {
let stacks = self
.stacks
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
stacks.iter().all(ItemStack::is_empty)
}
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move {
let stacks = self.stacks.read().await;
stacks
.get(slot)
.cloned()
.unwrap_or_else(|| ItemStack::EMPTY.clone())
})
fn get_stack(&self, slot: usize) -> ItemStack {
let stacks = self
.stacks
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
stacks
.get(slot)
.cloned()
.unwrap_or_else(|| ItemStack::EMPTY.clone())
}
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move {
let mut stacks = self.stacks.write().await;
if slot < stacks.len() {
std::mem::replace(&mut stacks[slot], ItemStack::EMPTY.clone())
} else {
ItemStack::EMPTY.clone()
}
})
fn remove_stack(&self, slot: usize) -> ItemStack {
let mut stacks = self
.stacks
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if slot < stacks.len() {
std::mem::replace(&mut stacks[slot], ItemStack::EMPTY.clone())
} else {
ItemStack::EMPTY.clone()
}
}
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move {
let mut stacks = self.stacks.write().await;
if slot < stacks.len() && !stacks[slot].is_empty() && amount > 0 {
stacks[slot].split(amount)
} else {
ItemStack::EMPTY.clone()
}
})
fn remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack {
let mut stacks = self
.stacks
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if slot < stacks.len() && !stacks[slot].is_empty() && amount > 0 {
stacks[slot].split(amount)
} else {
ItemStack::EMPTY.clone()
}
}
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
Box::pin(async move {
let mut stacks = self.stacks.write().await;
if slot < stacks.len() {
stacks[slot] = stack;
}
})
fn set_stack(&self, slot: usize, stack: ItemStack) {
let mut stacks = self
.stacks
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if slot < stacks.len() {
stacks[slot] = stack;
}
}
fn as_any(&self) -> &dyn Any {

View File

@ -11,13 +11,13 @@ use pumpkin_data::translation;
use pumpkin_inventory::anvil::AnvilScreenHandler;
use pumpkin_inventory::player::player_inventory::PlayerInventory;
use pumpkin_inventory::screen_handler::{
BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
};
use pumpkin_macros::pumpkin_block_from_tag;
use pumpkin_util::text::TextComponent;
use pumpkin_world::inventory::SimpleInventory;
use std::sync::Arc;
use tokio::sync::Mutex;
use std::sync::Mutex;
#[pumpkin_block_from_tag("minecraft:anvil")]
pub struct AnvilBlock;
@ -29,13 +29,8 @@ impl BlockBehaviour for AnvilBlock {
pumpkin_data::statistic::CustomStatistic::InteractWithAnvil as i32,
1,
);
let player = Arc::clone(args.player);
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(&AnvilScreenFactory, Some(pos))
.await;
});
args.player
.open_handled_screen(&AnvilScreenFactory, Some(*args.position));
BlockActionResult::Success
}
@ -73,19 +68,17 @@ impl BlockBehaviour for AnvilBlock {
struct AnvilScreenFactory;
impl ScreenHandlerFactory for AnvilScreenFactory {
fn create_screen_handler<'a>(
&'a self,
fn create_screen_handler(
&self,
sync_id: u8,
player_inventory: &'a Arc<PlayerInventory>,
_player: &'a dyn InventoryPlayer,
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
Box::pin(async move {
let inventory = Arc::new(SimpleInventory::new(3));
let handler = AnvilScreenHandler::new(sync_id, player_inventory, inventory);
let concrete_arc = Arc::new(Mutex::new(handler));
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let inventory = Arc::new(SimpleInventory::new(3));
let handler = AnvilScreenHandler::new(sync_id, player_inventory, inventory);
let concrete_arc = Arc::new(Mutex::new(handler));
Some(concrete_arc as SharedScreenHandler)
})
Some(concrete_arc as SharedScreenHandler)
}
fn get_display_name(&self) -> TextComponent {

View File

@ -1,4 +1,5 @@
use std::sync::Arc;
use std::sync::Mutex;
use crate::block::{GetComparatorOutputArgs, OnPlaceArgs, PlacedArgs};
use crate::block::{
@ -14,28 +15,25 @@ use pumpkin_data::translation;
use pumpkin_inventory::generic_container_screen_handler::create_generic_9x3;
use pumpkin_inventory::player::player_inventory::PlayerInventory;
use pumpkin_inventory::screen_handler::{
BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::text::TextComponent;
use pumpkin_world::inventory::Inventory;
use tokio::sync::Mutex;
struct BarrelScreenFactory(Arc<dyn Inventory>);
impl ScreenHandlerFactory for BarrelScreenFactory {
fn create_screen_handler<'a>(
&'a self,
fn create_screen_handler(
&self,
sync_id: u8,
player_inventory: &'a Arc<PlayerInventory>,
_player: &'a dyn InventoryPlayer,
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
Box::pin(async move {
let handler = create_generic_9x3(sync_id, player_inventory, self.0.clone()).await;
let concrete_arc = Arc::new(Mutex::new(handler));
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let handler = create_generic_9x3(sync_id, player_inventory, self.0.clone());
let concrete_arc = Arc::new(Mutex::new(handler));
Some(concrete_arc as SharedScreenHandler)
})
Some(concrete_arc as SharedScreenHandler)
}
fn get_display_name(&self) -> TextComponent {
@ -65,13 +63,8 @@ impl BlockBehaviour for BarrelBlock {
pumpkin_data::statistic::CustomStatistic::OpenBarrel as i32,
1,
);
let player = Arc::clone(args.player);
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(&BarrelScreenFactory(inventory), Some(pos))
.await;
});
args.player
.open_handled_screen(&BarrelScreenFactory(inventory), Some(*args.position));
}
BlockActionResult::Success

View File

@ -1,11 +1,12 @@
use std::sync::Arc;
use tokio::sync::Mutex;
use std::sync::Mutex;
use crate::block::entities::BlockEntity;
use pumpkin_data::translation;
use pumpkin_inventory::beacon_screen_handler::create_beacon_handler;
use pumpkin_inventory::player::player_inventory::PlayerInventory;
use pumpkin_inventory::screen_handler::{
BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::text::TextComponent;
@ -18,22 +19,16 @@ use crate::block::{BlockBehaviour, NormalUseArgs};
struct BeaconScreenFactory(Arc<dyn Inventory>);
impl ScreenHandlerFactory for BeaconScreenFactory {
fn create_screen_handler<'a>(
&'a self,
fn create_screen_handler(
&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;
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let concrete_handler = create_beacon_handler(sync_id, player_inventory, self.0.clone());
let concrete_arc = Arc::new(Mutex::new(concrete_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)
})
Some(concrete_arc as SharedScreenHandler)
}
fn get_display_name(&self) -> TextComponent {
@ -63,13 +58,8 @@ impl BlockBehaviour for BeaconBlock {
);
// Open the screen using the factory
let player = Arc::clone(args.player);
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(&BeaconScreenFactory(inventory), Some(pos))
.await;
});
args.player
.open_handled_screen(&BeaconScreenFactory(inventory), Some(*args.position));
BlockActionResult::Success
}

View File

@ -1,4 +1,5 @@
use std::sync::Arc;
use std::sync::Mutex;
use crate::block::entities::{
PropertyDelegate, blasting_furnace::BlastingFurnaceBlockEntity,
@ -13,11 +14,10 @@ use pumpkin_data::{
use pumpkin_inventory::{
furnace_like::furnace_like_screen_handler::FurnaceLikeScreenHandler,
player::player_inventory::PlayerInventory,
screen_handler::{BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler},
screen_handler::{InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler},
};
use pumpkin_macros::pumpkin_block;
use pumpkin_world::inventory::Inventory;
use tokio::sync::Mutex;
use crate::{
block::{
@ -48,27 +48,24 @@ impl BlastingFurnaceScreenFactory {
}
impl ScreenHandlerFactory for BlastingFurnaceScreenFactory {
fn create_screen_handler<'a>(
&'a self,
fn create_screen_handler(
&self,
sync_id: u8,
player_inventory: &'a Arc<PlayerInventory>,
_player: &'a dyn InventoryPlayer,
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
Box::pin(async move {
let concrete_handler = FurnaceLikeScreenHandler::new(
sync_id,
player_inventory,
self.inventory.clone(),
self.property_delegate.clone(),
self.experience_container.clone(),
WindowType::BlastFurnace,
)
.await;
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let concrete_handler = FurnaceLikeScreenHandler::new(
sync_id,
player_inventory,
self.inventory.clone(),
&self.property_delegate,
self.experience_container.clone(),
WindowType::BlastFurnace,
);
let concrete_arc = Arc::new(Mutex::new(concrete_handler));
let concrete_arc = Arc::new(Mutex::new(concrete_handler));
Some(concrete_arc as SharedScreenHandler)
})
Some(concrete_arc as SharedScreenHandler)
}
fn get_display_name(&self) -> pumpkin_util::text::TextComponent {
@ -99,13 +96,8 @@ impl BlockBehaviour for BlastFurnaceBlock {
property_delegate,
experience_container,
);
let player = Arc::clone(args.player);
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(&blasting_furnace_screen_factory, Some(pos))
.await;
});
args.player
.open_handled_screen(&blasting_furnace_screen_factory, Some(*args.position));
}
crate::block::registry::BlockActionResult::Consume
}

View File

@ -1,4 +1,5 @@
use std::sync::Arc;
use std::sync::Mutex;
use crate::block::{GetComparatorOutputArgs, PlacedArgs};
use crate::block::{
@ -9,7 +10,9 @@ use crate::block::{
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_inventory::screen_handler::{
InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::text::TextComponent;
use pumpkin_world::inventory::Inventory;
@ -20,21 +23,15 @@ struct BrewingScreenFactory(
);
impl ScreenHandlerFactory for BrewingScreenFactory {
fn create_screen_handler<'a>(
&'a self,
fn create_screen_handler(
&self,
sync_id: u8,
player_inventory: &'a Arc<PlayerInventory>,
_player: &'a dyn pumpkin_inventory::screen_handler::InventoryPlayer,
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let inventory = self.0.clone();
let property_delegate = self.1.clone();
let pi = player_inventory.clone();
Box::pin(async move {
// Delegate to pumpkin-inventory brewing creator
pumpkin_inventory::brewing::create_brewing(sync_id, pi, inventory, property_delegate)
.await
.map(|handler| Arc::new(tokio::sync::Mutex::new(handler)) as SharedScreenHandler)
})
pumpkin_inventory::brewing::create_brewing(sync_id, player_inventory, inventory, &self.1)
.map(|handler| Arc::new(Mutex::new(handler)) as SharedScreenHandler)
}
fn get_display_name(&self) -> TextComponent {
@ -59,13 +56,8 @@ impl BlockBehaviour for BrewingStandBlock {
pumpkin_data::statistic::CustomStatistic::InteractWithBrewingstand as i32,
1,
);
let player = Arc::clone(args.player);
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(&BrewingScreenFactory(inventory, pd), Some(pos))
.await;
});
args.player
.open_handled_screen(&BrewingScreenFactory(inventory, pd), Some(*args.position));
}
BlockActionResult::Success
@ -83,7 +75,7 @@ impl BlockBehaviour for BrewingStandBlock {
let mut bottles = 0u8;
// Bottle slots are 0, 1, 2 in brewing stands
for slot in 0..3 {
let stack = futures::executor::block_on(inventory.get_stack(slot));
let stack = inventory.get_stack(slot);
if !stack.is_empty() {
bottles += 1;
}

View File

@ -5,12 +5,12 @@ use pumpkin_data::translation;
use pumpkin_inventory::cartography_table_screen_handler::CartographyTableScreenHandler;
use pumpkin_inventory::player::player_inventory::PlayerInventory;
use pumpkin_inventory::screen_handler::{
BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::text::TextComponent;
use std::sync::Arc;
use tokio::sync::Mutex;
use std::sync::Mutex;
#[pumpkin_block("minecraft:cartography_table")]
pub struct CartographyTableBlock;
@ -22,13 +22,8 @@ impl BlockBehaviour for CartographyTableBlock {
pumpkin_data::statistic::CustomStatistic::InteractWithCartographyTable as i32,
1,
);
let player = Arc::clone(args.player);
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(&CartographyTableScreenFactory, Some(pos))
.await;
});
args.player
.open_handled_screen(&CartographyTableScreenFactory, Some(*args.position));
BlockActionResult::Success
}
@ -37,18 +32,16 @@ impl BlockBehaviour for CartographyTableBlock {
struct CartographyTableScreenFactory;
impl ScreenHandlerFactory for CartographyTableScreenFactory {
fn create_screen_handler<'a>(
&'a self,
fn create_screen_handler(
&self,
sync_id: u8,
player_inventory: &'a Arc<PlayerInventory>,
_player: &'a dyn InventoryPlayer,
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
Box::pin(async move {
let handler: SharedScreenHandler = Arc::new(Mutex::new(
CartographyTableScreenHandler::new(sync_id, player_inventory),
));
Some(handler)
})
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let handler: SharedScreenHandler = Arc::new(Mutex::new(
CartographyTableScreenHandler::new(sync_id, player_inventory),
));
Some(handler)
}
fn get_display_name(&self) -> TextComponent {

View File

@ -13,7 +13,7 @@ use pumpkin_inventory::double::DoubleInventory;
use pumpkin_inventory::generic_container_screen_handler::{create_generic_9x3, create_generic_9x6};
use pumpkin_inventory::player::player_inventory::PlayerInventory;
use pumpkin_inventory::screen_handler::{
BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
};
use pumpkin_macros::{pumpkin_block, pumpkin_block_from_tag};
use pumpkin_util::GameMode;
@ -21,7 +21,7 @@ use pumpkin_util::math::position::BlockPos;
use pumpkin_util::text::TextComponent;
use pumpkin_world::inventory::Inventory;
use pumpkin_world::world::BlockFlags;
use tokio::sync::Mutex;
use std::sync::Mutex;
use crate::block::{
BrokenArgs, EmitsRedstonePowerArgs, GetComparatorOutputArgs, GetRedstonePowerArgs,
@ -39,23 +39,21 @@ use crate::{
struct ChestScreenFactory(Arc<dyn Inventory>);
impl ScreenHandlerFactory for ChestScreenFactory {
fn create_screen_handler<'a>(
&'a self,
fn create_screen_handler(
&self,
sync_id: u8,
player_inventory: &'a Arc<PlayerInventory>,
_player: &'a dyn InventoryPlayer,
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
Box::pin(async move {
let concrete_handler = if self.0.size() > 27 {
create_generic_9x6(sync_id, player_inventory, self.0.clone()).await
} else {
create_generic_9x3(sync_id, player_inventory, self.0.clone()).await
};
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let concrete_handler = if self.0.size() > 27 {
create_generic_9x6(sync_id, player_inventory, self.0.clone())
} else {
create_generic_9x3(sync_id, player_inventory, self.0.clone())
};
let concrete_arc = Arc::new(Mutex::new(concrete_handler));
let concrete_arc = Arc::new(Mutex::new(concrete_handler));
Some(concrete_arc as SharedScreenHandler)
})
Some(concrete_arc as SharedScreenHandler)
}
fn get_display_name(&self) -> TextComponent {
@ -196,10 +194,8 @@ fn normal_use_chest_impl(args: &NormalUseArgs<'_>) -> BlockActionResult {
&& let Some(table) = get_chest_loot_table(&loot_key)
&& let Some(inv) = entity.clone().get_inventory()
{
tokio::spawn(async move {
fill_chest_inventory(&inv, table, seed).await;
inv.mark_dirty();
});
fill_chest_inventory(&inv, table, seed);
inv.mark_dirty();
}
let Some(first_inventory) = first_chest.and_then(BlockEntity::get_inventory) else {
@ -240,13 +236,8 @@ fn normal_use_chest_impl(args: &NormalUseArgs<'_>) -> BlockActionResult {
first_inventory
};
let player = args.player.clone();
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(&ChestScreenFactory(inventory), Some(pos))
.await;
});
args.player
.open_handled_screen(&ChestScreenFactory(inventory), Some(*args.position));
BlockActionResult::Success
}

View File

@ -5,12 +5,12 @@ use pumpkin_data::translation;
use pumpkin_inventory::crafting::crafting_screen_handler::CraftingTableScreenHandler;
use pumpkin_inventory::player::player_inventory::PlayerInventory;
use pumpkin_inventory::screen_handler::{
BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::text::TextComponent;
use std::sync::Arc;
use tokio::sync::Mutex;
use std::sync::Mutex;
#[pumpkin_block("minecraft:crafting_table")]
pub struct CraftingTableBlock;
@ -22,14 +22,11 @@ impl BlockBehaviour for CraftingTableBlock {
pumpkin_data::statistic::CustomStatistic::InteractWithCraftingTable as i32,
1,
);
let player = Arc::clone(args.player);
let recipe_manager = args.server.recipe_manager.clone();
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(&CraftingTableScreenFactory(recipe_manager), Some(pos))
.await;
});
args.player.open_handled_screen(
&CraftingTableScreenFactory(recipe_manager),
Some(*args.position),
);
BlockActionResult::Success
}
@ -38,20 +35,17 @@ impl BlockBehaviour for CraftingTableBlock {
struct CraftingTableScreenFactory(Arc<crate::server::RecipeManager>);
impl ScreenHandlerFactory for CraftingTableScreenFactory {
fn create_screen_handler<'a>(
&'a self,
fn create_screen_handler(
&self,
sync_id: u8,
player_inventory: &'a Arc<PlayerInventory>,
_player: &'a dyn InventoryPlayer,
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
Box::pin(async move {
let handler =
CraftingTableScreenHandler::new(sync_id, player_inventory, Some(self.0.clone()))
.await;
let concrete_arc = Arc::new(Mutex::new(handler));
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let handler =
CraftingTableScreenHandler::new(sync_id, player_inventory, Some(self.0.clone()));
let concrete_arc = Arc::new(Mutex::new(handler));
Some(concrete_arc as SharedScreenHandler)
})
Some(concrete_arc as SharedScreenHandler)
}
fn get_display_name(&self) -> TextComponent {

View File

@ -1,4 +1,5 @@
use std::sync::Arc;
use std::sync::Mutex;
use crate::block::entities::enchanting_table::EnchantingTableBlockEntity;
use crate::block::registry::BlockActionResult;
@ -7,13 +8,12 @@ use pumpkin_data::{Block, BlockStateId, translation};
use pumpkin_inventory::enchanting::enchanting_screen_handler::EnchantingTableScreenHandler;
use pumpkin_inventory::player::player_inventory::PlayerInventory;
use pumpkin_inventory::screen_handler::{
BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::text::TextComponent;
use pumpkin_world::inventory::{Inventory, SimpleInventory};
use tokio::sync::Mutex;
#[pumpkin_block("minecraft:enchanting_table")]
pub struct EnchantingTableBlock;
@ -69,20 +69,14 @@ impl BlockBehaviour for EnchantingTableBlock {
}
let bookshelf_count = bookshelf_count.min(15);
let player = Arc::clone(args.player);
let pos = *args.position;
let seed = args.player.enchantment_seed();
tokio::spawn(async move {
player
.open_handled_screen(
&EnchantingTableScreenFactory {
bookshelf_count,
seed,
},
Some(pos),
)
.await;
});
args.player.open_handled_screen(
&EnchantingTableScreenFactory {
bookshelf_count,
seed,
},
Some(*args.position),
);
BlockActionResult::Success
}
}
@ -101,24 +95,22 @@ struct EnchantingTableScreenFactory {
}
impl ScreenHandlerFactory for EnchantingTableScreenFactory {
fn create_screen_handler<'a>(
&'a self,
fn create_screen_handler(
&self,
sync_id: u8,
player_inventory: &'a Arc<PlayerInventory>,
_player: &'a dyn InventoryPlayer,
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
Box::pin(async move {
let inventory: Arc<dyn Inventory> = Arc::new(SimpleInventory::new(2));
let handler = EnchantingTableScreenHandler::new(
sync_id,
player_inventory,
&inventory,
self.seed,
self.bookshelf_count,
);
let screen_handler_arc = Arc::new(Mutex::new(handler));
Some(screen_handler_arc as SharedScreenHandler)
})
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let inventory: Arc<dyn Inventory> = Arc::new(SimpleInventory::new(2));
let handler = EnchantingTableScreenHandler::new(
sync_id,
player_inventory,
&inventory,
self.seed,
self.bookshelf_count,
);
let screen_handler_arc = Arc::new(Mutex::new(handler));
Some(screen_handler_arc as SharedScreenHandler)
}
fn get_display_name(&self) -> TextComponent {

View File

@ -1,4 +1,5 @@
use std::sync::Arc;
use std::sync::Mutex;
use crate::block::entities::ender_chest::EnderChestBlockEntity;
use crate::block::{
@ -13,13 +14,12 @@ use pumpkin_inventory::{
generic_container_screen_handler::create_generic_9x3,
player::ender_chest_inventory::EnderChestInventory,
player::player_inventory::PlayerInventory,
screen_handler::{BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler},
screen_handler::{InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler},
};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::text::TextComponent;
use pumpkin_world::block::viewer::ViewerCountTracker;
use tokio::sync::Mutex;
pub struct EnderChestScreenFactory {
pub inventory: Arc<EnderChestInventory>,
@ -27,22 +27,19 @@ pub struct EnderChestScreenFactory {
}
impl ScreenHandlerFactory for EnderChestScreenFactory {
fn create_screen_handler<'a>(
&'a self,
fn create_screen_handler(
&self,
sync_id: u8,
player_inventory: &'a Arc<PlayerInventory>,
_player: &'a dyn InventoryPlayer,
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
Box::pin(async move {
if let Some(tracker) = &self.tracker {
self.inventory.set_tracker(tracker.clone());
}
let handler =
create_generic_9x3(sync_id, player_inventory, self.inventory.clone()).await;
let concrete_arc = Arc::new(Mutex::new(handler));
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
if let Some(tracker) = &self.tracker {
self.inventory.set_tracker(tracker.clone());
}
let handler = create_generic_9x3(sync_id, player_inventory, self.inventory.clone());
let concrete_arc = Arc::new(Mutex::new(handler));
Some(concrete_arc as SharedScreenHandler)
})
Some(concrete_arc as SharedScreenHandler)
}
fn get_display_name(&self) -> TextComponent {
@ -96,21 +93,15 @@ impl BlockBehaviour for EnderChestBlock {
pumpkin_data::statistic::CustomStatistic::OpenEnderchest as i32,
1,
);
let player = Arc::clone(args.player);
let pos = *args.position;
let tracker = block_entity.get_tracker();
tokio::spawn(async move {
let inventory = player.ender_chest_inventory();
player
.open_handled_screen(
&EnderChestScreenFactory {
inventory: inventory.clone(),
tracker: Some(tracker),
},
Some(pos),
)
.await;
});
let inventory = args.player.ender_chest_inventory();
args.player.open_handled_screen(
&EnderChestScreenFactory {
inventory: inventory.clone(),
tracker: Some(tracker),
},
Some(*args.position),
);
// TODO: PiglinBrain.onGuardedBlockInteracted(serverWorld, player, true);
}

View File

@ -1,4 +1,5 @@
use std::sync::Arc;
use std::sync::Mutex;
use crate::block::entities::{
PropertyDelegate, furnace::FurnaceBlockEntity, furnace_like_block_entity::ExperienceContainer,
@ -12,11 +13,10 @@ use pumpkin_data::{
use pumpkin_inventory::{
furnace_like::furnace_like_screen_handler::FurnaceLikeScreenHandler,
player::player_inventory::PlayerInventory,
screen_handler::{BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler},
screen_handler::{InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler},
};
use pumpkin_macros::pumpkin_block;
use pumpkin_world::inventory::Inventory;
use tokio::sync::Mutex;
use crate::{
block::{
@ -47,27 +47,24 @@ impl FurnaceScreenFactory {
}
impl ScreenHandlerFactory for FurnaceScreenFactory {
fn create_screen_handler<'a>(
&'a self,
fn create_screen_handler(
&self,
sync_id: u8,
player_inventory: &'a Arc<PlayerInventory>,
_player: &'a dyn InventoryPlayer,
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
Box::pin(async move {
let concrete_handler = FurnaceLikeScreenHandler::new(
sync_id,
player_inventory,
self.inventory.clone(),
self.property_delegate.clone(),
self.experience_container.clone(),
WindowType::Furnace,
)
.await;
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let concrete_handler = FurnaceLikeScreenHandler::new(
sync_id,
player_inventory,
self.inventory.clone(),
&self.property_delegate,
self.experience_container.clone(),
WindowType::Furnace,
);
let concrete_arc = Arc::new(Mutex::new(concrete_handler));
let concrete_arc = Arc::new(Mutex::new(concrete_handler));
Some(concrete_arc as SharedScreenHandler)
})
Some(concrete_arc as SharedScreenHandler)
}
fn get_display_name(&self) -> pumpkin_util::text::TextComponent {
@ -95,13 +92,8 @@ impl BlockBehaviour for FurnaceBlock {
);
let furnace_screen_factory =
FurnaceScreenFactory::new(inventory, property_delegate, experience_container);
let player = Arc::clone(args.player);
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(&furnace_screen_factory, Some(pos))
.await;
});
args.player
.open_handled_screen(&furnace_screen_factory, Some(*args.position));
}
crate::block::registry::BlockActionResult::Consume
}

View File

@ -1,4 +1,5 @@
use std::sync::Arc;
use std::sync::Mutex;
use crate::block::blocks::redstone::block_receives_redstone_power;
use crate::block::{GetComparatorOutputArgs, OnNeighborUpdateArgs, OnPlaceArgs, PlacedArgs};
@ -15,31 +16,27 @@ use pumpkin_data::{Block, BlockDirection, translation};
use pumpkin_inventory::generic_container_screen_handler::create_hopper;
use pumpkin_inventory::player::player_inventory::PlayerInventory;
use pumpkin_inventory::screen_handler::{
BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::text::TextComponent;
use pumpkin_world::inventory::Inventory;
use pumpkin_world::world::BlockFlags;
use tokio::sync::Mutex;
struct HopperBlockScreenFactory(Arc<dyn Inventory>);
impl ScreenHandlerFactory for HopperBlockScreenFactory {
fn create_screen_handler<'a>(
&'a self,
fn create_screen_handler(
&self,
sync_id: u8,
player_inventory: &'a Arc<PlayerInventory>,
_player: &'a dyn InventoryPlayer,
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
Box::pin(async move {
let concrete_handler = create_hopper(sync_id, player_inventory, self.0.clone()).await;
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let concrete_handler = create_hopper(sync_id, player_inventory, self.0.clone());
let concrete_arc = Arc::new(Mutex::new(concrete_handler));
let concrete_arc = Arc::new(Mutex::new(concrete_handler));
Some(concrete_arc as SharedScreenHandler)
})
Some(concrete_arc as SharedScreenHandler)
}
fn get_display_name(&self) -> TextComponent {
@ -65,13 +62,8 @@ impl BlockBehaviour for HopperBlock {
pumpkin_data::statistic::CustomStatistic::InspectHopper as i32,
1,
);
let player = Arc::clone(args.player);
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(&HopperBlockScreenFactory(inventory), Some(pos))
.await;
});
args.player
.open_handled_screen(&HopperBlockScreenFactory(inventory), Some(*args.position));
}
BlockActionResult::Success

View File

@ -20,7 +20,7 @@ use pumpkin_data::{Block, BlockDirection, BlockStateId, tag, translation};
use pumpkin_inventory::lectern_screen_handler::{LecternController, LecternScreenHandler};
use pumpkin_inventory::player::player_inventory::PlayerInventory;
use pumpkin_inventory::screen_handler::{
BoxFuture, InventoryPlayer, ScreenHandlerFactory, ScreenHandlerFuture, SharedScreenHandler,
InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos;
@ -29,7 +29,7 @@ use pumpkin_util::text::TextComponent;
use pumpkin_world::inventory::Inventory;
use pumpkin_world::tick::TickPriority;
use pumpkin_world::world::BlockFlags;
use tokio::sync::Mutex;
use std::sync::Mutex;
/// Bridges the screen handler back into the world: page changes emit the
/// vanilla redstone pulse and taking the book clears `has_book`.
@ -51,29 +51,25 @@ impl LecternController for LecternPageController {
.map_or(0, |entity| entity.page.load(Ordering::Relaxed) as i32)
}
fn set_page(&self, page: i32) -> ScreenHandlerFuture<'_, ()> {
Box::pin(async move {
let Some(entity) = self.entity() else {
return;
};
let page_count = entity.page_count().await;
let page = page.clamp(0, (page_count - 1).max(0));
if page == entity.page.load(Ordering::Relaxed) as i32 {
return;
}
entity.page.store(page as usize, Ordering::Relaxed);
entity.mark_dirty();
LecternBlock::pulse(&self.world, &self.position);
})
fn set_page(&self, page: i32) {
let Some(entity) = self.entity() else {
return;
};
let page_count = entity.page_count();
let page = page.clamp(0, (page_count - 1).max(0));
if page == entity.page.load(Ordering::Relaxed) as i32 {
return;
}
entity.page.store(page as usize, Ordering::Relaxed);
entity.mark_dirty();
LecternBlock::pulse(&self.world, &self.position);
}
fn on_book_taken(&self) -> ScreenHandlerFuture<'_, ()> {
Box::pin(async move {
if let Some(entity) = self.entity() {
entity.page.store(0, Ordering::Relaxed);
}
LecternBlock::set_has_book(&self.world, &self.position, false);
})
fn on_book_taken(&self) {
if let Some(entity) = self.entity() {
entity.page.store(0, Ordering::Relaxed);
}
LecternBlock::set_has_book(&self.world, &self.position, false);
}
}
@ -83,17 +79,15 @@ struct LecternScreenFactory {
}
impl ScreenHandlerFactory for LecternScreenFactory {
fn create_screen_handler<'a>(
&'a self,
fn create_screen_handler(
&self,
sync_id: u8,
_player_inventory: &'a Arc<PlayerInventory>,
_player: &'a dyn InventoryPlayer,
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
Box::pin(async move {
let handler =
LecternScreenHandler::new(sync_id, self.inventory.clone(), self.controller.clone());
Some(Arc::new(Mutex::new(handler)) as SharedScreenHandler)
})
_player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let handler =
LecternScreenHandler::new(sync_id, self.inventory.clone(), self.controller.clone());
Some(Arc::new(Mutex::new(handler)) as SharedScreenHandler)
}
fn get_display_name(&self) -> TextComponent {
@ -194,19 +188,13 @@ impl BlockBehaviour for LecternBlock {
position: *args.position,
inventory: inventory.clone(),
});
let player = args.player.clone();
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(
&LecternScreenFactory {
inventory,
controller,
},
Some(pos),
)
.await;
});
args.player.open_handled_screen(
&LecternScreenFactory {
inventory,
controller,
},
Some(*args.position),
);
BlockActionResult::Success
}
@ -234,7 +222,7 @@ impl BlockBehaviour for LecternBlock {
};
let book = item_stack.split_unless_creative(args.player.gamemode.load(), 1);
futures::executor::block_on(lectern.set_stack(0, book));
lectern.set_stack(0, book);
Self::set_has_book(args.world, args.position, true);
args.world
@ -287,7 +275,7 @@ impl BlockBehaviour for LecternBlock {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(lectern_entity) = block_entity.as_any().downcast_ref::<LecternBlockEntity>()
{
let book = futures::executor::block_on(lectern_entity.remove_stack(0));
let book = lectern_entity.remove_stack(0);
if !book.is_empty() {
// Drop the book item
let entity = Entity::new(
@ -309,9 +297,7 @@ impl BlockBehaviour for LecternBlock {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(lectern_entity) = block_entity.as_any().downcast_ref::<LecternBlockEntity>()
{
Some(futures::executor::block_on(
lectern_entity.comparator_output(),
))
Some(lectern_entity.comparator_output())
} else {
Some(0)
}

View File

@ -8,12 +8,12 @@ use pumpkin_data::{BlockStateId, FacingExt};
use pumpkin_inventory::loom_screen_handler::LoomScreenHandler;
use pumpkin_inventory::player::player_inventory::PlayerInventory;
use pumpkin_inventory::screen_handler::{
BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::text::TextComponent;
use std::sync::Arc;
use tokio::sync::Mutex;
use std::sync::Mutex;
#[pumpkin_block("minecraft:loom")]
pub struct LoomBlock;
@ -39,13 +39,8 @@ impl BlockBehaviour for LoomBlock {
pumpkin_data::statistic::CustomStatistic::InteractWithLoom as i32,
1,
);
let player = Arc::clone(args.player);
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(&LoomScreenFactory, Some(pos))
.await;
});
args.player
.open_handled_screen(&LoomScreenFactory, Some(*args.position));
BlockActionResult::Success
}
@ -54,19 +49,17 @@ impl BlockBehaviour for LoomBlock {
struct LoomScreenFactory;
impl ScreenHandlerFactory for LoomScreenFactory {
fn create_screen_handler<'a>(
&'a self,
fn create_screen_handler(
&self,
sync_id: u8,
player_inventory: &'a Arc<PlayerInventory>,
_player: &'a dyn InventoryPlayer,
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
Box::pin(async move {
let handler: SharedScreenHandler = Arc::new(Mutex::new(LoomScreenHandler::new(
sync_id,
player_inventory,
)));
Some(handler)
})
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let handler: SharedScreenHandler = Arc::new(Mutex::new(LoomScreenHandler::new(
sync_id,
player_inventory,
)));
Some(handler)
}
fn get_display_name(&self) -> TextComponent {

View File

@ -1,5 +1,5 @@
use std::sync::Arc;
use tokio::sync::Mutex;
use std::sync::Mutex;
use crate::block::blocks::redstone::block_receives_redstone_power;
use crate::block::entities::crafter::CrafterBlockEntity;
@ -17,7 +17,7 @@ use pumpkin_data::{BlockDirection, BlockStateId};
use pumpkin_inventory::generic_container_screen_handler::create_crafter_3x3;
use pumpkin_inventory::player::player_inventory::PlayerInventory;
use pumpkin_inventory::screen_handler::{
BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::text::TextComponent;
@ -28,18 +28,16 @@ use pumpkin_world::world::BlockFlags;
struct CrafterScreenFactory(Arc<dyn Inventory>);
impl ScreenHandlerFactory for CrafterScreenFactory {
fn create_screen_handler<'a>(
&'a self,
fn create_screen_handler(
&self,
sync_id: u8,
player_inventory: &'a Arc<PlayerInventory>,
_player: &'a dyn InventoryPlayer,
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
Box::pin(async move {
let handler = create_crafter_3x3(sync_id, player_inventory, self.0.clone()).await;
let screen_handler_arc = Arc::new(Mutex::new(handler));
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let handler = create_crafter_3x3(sync_id, player_inventory, self.0.clone());
let screen_handler_arc = Arc::new(Mutex::new(handler));
Some(screen_handler_arc as SharedScreenHandler)
})
Some(screen_handler_arc as SharedScreenHandler)
}
fn get_display_name(&self) -> TextComponent {
@ -58,13 +56,8 @@ impl BlockBehaviour for CrafterBlock {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
let player = Arc::clone(args.player);
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(&CrafterScreenFactory(inventory), Some(pos))
.await;
});
args.player
.open_handled_screen(&CrafterScreenFactory(inventory), Some(*args.position));
}
BlockActionResult::Success
}
@ -171,7 +164,10 @@ impl BlockBehaviour for CrafterBlock {
if let Some(block_entity) = args.world.get_block_entity(args.position) {
let crafter = block_entity.as_any().downcast_ref::<CrafterBlockEntity>()?;
let items = crafter.items.blocking_read();
let items = crafter
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let occupied = items.iter().filter(|s| !s.is_empty()).count() as u8;
Some(occupied)
} else {

View File

@ -1,7 +1,7 @@
use rand::{Rng, RngExt, rng};
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::atomic::AtomicBool;
use tokio::sync::Mutex;
use uuid::Uuid;
use crate::block::blocks::redstone::block_receives_redstone_power;
@ -50,7 +50,7 @@ use pumpkin_data::{Block, BlockStateId, FacingExt};
use pumpkin_inventory::generic_container_screen_handler::create_generic_3x3;
use pumpkin_inventory::player::player_inventory::PlayerInventory;
use pumpkin_inventory::screen_handler::{
BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::boundingbox::{BoundingBox, EntityDimensions};
@ -65,18 +65,16 @@ use pumpkin_world::world::BlockFlags;
struct DispenserScreenFactory(Arc<dyn Inventory>);
impl ScreenHandlerFactory for DispenserScreenFactory {
fn create_screen_handler<'a>(
&'a self,
fn create_screen_handler(
&self,
sync_id: u8,
player_inventory: &'a Arc<PlayerInventory>,
_player: &'a dyn InventoryPlayer,
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
Box::pin(async move {
let handler = create_generic_3x3(sync_id, player_inventory, self.0.clone()).await;
let screen_handler_arc = Arc::new(Mutex::new(handler));
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let handler = create_generic_3x3(sync_id, player_inventory, self.0.clone());
let screen_handler_arc = Arc::new(Mutex::new(handler));
Some(screen_handler_arc as SharedScreenHandler)
})
Some(screen_handler_arc as SharedScreenHandler)
}
fn get_display_name(&self) -> TextComponent {
@ -129,13 +127,8 @@ impl BlockBehaviour for DispenserBlock {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
let player = Arc::clone(args.player);
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(&DispenserScreenFactory(inventory), Some(pos))
.await;
});
args.player
.open_handled_screen(&DispenserScreenFactory(inventory), Some(*args.position));
}
BlockActionResult::Success
}
@ -191,7 +184,7 @@ impl BlockBehaviour for DispenserBlock {
return;
};
if let Some((slot_index, mut item)) = dispenser.get_random_slot().await {
if let Some((slot_index, mut item)) = dispenser.get_random_slot() {
let props = DispenserLikeProperties::from_state_id(state.id, block);
let ctx = DispenseContext {
world: &world,
@ -199,7 +192,7 @@ impl BlockBehaviour for DispenserBlock {
facing: props.facing,
};
Self::dispense(&ctx, dispenser, &mut item).await;
dispenser.set_stack(slot_index, item).await;
dispenser.set_stack(slot_index, item);
} else {
world.sync_world_event(WorldEvent::SoundDispenserFail, position, 0);
}
@ -291,7 +284,7 @@ impl DispenserBlock {
Self::dispense_firework_rocket(ctx, item);
} else if item.item.id == Item::BUCKET.id {
// Empty buckets pick up the fluid in front of the dispenser
Self::dispense_empty_bucket(ctx, dispenser, item).await;
Self::dispense_empty_bucket(ctx, dispenser, item);
} else if FilledBucketItem::ids().contains(&item.item.id) {
// Filled buckets place their fluid in front of the dispenser
Self::dispense_filled_bucket(ctx, item);
@ -663,7 +656,7 @@ impl DispenserBlock {
Self::finish_projectile_launch(ctx, Arc::new(rocket), WorldEvent::SoundFireworkShoot);
}
async fn dispense_empty_bucket(
fn dispense_empty_bucket(
ctx: &DispenseContext<'_>,
dispenser: &DispenserBlockEntity,
item: &mut ItemStack,
@ -678,7 +671,7 @@ impl DispenserBlock {
let filled_stack = ItemStack::new(1, filled);
if item.is_empty() {
*item = filled_stack;
} else if let Some(rest) = Self::add_to_first_free_slot(dispenser, filled_stack).await {
} else if let Some(rest) = Self::add_to_first_free_slot(dispenser, filled_stack) {
Self::eject_item(ctx, rest);
}
@ -688,11 +681,14 @@ impl DispenserBlock {
/// Places `stack` into the first empty slot, returning it back if every slot is occupied.
/// The slot currently being dispensed from still holds its pre-dispense stack, so it is
/// never considered free.
async fn add_to_first_free_slot(
fn add_to_first_free_slot(
dispenser: &DispenserBlockEntity,
stack: ItemStack,
) -> Option<ItemStack> {
let mut items = dispenser.items.write().await;
let mut items = dispenser
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
for slot in items.iter_mut() {
if slot.is_empty() {
*slot = stack;

View File

@ -1,6 +1,6 @@
use rand::{Rng, RngExt, rng};
use std::sync::Arc;
use tokio::sync::Mutex;
use std::sync::Mutex;
use crate::block::blocks::redstone::block_receives_redstone_power;
use crate::block::registry::BlockActionResult;
@ -21,7 +21,7 @@ use pumpkin_data::{FacingExt, translation};
use pumpkin_inventory::generic_container_screen_handler::create_generic_3x3;
use pumpkin_inventory::player::player_inventory::PlayerInventory;
use pumpkin_inventory::screen_handler::{
BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::vector3::Vector3;
@ -33,18 +33,16 @@ use pumpkin_world::world::BlockFlags;
struct DropperScreenFactory(Arc<dyn Inventory>);
impl ScreenHandlerFactory for DropperScreenFactory {
fn create_screen_handler<'a>(
&'a self,
fn create_screen_handler(
&self,
sync_id: u8,
player_inventory: &'a Arc<PlayerInventory>,
_player: &'a dyn InventoryPlayer,
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
Box::pin(async move {
let handler = create_generic_3x3(sync_id, player_inventory, self.0.clone()).await;
let screen_handler_arc = Arc::new(Mutex::new(handler));
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let handler = create_generic_3x3(sync_id, player_inventory, self.0.clone());
let screen_handler_arc = Arc::new(Mutex::new(handler));
Some(screen_handler_arc as SharedScreenHandler)
})
Some(screen_handler_arc as SharedScreenHandler)
}
fn get_display_name(&self) -> TextComponent {
@ -91,13 +89,8 @@ impl BlockBehaviour for DropperBlock {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
let player = Arc::clone(args.player);
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(&DropperScreenFactory(inventory), Some(pos))
.await;
});
args.player
.open_handled_screen(&DropperScreenFactory(inventory), Some(*args.position));
}
BlockActionResult::Success
}
@ -152,7 +145,7 @@ impl BlockBehaviour for DropperBlock {
return;
};
if let Some((slot_index, mut item)) = dropper.get_random_slot().await {
if let Some((slot_index, mut item)) = dropper.get_random_slot() {
let props = DispenserLikeProperties::from_state_id(state.id, block);
let target_pos = position.offset(props.facing.to_block_direction().to_offset());
@ -163,20 +156,18 @@ impl BlockBehaviour for DropperBlock {
let backup = item.clone();
let one_item = item.split(1);
if HopperBlockEntity::add_one_item(dropper, container.as_ref(), one_item)
.await
{
dropper.set_stack(slot_index, item).await;
if HopperBlockEntity::add_one_item(dropper, container.as_ref(), &one_item) {
dropper.set_stack(slot_index, item);
return;
}
dropper.set_stack(slot_index, backup).await;
dropper.set_stack(slot_index, backup);
return;
}
// No container found, dispense item into the world
let drop_item = item.split(1);
dropper.set_stack(slot_index, item).await;
dropper.set_stack(slot_index, item);
let facing = to_normal(props.facing);
let mut pos = position.to_centered_f64().add(&(facing * 0.7));

View File

@ -1,4 +1,5 @@
use std::sync::Arc;
use std::sync::Mutex;
use crate::block::{GetComparatorOutputArgs, OnPlaceArgs, OnSyncedBlockEventArgs, PlacedArgs};
use crate::block::{
@ -13,28 +14,25 @@ use pumpkin_data::translation;
use pumpkin_inventory::generic_container_screen_handler::create_generic_9x3;
use pumpkin_inventory::player::player_inventory::PlayerInventory;
use pumpkin_inventory::screen_handler::{
BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
};
use pumpkin_macros::pumpkin_block_from_tag;
use pumpkin_util::text::TextComponent;
use pumpkin_world::inventory::Inventory;
use tokio::sync::Mutex;
struct ShulkerBoxScreenFactory(Arc<dyn Inventory>);
impl ScreenHandlerFactory for ShulkerBoxScreenFactory {
fn create_screen_handler<'a>(
&'a self,
fn create_screen_handler(
&self,
sync_id: u8,
player_inventory: &'a Arc<PlayerInventory>,
_player: &'a dyn InventoryPlayer,
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
Box::pin(async move {
let handler = create_generic_9x3(sync_id, player_inventory, self.0.clone()).await;
let screen_handler_arc = Arc::new(Mutex::new(handler));
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let handler = create_generic_9x3(sync_id, player_inventory, self.0.clone());
let screen_handler_arc = Arc::new(Mutex::new(handler));
Some(screen_handler_arc as SharedScreenHandler)
})
Some(screen_handler_arc as SharedScreenHandler)
}
fn get_display_name(&self) -> TextComponent {
@ -79,13 +77,8 @@ impl BlockBehaviour for ShulkerBoxBlock {
pumpkin_data::statistic::CustomStatistic::OpenShulkerBox as i32,
1,
);
let player = Arc::clone(args.player);
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(&ShulkerBoxScreenFactory(inventory), Some(pos))
.await;
});
args.player
.open_handled_screen(&ShulkerBoxScreenFactory(inventory), Some(*args.position));
}
BlockActionResult::Success

View File

@ -4,13 +4,13 @@ use crate::block::{BlockBehaviour, NormalUseArgs};
use pumpkin_data::translation;
use pumpkin_inventory::player::player_inventory::PlayerInventory;
use pumpkin_inventory::screen_handler::{
BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
};
use pumpkin_inventory::smithing_table_screen_handler::SmithingTableScreenHandler;
use pumpkin_macros::pumpkin_block;
use pumpkin_util::text::TextComponent;
use std::sync::Arc;
use tokio::sync::Mutex;
use std::sync::Mutex;
#[pumpkin_block("minecraft:smithing_table")]
pub struct SmithingTableBlock;
@ -22,13 +22,8 @@ impl BlockBehaviour for SmithingTableBlock {
pumpkin_data::statistic::CustomStatistic::InteractWithSmithingTable as i32,
1,
);
let player = Arc::clone(args.player);
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(&SmithingTableScreenFactory, Some(pos))
.await;
});
args.player
.open_handled_screen(&SmithingTableScreenFactory, Some(*args.position));
BlockActionResult::Success
}
@ -37,18 +32,17 @@ impl BlockBehaviour for SmithingTableBlock {
struct SmithingTableScreenFactory;
impl ScreenHandlerFactory for SmithingTableScreenFactory {
fn create_screen_handler<'a>(
&'a self,
fn create_screen_handler(
&self,
sync_id: u8,
player_inventory: &'a Arc<PlayerInventory>,
_player: &'a dyn InventoryPlayer,
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
Box::pin(async move {
let handler: SharedScreenHandler = Arc::new(Mutex::new(
SmithingTableScreenHandler::new(sync_id, player_inventory),
));
Some(handler)
})
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let handler: SharedScreenHandler = Arc::new(Mutex::new(SmithingTableScreenHandler::new(
sync_id,
player_inventory,
)));
Some(handler)
}
fn get_display_name(&self) -> TextComponent {

View File

@ -1,4 +1,5 @@
use std::sync::Arc;
use std::sync::Mutex;
use crate::block::entities::{
PropertyDelegate, furnace_like_block_entity::ExperienceContainer, smoker::SmokerBlockEntity,
@ -12,11 +13,10 @@ use pumpkin_data::{
use pumpkin_inventory::{
furnace_like::furnace_like_screen_handler::FurnaceLikeScreenHandler,
player::player_inventory::PlayerInventory,
screen_handler::{BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler},
screen_handler::{InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler},
};
use pumpkin_macros::pumpkin_block;
use pumpkin_world::inventory::Inventory;
use tokio::sync::Mutex;
use crate::{
block::{
@ -47,27 +47,24 @@ impl SmokerScreenFactory {
}
impl ScreenHandlerFactory for SmokerScreenFactory {
fn create_screen_handler<'a>(
&'a self,
fn create_screen_handler(
&self,
sync_id: u8,
player_inventory: &'a Arc<PlayerInventory>,
_player: &'a dyn InventoryPlayer,
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
Box::pin(async move {
let concrete_handler = FurnaceLikeScreenHandler::new(
sync_id,
player_inventory,
self.inventory.clone(),
self.property_delegate.clone(),
self.experience_container.clone(),
WindowType::Smoker,
)
.await;
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let concrete_handler = FurnaceLikeScreenHandler::new(
sync_id,
player_inventory,
self.inventory.clone(),
&self.property_delegate,
self.experience_container.clone(),
WindowType::Smoker,
);
let concrete_arc = Arc::new(Mutex::new(concrete_handler));
let concrete_arc = Arc::new(Mutex::new(concrete_handler));
Some(concrete_arc as SharedScreenHandler)
})
Some(concrete_arc as SharedScreenHandler)
}
fn get_display_name(&self) -> pumpkin_util::text::TextComponent {
@ -94,13 +91,8 @@ impl BlockBehaviour for SmokerBlock {
);
let smoker_screen_factory =
SmokerScreenFactory::new(inventory, property_delegate, experience_container);
let player = Arc::clone(args.player);
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(&smoker_screen_factory, Some(pos))
.await;
});
args.player
.open_handled_screen(&smoker_screen_factory, Some(*args.position));
}
crate::block::registry::BlockActionResult::Consume
}

View File

@ -4,12 +4,12 @@ use crate::block::{BlockBehaviour, NormalUseArgs};
use pumpkin_data::translation;
use pumpkin_inventory::player::player_inventory::PlayerInventory;
use pumpkin_inventory::screen_handler::{
BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::text::TextComponent;
use std::sync::Arc;
use tokio::sync::Mutex;
use std::sync::Mutex;
use pumpkin_inventory::stonecutter_screen_handler::StonecutterScreenHandler;
@ -23,13 +23,8 @@ impl BlockBehaviour for StonecutterBlock {
pumpkin_data::statistic::CustomStatistic::InteractWithStonecutter as i32,
1,
);
let player = Arc::clone(args.player);
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(&StonecutterScreenFactory, Some(pos))
.await;
});
args.player
.open_handled_screen(&StonecutterScreenFactory, Some(*args.position));
BlockActionResult::Success
}
@ -38,19 +33,17 @@ impl BlockBehaviour for StonecutterBlock {
struct StonecutterScreenFactory;
impl ScreenHandlerFactory for StonecutterScreenFactory {
fn create_screen_handler<'a>(
&'a self,
fn create_screen_handler(
&self,
sync_id: u8,
player_inventory: &'a Arc<PlayerInventory>,
_player: &'a dyn InventoryPlayer,
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
Box::pin(async move {
let handler: SharedScreenHandler = Arc::new(Mutex::new(StonecutterScreenHandler::new(
sync_id,
player_inventory,
)));
Some(handler)
})
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let handler: SharedScreenHandler = Arc::new(Mutex::new(StonecutterScreenHandler::new(
sync_id,
player_inventory,
)));
Some(handler)
}
fn get_display_name(&self) -> TextComponent {

View File

@ -8,6 +8,7 @@ use pumpkin_util::random::xoroshiro128::Xoroshiro;
use pumpkin_util::random::{RandomImpl, get_seed};
use std::any::Any;
use std::pin::Pin;
use std::sync::RwLock;
use std::{
array::from_fn,
sync::{
@ -18,14 +19,13 @@ use std::{
use crate::block::viewer::{ViewerCountListener, ViewerCountTracker, ViewerCountTrackerExt};
use crate::world::{BlockFlags, World};
use pumpkin_world::inventory::InventoryFuture;
use pumpkin_world::inventory::{Clearable, Inventory, sync_write_items_to_nbt};
use super::BlockEntity;
pub struct BarrelBlockEntity {
pub position: BlockPos,
pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub items: RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub dirty: AtomicBool,
// Viewer
@ -47,12 +47,18 @@ impl BlockEntity for BarrelBlockEntity {
{
let mut barrel = Self {
position,
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
dirty: AtomicBool::new(false),
viewers: ViewerCountTracker::new(),
};
pumpkin_world::inventory::sync_read_items_from_nbt(nbt, barrel.items.get_mut());
pumpkin_world::inventory::sync_read_items_from_nbt(
nbt,
barrel
.items
.get_mut()
.unwrap_or_else(std::sync::PoisonError::into_inner),
);
barrel
}
@ -61,7 +67,9 @@ impl BlockEntity for BarrelBlockEntity {
&'a self,
nbt: &'a mut NbtCompound,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
self.write_inventory_nbt(nbt, true)
Box::pin(async move {
self.write_inventory_nbt(nbt, true);
})
}
fn tick(&self, world: &Arc<World>) {
@ -114,7 +122,7 @@ impl BarrelBlockEntity {
pub fn new(position: BlockPos) -> Self {
Self {
position,
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
dirty: AtomicBool::new(false),
viewers: ViewerCountTracker::new(),
}
@ -159,60 +167,61 @@ impl Inventory for BarrelBlockEntity {
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 is_empty(&self) -> bool {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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 get_stack(&self, slot: usize) -> ItemStack {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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(&self, slot: usize) -> ItemStack {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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 remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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 set_stack(&self, slot: usize, stack: ItemStack) {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items[slot] = stack;
self.mark_dirty();
}
fn on_open(&self) -> InventoryFuture<'_, ()> {
Box::pin(async move {
self.viewers.open_container();
})
fn on_open(&self) {
self.viewers.open_container();
}
fn on_close(&self) -> InventoryFuture<'_, ()> {
Box::pin(async move {
self.viewers.close_container();
})
fn on_close(&self) {
self.viewers.close_container();
}
fn mark_dirty(&self) {
@ -225,11 +234,12 @@ impl Inventory for BarrelBlockEntity {
}
impl Clearable for BarrelBlockEntity {
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();
})
fn clear(&self) {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items.fill_with(|| ItemStack::EMPTY.clone());
self.mark_dirty();
}
}

View File

@ -3,19 +3,18 @@ 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 std::sync::{Arc, Mutex};
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};
use pumpkin_world::inventory::{Clearable, Inventory};
pub struct BeaconBlockEntity {
pub position: BlockPos,
@ -100,7 +99,7 @@ impl BeaconBlockEntity {
if levels < 4 {
return false;
}
// Regeneration (10) or tier II upgrade matching primary
// Regeneration (10) or identical to primary
secondary_id == 10 || secondary_id == primary_id
}
@ -120,48 +119,50 @@ impl BeaconBlockEntity {
true
}
/// Replicates Java's `updateBase` logic
fn update_base(&self, world: &Arc<World>) -> i32 {
let mut levels = 0;
pub fn update_base(&self, world: &Arc<World>) -> i32 {
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 {
let mut current_level = 0;
for level in 1..=4 {
let layer_y = y - level;
if layer_y < 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);
let mut layer_valid = true;
for dx in -level..=level {
for dz in -level..=level {
let block_pos = BlockPos::new(x + dx, layer_y, z + dz);
let state = world.get_block_state(&block_pos);
let block = world.get_block(&block_pos);
if !block.has_tag(&pumpkin_data::tag::Block::MINECRAFT_BEACON_BASE_BLOCKS) {
is_ok = false;
layer_valid = false;
break;
}
// Optional: Stricter block type validations can happen here
let _ = state;
}
if !is_ok {
if !layer_valid {
break;
}
}
if !is_ok {
if layer_valid {
current_level = level;
} else {
break;
}
levels = step;
}
levels
current_level
}
/// Replicates Java's `applyEffects` bounding box mapping and duration mapping
fn apply_effects(&self, world: &Arc<World>, levels: i32) {
if levels <= 0 {
return;
}
pub fn apply_effects(&self, world: &Arc<World>, levels: i32) {
let primary_id = self.primary_effect.load(Ordering::Relaxed);
let secondary_id = self.secondary_effect.load(Ordering::Relaxed);
@ -170,29 +171,32 @@ impl BeaconBlockEntity {
}
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 secondary_effect = StatusEffect::from_id(secondary_id as u16);
// Vanilla duration: (9 + levels * 2) * 20 ticks
let duration_ticks = (9 + levels * 2) * 20;
// Base amplifier: primary gets amp 1 (Level II) if secondary matches primary
let base_amp = i32::from(levels >= 4 && primary_id == secondary_id);
for player in players {
// Vanilla Range is level * 10 + 10 blocks in each horizontal direction
let range = f64::from(levels * 10 + 10);
let pos = self.position.0.to_f64();
let box_min = [pos.x - range, pos.y - range, pos.z - range];
let box_max = [
pos.x + range + 1.0,
pos.y + range + 1.0 + 384.0,
pos.z + range + 1.0,
];
let bounds = BoundingBox::new_array(box_min, box_max);
// Apply effect to all players in range
let players = world.players.load();
for player in players.iter() {
if !bounds.intersects(&player.living_entity.entity.bounding_box.load()) {
continue;
}
if let Some(effect) = primary_effect {
player.add_effect(pumpkin_data::potion::Effect {
effect_type: effect,
@ -273,10 +277,18 @@ impl BlockEntity for BeaconBlockEntity {
);
nbt.put_int("Levels", self.levels.load(Ordering::Relaxed));
if let Some(name) = &*self.custom_name.lock().await {
if let Some(name) = &*self
.custom_name
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
{
nbt.put_string("CustomName", name.clone());
}
if let Some(lock) = &*self.lock_key.lock().await {
if let Some(lock) = &*self
.lock_key
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
{
nbt.put_string("Lock", lock.clone());
}
})
@ -288,9 +300,6 @@ impl BlockEntity for BeaconBlockEntity {
let levels = self.update_base(world);
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);
}
@ -324,10 +333,6 @@ impl BlockEntity for BeaconBlockEntity {
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 {
@ -335,57 +340,64 @@ impl Inventory for BeaconBlockEntity {
1
}
fn is_empty(&self) -> InventoryFuture<'_, bool> {
Box::pin(async move { self.payment.lock().await.is_empty() })
fn is_empty(&self) -> bool {
self.payment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.is_empty()
}
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move {
if slot == 0 {
self.payment.lock().await.clone()
} else {
ItemStack::EMPTY.clone()
}
})
fn get_stack(&self, slot: usize) -> ItemStack {
if slot == 0 {
self.payment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
} else {
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(&self, slot: usize) -> ItemStack {
if slot == 0 {
let mut removed = ItemStack::EMPTY.clone();
let mut guard = self
.payment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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 remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack {
if slot == 0 {
let mut stack = self
.payment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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 set_stack(&self, slot: usize, stack: ItemStack) {
if slot == 0 {
*self
.payment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = stack;
self.mark_dirty();
}
}
fn mark_dirty(&self) {
@ -398,11 +410,9 @@ impl Inventory for BeaconBlockEntity {
}
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();
}
})
fn clear(&self) {
if let Ok(mut payment) = self.payment.try_lock() {
*payment = ItemStack::EMPTY.clone();
}
}
}

View File

@ -5,7 +5,7 @@ use std::{
array::from_fn,
collections::HashMap,
sync::{
Arc, Mutex as StdMutex,
Arc, Mutex as StdMutex, RwLock,
atomic::{AtomicBool, AtomicU16, Ordering},
},
};
@ -29,7 +29,7 @@ pub struct BlastingFurnaceBlockEntity {
pub lit_time_remaining: AtomicU16,
pub lit_total_time: AtomicU16,
pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub items: RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
/// Tracks recipes used for XP calculation (vanilla `RecipesUsed` NBT format)
/// Maps result item ID -> craft count
@ -45,7 +45,7 @@ impl BlastingFurnaceBlockEntity {
Self {
position,
dirty: AtomicBool::new(false),
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
cooking_total_time: AtomicU16::new(0),
cooking_time_spent: AtomicU16::new(0),
lit_total_time: AtomicU16::new(0),

View File

@ -2,14 +2,13 @@ use std::any::Any;
use std::future::Future;
use std::pin::Pin;
use std::sync::{
Arc, Mutex as StdMutex,
Arc, Mutex as StdMutex, RwLock,
atomic::AtomicI32,
atomic::{AtomicBool, Ordering},
};
use crate::block::entities::PropertyDelegate;
use pumpkin_data::block_properties::BlockProperties;
use pumpkin_data::data_component_impl::DataComponentImpl;
use pumpkin_data::item::Item;
use pumpkin_data::item_stack::ItemStack;
use pumpkin_data::potion_brewing::{ITEM_RECIPES, POTION_RECIPES};
@ -19,7 +18,6 @@ use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_world::inventory::{Inventory, sync_read_items_from_nbt, sync_write_items_to_nbt};
use tokio::sync::RwLock;
pub struct BrewingStandBlockEntity {
pub position: BlockPos,
@ -66,7 +64,7 @@ impl BrewingStandBlockEntity {
let ingredient_id = ingredient.get_item().id;
// Check potion recipes (water bottle -> potions, potion upgrades, etc.)
let Ok(items) = self.items.try_read() else {
let Ok(items) = self.items.read() else {
return false;
};
for slot_idx in 0..3usize {
@ -102,100 +100,123 @@ impl BrewingStandBlockEntity {
false
}
/// Perform brewing on all valid potion slots
/// Perform the brewing process on all valid potion slots
#[expect(clippy::too_many_lines)]
fn do_brew(&self, world: &Arc<crate::world::World>, ingredient: &ItemStack) {
if ingredient.is_empty() {
return;
}
let ingredient_id = ingredient.get_item().id;
// Apply recipes to each slot
for slot_idx in 0..3usize {
let Ok(items) = self.items.try_read() else {
continue;
// Brew potion slots
let mut ingredient_used = false;
{
let Ok(mut items) = self.items.write() else {
return;
};
let slot = &items[slot_idx];
if slot.is_empty() {
continue;
}
let mut new_stack_opt: Option<ItemStack> = None;
// Try item recipes first (potion -> splash/lingering)
for recipe in &ITEM_RECIPES {
if slot.get_item().id == recipe.from().id
&& recipe.ingredient().iter().any(|i| i.id == ingredient_id)
{
let new_item = recipe.to();
let potion_comp = slot.get_data_component::<pumpkin_data::data_component_impl::PotionContentsImpl>().cloned();
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;
for slot_idx in 0..3usize {
let slot = &mut items[slot_idx];
if slot.is_empty() {
continue;
}
}
// Try potion recipes (modify potion type) if item recipe didn't apply
if new_stack_opt.is_none()
&& let Some(pc) = slot
.get_data_component::<pumpkin_data::data_component_impl::PotionContentsImpl>()
&& let Some(potion_id) = pc.potion_id
{
for recipe in &POTION_RECIPES {
if recipe.from().id as i32 == potion_id
// 1. Try item recipes (e.g. gunpowder -> splash potion, dragon breath -> lingering)
let mut item_brewed = false;
for recipe in &ITEM_RECIPES {
if slot.get_item().id == recipe.from().id
&& recipe.ingredient().iter().any(|i| i.id == ingredient_id)
{
let new_pc = pumpkin_data::data_component_impl::PotionContentsImpl {
potion_id: Some(recipe.to().id as i32),
custom_color: pc.custom_color,
custom_effects: pc.custom_effects.clone(),
custom_name: pc.custom_name.clone(),
};
let new_stack = ItemStack::new_with_component(
slot.item_count,
slot.get_item(),
vec![(
pumpkin_data::data_component::DataComponent::PotionContents,
Some(new_pc.to_dyn()),
)],
);
new_stack_opt = Some(new_stack);
// Preserve potion contents component when converting potion type
let pc = slot.get_data_component::<pumpkin_data::data_component_impl::PotionContentsImpl>().cloned();
*slot = ItemStack::new(1, recipe.to());
if let Some(pc) = pc {
slot.set_data_component(pc);
}
item_brewed = true;
ingredient_used = true;
break;
}
}
if item_brewed {
continue;
}
// 2. Try potion recipes (e.g. water bottle -> awkward potion, awkward -> strength)
if let Some(pc) = slot
.get_data_component::<pumpkin_data::data_component_impl::PotionContentsImpl>()
&& let Some(potion_id) = pc.potion_id
{
for recipe in &POTION_RECIPES {
if recipe.from().id as i32 == potion_id
&& recipe.ingredient().iter().any(|i| i.id == ingredient_id)
{
let new_potion_id = recipe.to().id as i32;
let mut new_pc = pc.clone();
new_pc.potion_id = Some(new_potion_id);
slot.set_data_component(new_pc);
ingredient_used = true;
break;
}
}
}
}
drop(items);
// Update the slot if a recipe was applied
if let Some(new_stack) = new_stack_opt
&& let Ok(mut items) = self.items.try_write()
{
items[slot_idx] = new_stack;
self.mark_dirty();
// Consume one ingredient
if ingredient_used {
items[3].decrement(1);
}
}
if let Some(server) = world.server.upgrade() {
let mut event = crate::plugin::api::events::inventory::brew::BrewEvent::new(
self.position,
self.fuel.load(std::sync::atomic::Ordering::Relaxed) as u8,
);
server.plugin_manager.fire_blocking(&server, &mut event);
if !ingredient_used {
return;
}
// Consume ingredient
if let Ok(mut items) = self.items.try_write() {
items[3].decrement(1);
self.mark_dirty();
// Fire BrewEvent
if let Some(server) = world.server.upgrade() {
let mut brew_event = crate::plugin::api::events::inventory::brew::BrewEvent::new(
self.position,
self.fuel.load(Ordering::Relaxed) as u8,
);
server
.plugin_manager
.fire_blocking(&server, &mut brew_event);
}
// Check if remaining ingredient matches or clear it
if let Ok(items) = self.items.read() {
let remaining = &items[3];
if remaining.is_empty()
|| !self
.ingredient_item
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.is_some_and(|stored| remaining.get_item().id == stored.id)
{
*self
.ingredient_item
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = None;
}
}
// Check if we can immediately start the next brew
if let Ok(items) = self.items.read() {
let ingredient = items[3].clone();
drop(items);
if self.fuel.load(Ordering::Relaxed) > 0 && self.is_brewable(&ingredient) {
self.fuel.fetch_sub(1, Ordering::Relaxed);
self.brew_time.store(400, Ordering::Relaxed);
*self
.ingredient_item
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) =
Some(ingredient.get_item());
} else {
self.brew_time.store(0, Ordering::Relaxed);
}
}
// Play sound at the center of the block
@ -208,9 +229,6 @@ impl BrewingStandBlockEntity {
// Mark dirty to trigger update
self.mark_dirty();
// Handle crafting remainder (like glass bottles from honey bottles)
// TODO: Implement remainder handling when item lookup by ID is available
}
}
@ -219,76 +237,66 @@ impl pumpkin_world::inventory::Inventory for BrewingStandBlockEntity {
Self::INVENTORY_SIZE
}
fn is_empty(&self) -> pumpkin_world::inventory::InventoryFuture<'_, bool> {
Box::pin(async move {
let items = self.items.read().await;
for slot in items.iter() {
if !slot.is_empty() {
return false;
}
fn is_empty(&self) -> bool {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
for slot in items.iter() {
if !slot.is_empty() {
return false;
}
true
})
}
true
}
fn get_stack(&self, slot: usize) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
Box::pin(async move {
let items = self.items.read().await;
items[slot].clone()
})
fn get_stack(&self, slot: usize) -> ItemStack {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items[slot].clone()
}
fn remove_stack(
&self,
slot: usize,
) -> pumpkin_world::inventory::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(&self, slot: usize) -> ItemStack {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone());
self.mark_dirty();
removed
}
fn remove_stack_specific(
&self,
slot: usize,
amount: u8,
) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
Box::pin(async move {
let mut items = self.items.write().await;
let taken = if items[slot].item_count <= amount {
std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone())
} else {
let mut taken = items[slot].clone();
taken.item_count = amount;
items[slot].item_count -= amount;
taken
};
self.mark_dirty();
fn remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let taken = if items[slot].item_count <= amount {
std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone())
} else {
let mut taken = items[slot].clone();
taken.item_count = amount;
items[slot].item_count -= amount;
taken
})
};
self.mark_dirty();
taken
}
fn set_stack(
&self,
slot: usize,
stack: ItemStack,
) -> pumpkin_world::inventory::InventoryFuture<'_, ()> {
Box::pin(async move {
let mut items = self.items.write().await;
items[slot] = stack;
self.mark_dirty();
})
fn set_stack(&self, slot: usize, stack: ItemStack) {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items[slot] = stack;
self.mark_dirty();
}
fn on_open(&self) -> pumpkin_world::inventory::InventoryFuture<'_, ()> {
Box::pin(async move {})
}
fn on_open(&self) {}
fn on_close(&self) -> pumpkin_world::inventory::InventoryFuture<'_, ()> {
Box::pin(async move {})
}
fn on_close(&self) {}
fn mark_dirty(&self) {
self.dirty.store(true, Ordering::Relaxed);
@ -325,12 +333,13 @@ impl pumpkin_world::inventory::Inventory for BrewingStandBlockEntity {
}
impl pumpkin_world::inventory::Clearable for BrewingStandBlockEntity {
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();
})
fn clear(&self) {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items.fill_with(|| ItemStack::EMPTY.clone());
self.mark_dirty();
}
}
@ -358,7 +367,10 @@ impl crate::block::entities::BlockEntity for BrewingStandBlockEntity {
}
// Load inventory items from NBT
let items = entity.items.get_mut();
let items = entity
.items
.get_mut()
.unwrap_or_else(std::sync::PoisonError::into_inner);
sync_read_items_from_nbt(nbt, items);
// If there's an ingredient in slot 3, remember its base item for matching
@ -399,7 +411,7 @@ impl crate::block::entities::BlockEntity for BrewingStandBlockEntity {
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);
})
}

View File

@ -1,4 +1,4 @@
use std::sync::{Arc, Mutex as StdMutex, atomic::AtomicBool};
use std::sync::{Arc, Mutex as StdMutex, RwLock, atomic::AtomicBool};
use pumpkin_data::block_properties::BlockProperties;
use pumpkin_data::item_stack::ItemStack;
@ -11,7 +11,7 @@ use crate::{
pub struct ChestBlockEntity {
pub position: BlockPos,
pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub items: RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub dirty: AtomicBool,
// Viewer

View File

@ -24,7 +24,7 @@ macro_rules! impl_block_entity_for_chest {
let mut chest = Self {
position,
items: tokio::sync::RwLock::new(std::array::from_fn(|_| {
items: std::sync::RwLock::new(std::array::from_fn(|_| {
ItemStack::EMPTY.clone()
})),
dirty: std::sync::atomic::AtomicBool::new(false),
@ -40,7 +40,13 @@ macro_rules! impl_block_entity_for_chest {
.unwrap_or_else(std::sync::PoisonError::into_inner)
.is_some();
if !has_loot_table {
pumpkin_world::inventory::sync_read_items_from_nbt(nbt, chest.items.get_mut());
pumpkin_world::inventory::sync_read_items_from_nbt(
nbt,
chest
.items
.get_mut()
.unwrap_or_else(std::sync::PoisonError::into_inner),
);
}
chest
@ -70,7 +76,7 @@ macro_rules! impl_block_entity_for_chest {
}
} else {
// Loot has already been generated, so persist the actual items.
self.write_inventory_nbt(nbt, true).await;
self.write_inventory_nbt(nbt, true);
}
})
}
@ -145,74 +151,61 @@ macro_rules! impl_inventory_for_chest {
Self::INVENTORY_SIZE
}
fn is_empty(&self) -> pumpkin_world::inventory::InventoryFuture<'_, bool> {
Box::pin(async move {
let items = self.items.read().await;
items.iter().all(|s| s.is_empty())
})
fn is_empty(&self) -> bool {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items.iter().all(|s| s.is_empty())
}
fn get_stack(
&self,
slot: usize,
) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
Box::pin(async move {
let items = self.items.read().await;
items[slot].clone()
})
fn get_stack(&self, slot: usize) -> ItemStack {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items[slot].clone()
}
fn remove_stack(
&self,
slot: usize,
) -> pumpkin_world::inventory::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(&self, slot: usize) -> ItemStack {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone());
self.mark_dirty();
removed
}
fn remove_stack_specific(
&self,
slot: usize,
amount: u8,
) -> pumpkin_world::inventory::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 remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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,
) -> pumpkin_world::inventory::InventoryFuture<'_, ()> {
Box::pin(async move {
let mut items = self.items.write().await;
items[slot] = stack;
self.mark_dirty();
})
fn set_stack(&self, slot: usize, stack: ItemStack) {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items[slot] = stack;
self.mark_dirty();
}
fn on_open(&self) -> pumpkin_world::inventory::InventoryFuture<'_, ()> {
Box::pin(async move {
self.viewers.open_container();
})
fn on_open(&self) {
self.viewers.open_container();
}
fn on_close(&self) -> pumpkin_world::inventory::InventoryFuture<'_, ()> {
Box::pin(async move {
self.viewers.close_container();
})
fn on_close(&self) {
self.viewers.close_container();
}
fn mark_dirty(&self) {
@ -231,14 +224,13 @@ macro_rules! impl_inventory_for_chest {
macro_rules! impl_clearable_for_chest {
($struct_name:ty) => {
impl pumpkin_world::inventory::Clearable for $struct_name {
fn clear(
&self,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + '_>> {
Box::pin(async move {
let mut items = self.items.write().await;
items.fill_with(|| ItemStack::EMPTY.clone());
<$struct_name as pumpkin_world::inventory::Inventory>::mark_dirty(self);
})
fn clear(&self) {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items.fill_with(|| ItemStack::EMPTY.clone());
<$struct_name as pumpkin_world::inventory::Inventory>::mark_dirty(self);
}
}
};
@ -311,11 +303,12 @@ macro_rules! impl_chest_helper_methods {
pub fn new(position: pumpkin_util::math::position::BlockPos) -> Self {
use std::array::from_fn;
use std::sync::Mutex as StdMutex;
use std::sync::RwLock;
use std::sync::atomic::AtomicBool;
Self {
position,
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
dirty: AtomicBool::new(false),
viewers: $crate::block::viewer::ViewerCountTracker::new(),
loot_table: StdMutex::new(None),

View File

@ -6,6 +6,7 @@ use pumpkin_util::math::position::BlockPos;
use std::any::Any;
use std::future::Future;
use std::pin::Pin;
use std::sync::RwLock;
use std::{
array::from_fn,
sync::{
@ -19,12 +20,11 @@ use crate::{
block::entities::BlockEntity,
world::{BlockFlags, World},
};
use pumpkin_world::inventory::InventoryFuture;
use pumpkin_world::inventory::{Clearable, Inventory, sync_write_items_to_nbt};
pub struct ChiseledBookshelfBlockEntity {
pub position: BlockPos,
pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub items: RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub last_interacted_slot: AtomicI8,
pub dirty: AtomicBool,
}
@ -46,11 +46,17 @@ impl BlockEntity for ChiseledBookshelfBlockEntity {
{
let mut bookshelf = Self {
position,
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
last_interacted_slot: AtomicI8::new(-1),
dirty: AtomicBool::new(false),
};
pumpkin_world::inventory::sync_read_items_from_nbt(nbt, bookshelf.items.get_mut());
pumpkin_world::inventory::sync_read_items_from_nbt(
nbt,
bookshelf
.items
.get_mut()
.unwrap_or_else(std::sync::PoisonError::into_inner),
);
if let Some(slot) = nbt.get_int(LAST_INTERACTED_SLOT) {
bookshelf
.last_interacted_slot
@ -65,7 +71,10 @@ impl BlockEntity for ChiseledBookshelfBlockEntity {
nbt: &'a mut NbtCompound,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
let items = self.items.read().await;
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
sync_write_items_to_nbt(items.as_slice(), nbt);
nbt.put_int(
LAST_INTERACTED_SLOT,
@ -99,7 +108,7 @@ impl ChiseledBookshelfBlockEntity {
pub fn new(position: BlockPos) -> Self {
Self {
position,
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
last_interacted_slot: AtomicI8::new(-1),
dirty: AtomicBool::new(false),
}
@ -116,7 +125,11 @@ impl ChiseledBookshelfBlockEntity {
.store(slot as i8, Ordering::Relaxed);
self.mark_dirty();
let occupied = !self.items.blocking_read()[slot].is_empty();
let occupied = !self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner)[slot]
.is_empty();
match slot {
0 => properties.slot_0_occupied = occupied,
1 => properties.slot_1_occupied = occupied,
@ -141,13 +154,19 @@ impl ChiseledBookshelfBlockEntity {
}
pub fn set_book(&self, slot: usize, stack: ItemStack) {
let mut items = self.items.blocking_write();
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items[slot] = stack;
self.mark_dirty();
}
pub fn remove_book(&self, slot: usize, amount: u8) -> ItemStack {
let mut items = self.items.blocking_write();
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let res = if !items[slot].is_empty() && amount > 0 {
items[slot].split(amount)
} else {
@ -163,48 +182,53 @@ impl Inventory for ChiseledBookshelfBlockEntity {
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 is_empty(&self) -> bool {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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 get_stack(&self, slot: usize) -> ItemStack {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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(&self, slot: usize) -> ItemStack {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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 remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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 set_stack(&self, slot: usize, stack: ItemStack) {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items[slot] = stack;
self.mark_dirty();
}
fn mark_dirty(&self) {
@ -217,11 +241,12 @@ impl Inventory for ChiseledBookshelfBlockEntity {
}
impl Clearable for ChiseledBookshelfBlockEntity {
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();
})
fn clear(&self) {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items.fill_with(|| ItemStack::EMPTY.clone());
self.mark_dirty();
}
}

View File

@ -2,16 +2,17 @@ use crate::block::entities::BlockEntity;
use pumpkin_data::item_stack::ItemStack;
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture, sync_write_items_to_nbt};
use pumpkin_world::inventory::{Clearable, Inventory, sync_write_items_to_nbt};
use std::any::Any;
use std::array::from_fn;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::RwLock;
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
pub struct CrafterBlockEntity {
pub position: BlockPos,
pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub items: RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub crafting_ticks_remaining: AtomicI32,
pub triggered: AtomicBool,
pub dirty: AtomicBool,
@ -23,7 +24,10 @@ impl BlockEntity for CrafterBlockEntity {
nbt: &'a mut NbtCompound,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
let items = self.items.read().await;
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
sync_write_items_to_nbt(items.as_slice(), nbt);
nbt.put_int(
"crafting_ticks_remaining",
@ -39,7 +43,7 @@ impl BlockEntity for CrafterBlockEntity {
{
let crafter = Self {
position,
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
crafting_ticks_remaining: AtomicI32::new(
nbt.get_int("crafting_ticks_remaining").unwrap_or(0),
),
@ -97,7 +101,7 @@ impl CrafterBlockEntity {
pub fn new(position: BlockPos) -> Self {
Self {
position,
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
crafting_ticks_remaining: AtomicI32::new(0),
triggered: AtomicBool::new(false),
dirty: AtomicBool::new(false),
@ -110,48 +114,53 @@ impl Inventory for CrafterBlockEntity {
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 is_empty(&self) -> bool {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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 get_stack(&self, slot: usize) -> ItemStack {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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(&self, slot: usize) -> ItemStack {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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 remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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 set_stack(&self, slot: usize, stack: ItemStack) {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items[slot] = stack;
self.mark_dirty();
}
fn mark_dirty(&self) {
@ -164,11 +173,12 @@ impl Inventory for CrafterBlockEntity {
}
impl Clearable for CrafterBlockEntity {
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();
})
fn clear(&self) {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items.fill_with(|| ItemStack::EMPTY.clone());
self.mark_dirty();
}
}

View File

@ -2,17 +2,18 @@ use crate::block::entities::BlockEntity;
use pumpkin_data::item_stack::ItemStack;
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture, sync_write_items_to_nbt};
use pumpkin_world::inventory::{Clearable, Inventory, sync_write_items_to_nbt};
use rand::{RngExt, rng};
use std::any::Any;
use std::array::from_fn;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::RwLock;
use std::sync::atomic::{AtomicBool, Ordering};
pub struct DispenserBlockEntity {
pub position: BlockPos,
pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub items: RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub dirty: AtomicBool,
}
@ -21,7 +22,9 @@ impl BlockEntity for DispenserBlockEntity {
&'a self,
nbt: &'a mut NbtCompound,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
self.write_inventory_nbt(nbt, true)
Box::pin(async move {
self.write_inventory_nbt(nbt, true);
})
}
fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self
@ -30,11 +33,17 @@ impl BlockEntity for DispenserBlockEntity {
{
let mut dispenser = Self {
position,
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
dirty: AtomicBool::new(false),
};
pumpkin_world::inventory::sync_read_items_from_nbt(nbt, dispenser.items.get_mut());
pumpkin_world::inventory::sync_read_items_from_nbt(
nbt,
dispenser
.items
.get_mut()
.unwrap_or_else(std::sync::PoisonError::into_inner),
);
dispenser
}
@ -80,13 +89,16 @@ impl DispenserBlockEntity {
pub fn new(position: BlockPos) -> Self {
Self {
position,
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
dirty: AtomicBool::new(false),
}
}
pub async fn get_random_slot(&self) -> Option<(usize, ItemStack)> {
let items = self.items.read().await;
pub fn get_random_slot(&self) -> Option<(usize, ItemStack)> {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let mut non_empty = Vec::new();
for (i, stack) in items.iter().enumerate() {
if !stack.is_empty() {
@ -107,48 +119,53 @@ impl Inventory for DispenserBlockEntity {
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 is_empty(&self) -> bool {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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 get_stack(&self, slot: usize) -> ItemStack {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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(&self, slot: usize) -> ItemStack {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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 remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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 set_stack(&self, slot: usize, stack: ItemStack) {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items[slot] = stack;
self.mark_dirty();
}
fn mark_dirty(&self) {
@ -161,11 +178,12 @@ impl Inventory for DispenserBlockEntity {
}
impl Clearable for DispenserBlockEntity {
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();
})
fn clear(&self) {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items.fill_with(|| ItemStack::EMPTY.clone());
self.mark_dirty();
}
}

View File

@ -2,17 +2,18 @@ use crate::block::entities::BlockEntity;
use pumpkin_data::item_stack::ItemStack;
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture, sync_write_items_to_nbt};
use pumpkin_world::inventory::{Clearable, Inventory, sync_write_items_to_nbt};
use rand::{RngExt, rng};
use std::any::Any;
use std::array::from_fn;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::RwLock;
use std::sync::atomic::{AtomicBool, Ordering};
pub struct DropperBlockEntity {
pub position: BlockPos,
pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub items: RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub dirty: AtomicBool,
}
@ -21,7 +22,9 @@ impl BlockEntity for DropperBlockEntity {
&'a self,
nbt: &'a mut NbtCompound,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
self.write_inventory_nbt(nbt, true)
Box::pin(async move {
self.write_inventory_nbt(nbt, true);
})
}
fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self
@ -30,11 +33,17 @@ impl BlockEntity for DropperBlockEntity {
{
let mut dropper = Self {
position,
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
dirty: AtomicBool::new(false),
};
pumpkin_world::inventory::sync_read_items_from_nbt(nbt, dropper.items.get_mut());
pumpkin_world::inventory::sync_read_items_from_nbt(
nbt,
dropper
.items
.get_mut()
.unwrap_or_else(std::sync::PoisonError::into_inner),
);
dropper
}
@ -80,13 +89,16 @@ impl DropperBlockEntity {
pub fn new(position: BlockPos) -> Self {
Self {
position,
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
dirty: AtomicBool::new(false),
}
}
pub async fn get_random_slot(&self) -> Option<(usize, ItemStack)> {
let items = self.items.read().await;
pub fn get_random_slot(&self) -> Option<(usize, ItemStack)> {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let mut non_empty = Vec::new();
for (i, stack) in items.iter().enumerate() {
if !stack.is_empty() {
@ -107,48 +119,53 @@ impl Inventory for DropperBlockEntity {
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 is_empty(&self) -> bool {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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 get_stack(&self, slot: usize) -> ItemStack {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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(&self, slot: usize) -> ItemStack {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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 remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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 set_stack(&self, slot: usize, stack: ItemStack) {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items[slot] = stack;
self.mark_dirty();
}
fn mark_dirty(&self) {
@ -161,11 +178,12 @@ impl Inventory for DropperBlockEntity {
}
impl Clearable for DropperBlockEntity {
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();
})
fn clear(&self) {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items.fill_with(|| ItemStack::EMPTY.clone());
self.mark_dirty();
}
}

View File

@ -5,7 +5,7 @@ use std::{
array::from_fn,
collections::HashMap,
sync::{
Arc, Mutex as StdMutex,
Arc, Mutex as StdMutex, RwLock,
atomic::{AtomicBool, AtomicU16, Ordering},
},
};
@ -29,7 +29,7 @@ pub struct FurnaceBlockEntity {
pub lit_time_remaining: AtomicU16,
pub lit_total_time: AtomicU16,
pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub items: RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
/// Tracks recipes used for XP calculation (vanilla `RecipesUsed` NBT format)
/// Maps result item ID -> craft count
@ -45,7 +45,7 @@ impl FurnaceBlockEntity {
Self {
position,
dirty: AtomicBool::new(false),
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
cooking_total_time: AtomicU16::new(0),
cooking_time_spent: AtomicU16::new(0),
lit_total_time: AtomicU16::new(0),

View File

@ -20,9 +20,9 @@ pub trait CookingBlockEntityBase:
/// Calculates XP from tracked recipes and clears the `recipes_used` map
fn extract_experience_from_recipes(&self) -> i32;
fn get_input_item(&self) -> impl std::future::Future<Output = ItemStack>;
fn get_fuel_item(&self) -> impl std::future::Future<Output = ItemStack>;
fn get_output_item(&self) -> impl std::future::Future<Output = ItemStack>;
fn get_input_item(&self) -> ItemStack;
fn get_fuel_item(&self) -> ItemStack;
fn get_output_item(&self) -> ItemStack;
fn set_cooking_time_spent(&self, spent_time: u16);
fn set_cooking_total_time(&self, total_time: u16);
@ -54,25 +54,28 @@ macro_rules! impl_cooking_block_entity_base {
self.lit_total_time.load(Ordering::Relaxed)
}
fn get_input_item(&self) -> impl std::future::Future<Output = ItemStack> {
async move {
let items = self.items.read().await;
items[0].clone()
}
fn get_input_item(&self) -> ItemStack {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items[0].clone()
}
fn get_fuel_item(&self) -> impl std::future::Future<Output = ItemStack> {
async move {
let items = self.items.read().await;
items[1].clone()
}
fn get_fuel_item(&self) -> ItemStack {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items[1].clone()
}
fn get_output_item(&self) -> impl std::future::Future<Output = ItemStack> {
async move {
let items = self.items.read().await;
items[2].clone()
}
fn get_output_item(&self) -> ItemStack {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items[2].clone()
}
fn set_cooking_time_spent(&self, spent_time: u16) {
@ -237,12 +240,13 @@ macro_rules! impl_property_delegate_for_cooking {
macro_rules! impl_clearable_for_cooking {
($struct_name:ty) => {
impl pumpkin_world::inventory::Clearable for $struct_name {
fn clear(&self) -> std::pin::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();
})
fn clear(&self) {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items.fill_with(|| ItemStack::EMPTY.clone());
self.mark_dirty();
}
}
};
@ -270,81 +274,70 @@ macro_rules! impl_inventory_for_cooking {
Self::INVENTORY_SIZE
}
fn is_empty(&self) -> pumpkin_world::inventory::InventoryFuture<'_, bool> {
Box::pin(async move {
let items = self.items.read().await;
items.iter().all(|s| s.is_empty())
})
fn is_empty(&self) -> bool {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items.iter().all(|s| s.is_empty())
}
fn get_stack(
&self,
slot: usize,
) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
Box::pin(async move {
let items = self.items.read().await;
items[slot].clone()
})
fn get_stack(&self, slot: usize) -> ItemStack {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items[slot].clone()
}
fn remove_stack(
&self,
slot: usize,
) -> pumpkin_world::inventory::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(&self, slot: usize) -> ItemStack {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone());
self.mark_dirty();
removed
}
fn remove_stack_specific(
&self,
slot: usize,
amount: u8,
) -> pumpkin_world::inventory::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)
fn remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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) {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let is_same_item = !stack.is_empty()
&& ItemStack::are_items_and_components_equal(&items[slot], &stack);
items[slot] = stack.clone();
if slot == 0 && !is_same_item {
if let Some(recipe) = pumpkin_data::recipes::get_cooking_recipe_with_ingredient(
stack.item,
CookingRecipeKind::Smelting,
) {
self.set_cooking_total_time(recipe.cookingtime as u16);
} else {
ItemStack::EMPTY.clone()
};
self.mark_dirty();
res
})
}
fn set_stack(
&self,
slot: usize,
stack: ItemStack,
) -> pumpkin_world::inventory::InventoryFuture<'_, ()> {
Box::pin(async move {
let mut items = self.items.write().await;
let is_same_item = !stack.is_empty()
&& ItemStack::are_items_and_components_equal(&items[slot], &stack);
items[slot] = stack.clone();
if slot == 0 && !is_same_item {
if let Some(recipe) =
pumpkin_data::recipes::get_cooking_recipe_with_ingredient(
stack.item,
CookingRecipeKind::Smelting,
)
{
self.set_cooking_total_time(recipe.cookingtime as u16);
} else {
self.set_cooking_total_time(0);
}
self.set_cooking_time_spent(0);
self.set_cooking_total_time(0);
}
self.set_cooking_time_spent(0);
}
// Always consider the inventory changed when setting a stack
self.mark_dirty();
})
// Always consider the inventory changed when setting a stack
self.mark_dirty();
}
fn mark_dirty(&self) {
@ -485,7 +478,6 @@ macro_rules! impl_block_entity_for_cooking {
if is_burning != self.is_burning() {
is_dirty = true;
let (furnace_block, furnace_block_state) =
world.get_block_and_state(&self.position);
let mut props =
@ -548,14 +540,14 @@ macro_rules! impl_block_entity_for_cooking {
let mut furnace = Self {
position,
dirty: AtomicBool::new(false),
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
items: std::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
cooking_total_time,
cooking_time_spent,
lit_total_time,
lit_time_remaining,
recipes_used: std::sync::Mutex::new(recipes_used_map),
};
pumpkin_world::inventory::sync_read_items_from_nbt(nbt, furnace.items.get_mut());
pumpkin_world::inventory::sync_read_items_from_nbt(nbt, furnace.items.get_mut().unwrap_or_else(std::sync::PoisonError::into_inner));
furnace
}
@ -571,7 +563,6 @@ macro_rules! impl_block_entity_for_cooking {
nbt.put_short("lit_time_remaining", self.get_lit_time_remaining() as i16);
// Save RecipesUsed in vanilla format (map of recipe ID -> craft count)
// Scope the mutex guard so it's dropped before the await
{
let recipes = self
.recipes_used
@ -592,7 +583,7 @@ macro_rules! impl_block_entity_for_cooking {
}
}
self.write_inventory_nbt(nbt, true).await;
self.write_inventory_nbt(nbt, true);
})
}

View File

@ -10,17 +10,18 @@ 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::inventory::{Clearable, Inventory, InventoryFuture, sync_write_items_to_nbt};
use pumpkin_world::inventory::{Clearable, Inventory, sync_write_items_to_nbt};
use std::any::Any;
use std::array::from_fn;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::RwLock;
use std::sync::atomic::Ordering;
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicI64};
pub struct HopperBlockEntity {
pub position: BlockPos,
pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub items: RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub dirty: AtomicBool,
pub facing: FacingHopper,
pub cooldown_time: AtomicI32,
@ -49,7 +50,7 @@ impl BlockEntity for HopperBlockEntity {
"TransferCooldown",
NbtTag::Int(self.cooldown_time.load(Ordering::Relaxed)),
);
self.write_inventory_nbt(nbt, true).await;
self.write_inventory_nbt(nbt, true);
})
}
@ -59,14 +60,20 @@ impl BlockEntity for HopperBlockEntity {
{
let mut hopper = Self {
position,
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
dirty: AtomicBool::new(false),
facing: FacingHopper::Down,
cooldown_time: AtomicI32::from(nbt.get_int("TransferCooldown").unwrap_or(-1)),
ticked_game_time: AtomicI64::new(0),
};
pumpkin_world::inventory::sync_read_items_from_nbt(nbt, hopper.items.get_mut());
pumpkin_world::inventory::sync_read_items_from_nbt(
nbt,
hopper
.items
.get_mut()
.unwrap_or_else(std::sync::PoisonError::into_inner),
);
hopper
}
@ -143,7 +150,7 @@ impl HopperBlockEntity {
pub fn new(position: BlockPos, facing: FacingHopper) -> Self {
Self {
position,
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
dirty: AtomicBool::new(false),
facing,
cooldown_time: AtomicI32::new(-1),
@ -152,12 +159,12 @@ impl HopperBlockEntity {
}
async fn try_move_items(&self, state: &HopperLikeProperties, world: &Arc<World>) {
if self.cooldown_time.load(Ordering::Relaxed) <= 0 && state.enabled {
let mut success = if self.is_empty().await {
let mut success = if self.is_empty() {
false
} else {
self.eject_items(world).await
};
if !self.inventory_full().await {
if !self.inventory_full() {
success |= self.suck_in_items(world).await;
}
if success {
@ -167,8 +174,11 @@ impl HopperBlockEntity {
}
}
async fn inventory_full(&self) -> bool {
let items = self.items.read().await;
fn inventory_full(&self) -> bool {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
for item in items.iter() {
if item.is_empty() || item.item_count != item.get_max_stack_size() {
return false;
@ -196,13 +206,13 @@ impl HopperBlockEntity {
{
// TODO check WorldlyContainer
for i in 0..container.size() {
let mut item = container.get_stack(i).await;
let mut item = container.get_stack(i);
if !item.is_empty() && container.can_transfer_to(self, i, &item) {
//TODO WorldlyContainer
let _backup = item.clone();
let one_item = item.split(1);
if Self::add_one_item(container.as_ref(), self, one_item).await {
container.set_stack(i, item).await;
if Self::add_one_item(container.as_ref(), self, &one_item) {
container.set_stack(i, item);
// If extracting from furnace output slot (index 2), drop XP as orbs
let furnace_output_slot: usize = 2;
if i == furnace_output_slot
@ -264,7 +274,7 @@ impl HopperBlockEntity {
let is_empty = stack.is_empty();
(backup, one_item, is_empty)
};
if Self::add_one_item(self, self, one_item).await {
if Self::add_one_item(self, self, &one_item) {
if is_empty {
item_entity.get_entity().remove();
}
@ -291,7 +301,7 @@ impl HopperBlockEntity {
// TODO check WorldlyContainer
let mut is_full = true;
for i in 0..container.size() {
let item = container.get_stack(i).await;
let item = container.get_stack(i);
if item.item_count < item.get_max_stack_size() {
is_full = false;
break;
@ -301,8 +311,12 @@ impl HopperBlockEntity {
return false;
}
let target_pos = self.position.offset(to_offset(&self.facing));
let items = self.items.read().await;
for item in items.iter() {
let items: [ItemStack; 5] = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
for item in &items {
if !item.is_empty() {
let mut move_event = crate::plugin::api::events::inventory::inventory_move_item::InventoryMoveItemEvent::new(
self.position,
@ -318,7 +332,7 @@ impl HopperBlockEntity {
}
let mut item_clone = item.clone();
let one_item = item_clone.split(1);
if Self::add_one_item(self, container.as_ref(), one_item).await {
if Self::add_one_item(self, container.as_ref(), &one_item) {
return true;
}
}
@ -326,20 +340,20 @@ impl HopperBlockEntity {
}
false
}
pub async fn add_one_item(from: &dyn Inventory, to: &dyn Inventory, item: ItemStack) -> bool {
pub fn add_one_item(from: &dyn Inventory, to: &dyn Inventory, item: &ItemStack) -> bool {
let mut success = false;
let to_empty = to.is_empty().await;
let to_empty = to.is_empty();
for j in 0..to.size() {
if to.is_valid_slot_for(j, &item) {
let mut dst = to.get_stack(j).await;
if to.is_valid_slot_for(j, item) {
let mut dst = to.get_stack(j);
if dst.is_empty() {
dst = item.clone();
to.set_stack(j, dst).await;
to.set_stack(j, dst);
success = true;
} else if dst.item_count < dst.get_max_stack_size() && dst.item == item.item {
// TODO check Components equal
dst.item_count += 1;
to.set_stack(j, dst).await;
to.set_stack(j, dst);
success = true;
}
if success {
@ -373,48 +387,53 @@ impl Inventory for HopperBlockEntity {
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 is_empty(&self) -> bool {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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 get_stack(&self, slot: usize) -> ItemStack {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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(&self, slot: usize) -> ItemStack {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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 remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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 set_stack(&self, slot: usize, stack: ItemStack) {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items[slot] = stack;
self.mark_dirty();
}
fn mark_dirty(&self) {
@ -427,11 +446,12 @@ impl Inventory for HopperBlockEntity {
}
impl Clearable for HopperBlockEntity {
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();
})
fn clear(&self) {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items.fill_with(|| ItemStack::EMPTY.clone());
self.mark_dirty();
}
}

View File

@ -1,18 +1,17 @@
use std::any::Any;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use pumpkin_data::item_stack::ItemStack;
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_nbt::tag::NbtTag;
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};
use pumpkin_world::inventory::{Clearable, Inventory};
/// Matches vanilla's `JukeboxBlockEntity`
pub struct JukeboxBlockEntity {
@ -64,7 +63,10 @@ impl BlockEntity for JukeboxBlockEntity {
nbt: &'a mut NbtCompound,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
let record = self.record_stack.lock().await;
let record = self
.record_stack
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if !record.is_empty() {
let mut record_nbt = NbtCompound::new();
record.write_item_stack(&mut record_nbt);
@ -139,20 +141,29 @@ impl JukeboxBlockEntity {
/// Get the current record stack
pub fn get_record(&self) -> ItemStack {
self.record_stack.blocking_lock().clone()
self.record_stack
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
}
/// Set the record stack - matches vanilla's `setStack()`
/// Note: The caller is responsible for updating block state and playing music
pub fn set_record(&self, stack: ItemStack) {
*self.record_stack.blocking_lock() = stack;
*self
.record_stack
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = stack;
self.mark_dirty();
}
/// Clear the stack and return what was there - used for dropping
pub fn clear_record(&self) -> ItemStack {
self.stop_playing();
let mut record = self.record_stack.blocking_lock();
let mut record = self
.record_stack
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let taken = record.clone();
*record = ItemStack::EMPTY.clone();
self.mark_dirty();
@ -195,35 +206,43 @@ impl Inventory for JukeboxBlockEntity {
1
}
fn is_empty(&self) -> InventoryFuture<'_, bool> {
Box::pin(async move { self.record_stack.lock().await.is_empty() })
fn is_empty(&self) -> bool {
self.record_stack
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.is_empty()
}
fn get_stack(&self, _slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { self.record_stack.lock().await.clone() })
fn get_stack(&self, _slot: usize) -> ItemStack {
self.record_stack
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
}
fn remove_stack(&self, _slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move {
self.stop_playing();
let mut record = self.record_stack.lock().await;
let taken = record.clone();
*record = ItemStack::EMPTY.clone();
self.mark_dirty();
taken
})
fn remove_stack(&self, _slot: usize) -> ItemStack {
self.stop_playing();
let mut record = self
.record_stack
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let taken = record.clone();
*record = ItemStack::EMPTY.clone();
self.mark_dirty();
taken
}
fn remove_stack_specific(&self, _slot: usize, _amount: u8) -> InventoryFuture<'_, ItemStack> {
fn remove_stack_specific(&self, _slot: usize, _amount: u8) -> ItemStack {
// Jukebox only holds one item, so remove the whole stack
self.remove_stack(0)
}
fn set_stack(&self, _slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
Box::pin(async move {
*self.record_stack.lock().await = stack;
self.mark_dirty();
})
fn set_stack(&self, _slot: usize, stack: ItemStack) {
*self
.record_stack
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = stack;
self.mark_dirty();
}
fn mark_dirty(&self) {
@ -236,11 +255,12 @@ impl Inventory for JukeboxBlockEntity {
}
impl Clearable for JukeboxBlockEntity {
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move {
self.stop_playing();
*self.record_stack.lock().await = ItemStack::EMPTY.clone();
self.mark_dirty();
})
fn clear(&self) {
self.stop_playing();
*self
.record_stack
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = ItemStack::EMPTY.clone();
self.mark_dirty();
}
}

View File

@ -8,14 +8,13 @@ use std::{
future::Future,
pin::Pin,
sync::{
Arc,
Arc, Mutex,
atomic::{AtomicBool, AtomicUsize, Ordering},
},
};
use tokio::sync::Mutex;
use crate::block::entities::BlockEntity;
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture};
use pumpkin_world::inventory::{Clearable, Inventory};
pub struct LecternBlockEntity {
pub position: BlockPos,
@ -62,7 +61,10 @@ impl BlockEntity for LecternBlockEntity {
nbt: &'a mut NbtCompound,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
let book = self.book.lock().await;
let book = self
.book
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if !book.is_empty() {
let mut book_nbt = NbtCompound::default();
book.write_item_stack(&mut book_nbt);
@ -129,15 +131,23 @@ impl LecternBlockEntity {
.map_or(0, |pages| pages as i32)
}
pub async fn page_count(&self) -> i32 {
Self::page_count_of(&*self.book.lock().await)
pub fn page_count(&self) -> i32 {
Self::page_count_of(
&self
.book
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner),
)
}
/// Vanilla comparator output: `floor(page / (page_count - 1) * 14) + 1`,
/// or `0` without a book. Single-page books emit `1` (`0 / 0` is `NaN`,
/// which vanilla's `MathHelper.floor` turns into `0`).
pub async fn comparator_output(&self) -> u8 {
let book = self.book.lock().await;
pub fn comparator_output(&self) -> u8 {
let book = self
.book
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if book.is_empty() {
return 0;
}
@ -155,43 +165,52 @@ impl Inventory for LecternBlockEntity {
1
}
fn is_empty(&self) -> InventoryFuture<'_, bool> {
Box::pin(async move { self.book.lock().await.is_empty() })
fn is_empty(&self) -> bool {
self.book
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.is_empty()
}
fn get_stack(&self, _slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { self.book.lock().await.clone() })
fn get_stack(&self, _slot: usize) -> ItemStack {
self.book
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
}
fn remove_stack(&self, _slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move {
let mut removed = ItemStack::EMPTY.clone();
let mut guard = self.book.lock().await;
std::mem::swap(&mut removed, &mut *guard);
self.mark_dirty();
removed
})
fn remove_stack(&self, _slot: usize) -> ItemStack {
let mut removed = ItemStack::EMPTY.clone();
let mut guard = self
.book
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
std::mem::swap(&mut removed, &mut *guard);
self.mark_dirty();
removed
}
fn remove_stack_specific(&self, _slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move {
let mut stack = self.book.lock().await;
if stack.is_empty() {
return ItemStack::EMPTY.clone();
}
let res = stack.split(amount);
self.mark_dirty();
res
})
fn remove_stack_specific(&self, _slot: usize, amount: u8) -> ItemStack {
let mut stack = self
.book
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if stack.is_empty() {
return ItemStack::EMPTY.clone();
}
let res = stack.split(amount);
self.mark_dirty();
res
}
fn set_stack(&self, _slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
Box::pin(async move {
*self.book.lock().await = stack;
// A freshly placed book always opens on its first page.
self.page.store(0, Ordering::Relaxed);
self.mark_dirty();
})
fn set_stack(&self, _slot: usize, stack: ItemStack) {
*self
.book
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = stack;
// A freshly placed book always opens on its first page.
self.page.store(0, Ordering::Relaxed);
self.mark_dirty();
}
fn mark_dirty(&self) {
@ -204,10 +223,11 @@ impl Inventory for LecternBlockEntity {
}
impl Clearable for LecternBlockEntity {
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move {
*self.book.lock().await = ItemStack::EMPTY.clone();
self.mark_dirty();
})
fn clear(&self) {
*self
.book
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = ItemStack::EMPTY.clone();
self.mark_dirty();
}
}

View File

@ -144,8 +144,7 @@ pub trait BlockEntity: Any + Send + Sync {
{
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;
world.scatter_inventory(&position, &inventory);
}
})
}
@ -460,9 +459,7 @@ mod test {
async fn furnace_contents_survive_a_chunk_round_trip() {
let position = BlockPos::new(0, 100, 0);
let furnace = Arc::new(FurnaceBlockEntity::new(position));
furnace
.set_stack(0, ItemStack::new(5, &Item::DIAMOND))
.await;
furnace.set_stack(0, ItemStack::new(5, &Item::DIAMOND));
let mut nbt = NbtCompound::new();
furnace.write_internal(&mut nbt).await;
@ -474,7 +471,7 @@ mod test {
);
if let Some(inventory) = inventory {
let stack = inventory.get_stack(0).await;
let stack = inventory.get_stack(0);
assert_eq!(stack.get_item().id, Item::DIAMOND.id);
assert_eq!(stack.item_count, 5);
}

View File

@ -2,16 +2,17 @@ use crate::block::entities::BlockEntity;
use pumpkin_data::item_stack::ItemStack;
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture, sync_write_items_to_nbt};
use pumpkin_world::inventory::{Clearable, Inventory, sync_write_items_to_nbt};
use std::any::Any;
use std::array::from_fn;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::RwLock;
use std::sync::atomic::{AtomicBool, Ordering};
pub struct ShelfBlockEntity {
pub position: BlockPos,
pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub items: RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub dirty: AtomicBool,
}
@ -20,7 +21,9 @@ impl BlockEntity for ShelfBlockEntity {
&'a self,
nbt: &'a mut NbtCompound,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
self.write_inventory_nbt(nbt, true)
Box::pin(async move {
self.write_inventory_nbt(nbt, true);
})
}
fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self
@ -29,11 +32,17 @@ impl BlockEntity for ShelfBlockEntity {
{
let mut shelf = Self {
position,
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
dirty: AtomicBool::new(false),
};
pumpkin_world::inventory::sync_read_items_from_nbt(nbt, shelf.items.get_mut());
pumpkin_world::inventory::sync_read_items_from_nbt(
nbt,
shelf
.items
.get_mut()
.unwrap_or_else(std::sync::PoisonError::into_inner),
);
shelf
}
@ -79,7 +88,7 @@ impl ShelfBlockEntity {
pub fn new(position: BlockPos) -> Self {
Self {
position,
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
dirty: AtomicBool::new(false),
}
}
@ -90,48 +99,53 @@ impl Inventory for ShelfBlockEntity {
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 is_empty(&self) -> bool {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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 get_stack(&self, slot: usize) -> ItemStack {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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(&self, slot: usize) -> ItemStack {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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 remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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 set_stack(&self, slot: usize, stack: ItemStack) {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items[slot] = stack;
self.mark_dirty();
}
fn mark_dirty(&self) {
@ -144,11 +158,12 @@ impl Inventory for ShelfBlockEntity {
}
impl Clearable for ShelfBlockEntity {
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();
})
fn clear(&self) {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items.fill_with(|| ItemStack::EMPTY.clone());
self.mark_dirty();
}
}

View File

@ -5,14 +5,14 @@ use pumpkin_util::math::position::BlockPos;
use std::any::Any;
use std::future::Future;
use std::pin::Pin;
use std::sync::RwLock;
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};
use crate::world::World;
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture, sync_write_items_to_nbt};
use pumpkin_world::inventory::{Clearable, Inventory, sync_write_items_to_nbt};
pub struct ShulkerBoxBlockEntity {
pub position: BlockPos,
@ -43,7 +43,13 @@ impl BlockEntity for ShulkerBoxBlockEntity {
viewers: ViewerCountTracker::new(),
};
pumpkin_world::inventory::sync_read_items_from_nbt(nbt, shulker_box.items.get_mut());
pumpkin_world::inventory::sync_read_items_from_nbt(
nbt,
shulker_box
.items
.get_mut()
.unwrap_or_else(std::sync::PoisonError::into_inner),
);
shulker_box
}
@ -52,7 +58,9 @@ impl BlockEntity for ShulkerBoxBlockEntity {
&'a self,
nbt: &'a mut NbtCompound,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
self.write_inventory_nbt(nbt, true)
Box::pin(async move {
self.write_inventory_nbt(nbt, true);
})
}
fn tick(&self, world: &Arc<World>) {
@ -150,60 +158,61 @@ impl Inventory for ShulkerBoxBlockEntity {
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 is_empty(&self) -> bool {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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 get_stack(&self, slot: usize) -> ItemStack {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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(&self, slot: usize) -> ItemStack {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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 remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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 set_stack(&self, slot: usize, stack: ItemStack) {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items[slot] = stack;
self.mark_dirty();
}
fn on_open(&self) -> InventoryFuture<'_, ()> {
Box::pin(async move {
self.viewers.open_container();
})
fn on_open(&self) {
self.viewers.open_container();
}
fn on_close(&self) -> InventoryFuture<'_, ()> {
Box::pin(async move {
self.viewers.close_container();
})
fn on_close(&self) {
self.viewers.close_container();
}
fn mark_dirty(&self) {
@ -216,11 +225,12 @@ impl Inventory for ShulkerBoxBlockEntity {
}
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();
})
fn clear(&self) {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items.fill_with(|| ItemStack::EMPTY.clone());
self.mark_dirty();
}
}

View File

@ -5,7 +5,7 @@ use std::{
array::from_fn,
collections::HashMap,
sync::{
Arc, Mutex as StdMutex,
Arc, Mutex as StdMutex, RwLock,
atomic::{AtomicBool, AtomicU16, Ordering},
},
};
@ -29,7 +29,7 @@ pub struct SmokerBlockEntity {
pub lit_time_remaining: AtomicU16,
pub lit_total_time: AtomicU16,
pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub items: RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
/// Tracks recipes used for XP calculation (vanilla `RecipesUsed` NBT format)
/// Maps result item ID -> craft count
@ -45,7 +45,7 @@ impl SmokerBlockEntity {
Self {
position,
dirty: AtomicBool::new(false),
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
cooking_total_time: AtomicU16::new(0),
cooking_time_spent: AtomicU16::new(0),
lit_total_time: AtomicU16::new(0),

View File

@ -1,4 +1,4 @@
use std::sync::{Arc, Mutex as StdMutex, atomic::AtomicBool};
use std::sync::{Arc, Mutex as StdMutex, RwLock, atomic::AtomicBool};
use pumpkin_data::block_properties::BlockProperties;
use pumpkin_data::item_stack::ItemStack;
@ -11,7 +11,7 @@ use crate::{
pub struct TrappedChestBlockEntity {
pub position: BlockPos,
pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub items: RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
pub dirty: AtomicBool,
// Viewer

View File

@ -493,7 +493,7 @@ pub fn calculate_comparator_output(inventory: &dyn pumpkin_world::inventory::Inv
let mut fill_sum = 0.0;
let mut non_empty_count = 0;
for i in 0..size {
let stack = futures::executor::block_on(inventory.get_stack(i));
let stack = inventory.get_stack(i);
if !stack.is_empty() {
let max_stack = stack.get_max_stack_size() as f32;
let count = stack.item_count as f32;

View File

@ -98,7 +98,7 @@ impl CommandExecutor for BlockReplaceExecutor {
let mut item_stack = parsed_stack.clone();
item_stack.item_count = count as u8;
futures::executor::block_on(inventory.set_stack(slot, item_stack));
inventory.set_stack(slot, item_stack);
let msg = TextComponent::translate_cross(
translation::java::COMMANDS_ITEM_BLOCK_SET_SUCCESS,
@ -151,11 +151,9 @@ impl CommandExecutor for EntityReplaceExecutor {
if (200..=226).contains(&mojang_slot) {
let ender_slot = mojang_slot - 200;
if ender_slot < player.ender_chest_inventory.size() {
futures::executor::block_on(
player_arc
.ender_chest_inventory
.set_stack(ender_slot, item_stack.clone()),
);
player_arc
.ender_chest_inventory
.set_stack(ender_slot, item_stack.clone());
modified_count += 1;
}
} else {
@ -181,16 +179,12 @@ impl CommandExecutor for EntityReplaceExecutor {
if let Some(slot) = mapped_slot
&& slot < inventory.size()
{
futures::executor::block_on(
player_arc.inventory().set_stack(slot, item_stack.clone()),
);
player_arc.inventory().set_stack(slot, item_stack.clone());
let stack_serializer = ItemStackSerializer::from(item_stack.clone());
let packet =
CSetContainerSlot::new(0, 0, slot as i16, &stack_serializer);
futures::executor::block_on(
player_arc.enqueue_slot_packet(&packet, None, 0),
);
player_arc.enqueue_slot_packet(&packet, None, 0);
let eq_slot = if slot == 36 {
Some(EquipmentSlot::FEET)

View File

@ -50,7 +50,7 @@ struct LootExecutor {
source: Source,
}
async fn insert_into_inventory(
fn insert_into_inventory(
inventory: &dyn pumpkin_world::inventory::Inventory,
mut stack: pumpkin_data::item_stack::ItemStack,
) -> pumpkin_data::item_stack::ItemStack {
@ -58,7 +58,7 @@ async fn insert_into_inventory(
if stack.is_empty() {
break;
}
let mut slot_stack = inventory.get_stack(i).await;
let mut slot_stack = inventory.get_stack(i);
if !slot_stack.is_empty() && slot_stack.get_item().id == stack.get_item().id {
let max_stack_size = 64;
let space = max_stack_size - slot_stack.item_count;
@ -66,7 +66,7 @@ async fn insert_into_inventory(
let to_add = stack.item_count.min(space);
slot_stack.item_count += to_add;
stack.item_count -= to_add;
inventory.set_stack(i, slot_stack).await;
inventory.set_stack(i, slot_stack);
}
}
}
@ -75,9 +75,9 @@ async fn insert_into_inventory(
if stack.is_empty() {
break;
}
let slot_stack = inventory.get_stack(i).await;
let slot_stack = inventory.get_stack(i);
if slot_stack.is_empty() {
inventory.set_stack(i, stack.clone()).await;
inventory.set_stack(i, stack.clone());
stack.item_count = 0;
break;
}
@ -194,10 +194,7 @@ impl CommandExecutor for LootExecutor {
};
for stack in stacks {
let remaining = futures::executor::block_on(insert_into_inventory(
inventory.as_ref(),
stack,
));
let remaining = insert_into_inventory(inventory.as_ref(), stack);
if !remaining.is_empty() {
world.drop_stack(&pos, remaining);
}

View File

@ -48,10 +48,9 @@ impl ServerPlayerData {
player
.player_screen_handler
.lock()
.await
.on_closed(player.as_ref())
.await;
player.on_handled_screen_closed().await;
.unwrap_or_else(std::sync::PoisonError::into_inner)
.on_closed(player.as_ref());
player.on_handled_screen_closed();
let mut nbt = NbtCompound::new();
player.write_nbt(&mut nbt);

View File

@ -567,17 +567,13 @@ impl EntityBase for ItemEntity {
.living_entity
.pickup(&self.entity, amount_picked_up.into());
let player_clone = player.clone();
tokio::spawn(async move {
player_clone
.current_screen_handler
.lock()
.await
.lock()
.await
.send_content_updates()
.await;
});
player
.current_screen_handler
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.send_content_updates();
if is_empty {
self.entity.remove();

View File

@ -1906,7 +1906,7 @@ impl LivingEntity {
}
#[allow(dead_code)]
async fn damage_armor_items(&self, caller: &dyn EntityBase, damage_amount: f32) {
fn damage_armor_items(&self, caller: &dyn EntityBase, damage_amount: f32) {
// Formula: armor loses floor(incoming_damage / 4) durability, minimum 1.
let armor_damage = (damage_amount / 4.0).floor().max(1.0) as i32;
let mut equipment_updates = Vec::new();
@ -1954,12 +1954,10 @@ impl LivingEntity {
}
equipment_updates.push((slot.clone(), stack.clone()));
if let Some(player) = caller.get_player() {
player
.enqueue_slot_set_packet(&CSetPlayerInventory::new(
(slot_index as i32).into(),
&ItemStackSerializer::from(stack),
))
.await;
player.enqueue_slot_set_packet(&CSetPlayerInventory::new(
(slot_index as i32).into(),
&ItemStackSerializer::from(stack),
));
}
}
}

View File

@ -18,7 +18,7 @@ use pumpkin_data::tag::{Enchantment as EnchantmentTag, Taggable};
use pumpkin_data::tracked_data;
use pumpkin_inventory::merchant::merchant_screen_handler::MerchantScreenHandler;
use pumpkin_inventory::screen_handler::{
BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
};
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_nbt::tag::NbtTag;
@ -32,7 +32,6 @@ use pumpkin_util::math::{boundingbox::BoundingBox, position::BlockPos, vector3::
use pumpkin_util::text::TextComponent;
use pumpkin_util::version::JavaMinecraftVersion;
use pumpkin_world::inventory::SimpleInventory;
use tokio::sync::Mutex;
use crate::entity::player::Player;
use crate::entity::{
@ -881,18 +880,30 @@ impl VillagerEntity {
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let screen = player.current_screen_handler.lock().await.clone();
let mut screen = screen.lock().await;
if screen.sync_id() != sync_id {
let ok = {
let screen = player
.current_screen_handler
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
let mut screen = screen
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if screen.sync_id() != sync_id {
false
} else if let Some(handler) =
screen.as_any_mut().downcast_mut::<MerchantScreenHandler>()
{
handler.offers.clone_from(&offers);
handler.update_result_slot();
true
} else {
false
}
};
if !ok {
return;
}
if let Some(handler) = screen.as_any_mut().downcast_mut::<MerchantScreenHandler>() {
handler.offers.clone_from(&offers);
handler.update_result_slot().await;
} else {
return;
}
drop(screen);
self.send_trade_offers(player, sync_id, offers, villager_data)
.await;
}
@ -919,18 +930,30 @@ impl VillagerEntity {
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let screen = player.current_screen_handler.lock().await.clone();
let mut screen = screen.lock().await;
if screen.sync_id() != sync_id {
let ok = {
let screen = player
.current_screen_handler
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
let mut screen = screen
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if screen.sync_id() != sync_id {
false
} else if let Some(handler) =
screen.as_any_mut().downcast_mut::<MerchantScreenHandler>()
{
handler.offers.clone_from(&offers);
handler.update_result_slot();
true
} else {
false
}
};
if !ok {
return;
}
if let Some(handler) = screen.as_any_mut().downcast_mut::<MerchantScreenHandler>() {
handler.offers.clone_from(&offers);
handler.update_result_slot().await;
} else {
return;
}
drop(screen);
self.send_trade_offers(&player, sync_id, offers, villager_data)
.await;
}
@ -1277,7 +1300,7 @@ impl VillagerEntity {
pub async fn open_trading_screen(&self, player: &Arc<Player>) {
// Open the merchant screen and then send the current offers packet
if let Some(sync_id) = player.open_handled_screen(self, None).await {
if let Some(sync_id) = player.open_handled_screen(self, None) {
let offers = self
.offers
.lock()
@ -1435,96 +1458,89 @@ impl VillagerEntity {
}
impl ScreenHandlerFactory for VillagerEntity {
fn create_screen_handler<'a>(
&'a self,
fn create_screen_handler(
&self,
sync_id: u8,
player_inventory: &'a Arc<pumpkin_inventory::player::player_inventory::PlayerInventory>,
player: &'a dyn InventoryPlayer,
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
Box::pin(async move {
let self_weak = self
.self_weak
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()?;
let server_player = player.as_any().downcast_ref::<Player>();
let player_uuid =
server_player.map_or_else(uuid::Uuid::nil, |p| p.get_entity().entity_uuid);
if let Some(player) = server_player {
self.update_special_prices(player);
player_inventory: &Arc<pumpkin_inventory::player::player_inventory::PlayerInventory>,
player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let self_weak = self
.self_weak
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()?;
let server_player = player.as_any().downcast_ref::<Player>();
let player_uuid =
server_player.map_or_else(uuid::Uuid::nil, |p| p.get_entity().entity_uuid);
if let Some(player) = server_player {
self.update_special_prices(player);
}
let offers = self
.offers
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
let world = self.get_entity().world.load().clone();
let mut handler = MerchantScreenHandler::new(
sync_id,
player_inventory,
self.merchant_inventory.clone(),
offers,
);
self.is_trading.store(true, Ordering::Relaxed);
*self
.trading_player
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some((player_uuid, sync_id));
let validity_weak = self_weak.clone();
handler.validity_check = Some(Box::new(move |inventory_player| {
validity_weak.upgrade().is_some_and(|villager| {
villager.can_continue_trading(inventory_player, player_uuid, sync_id)
})
}));
let update_weak = self_weak.clone();
handler.on_trade_updated = Some(Box::new(move |has_result| {
let Some(villager) = update_weak.upgrade() else {
return;
};
if villager
.trade_sound_cooldown
.compare_exchange(0, 20, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
villager.get_entity().play_sound(if has_result {
pumpkin_data::sound::Sound::EntityVillagerYes
} else {
pumpkin_data::sound::Sound::EntityVillagerNo
});
}
let offers = self
.offers
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
let world = self.get_entity().world.load().clone();
}));
let close_weak = self_weak.clone();
handler.on_close = Some(Box::new(move || {
if let Some(villager) = close_weak.upgrade() {
villager.is_trading.store(false, Ordering::Relaxed);
*villager
.trading_player
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = None;
villager.reset_special_prices();
}
}));
let mut handler = MerchantScreenHandler::new(
sync_id,
player_inventory,
self.merchant_inventory.clone(),
offers,
)
.await;
self.is_trading.store(true, Ordering::Relaxed);
*self
.trading_player
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some((player_uuid, sync_id));
let validity_weak = self_weak.clone();
handler.validity_check = Some(Box::new(move |inventory_player| {
validity_weak.upgrade().is_some_and(|villager| {
villager.can_continue_trading(inventory_player, player_uuid, sync_id)
})
}));
let update_weak = self_weak.clone();
handler.on_trade_updated = Some(Box::new(move |has_result| {
let Some(villager) = update_weak.upgrade() else {
return;
};
if villager
.trade_sound_cooldown
.compare_exchange(0, 20, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
villager.get_entity().play_sound(if has_result {
pumpkin_data::sound::Sound::EntityVillagerYes
} else {
pumpkin_data::sound::Sound::EntityVillagerNo
});
}
}));
let close_weak = self_weak.clone();
handler.on_close = Some(Box::new(move || {
let close_weak = close_weak.clone();
Box::pin(async move {
if let Some(villager) = close_weak.upgrade() {
villager.is_trading.store(false, Ordering::Relaxed);
*villager
.trading_player
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = None;
villager.reset_special_prices();
}
})
}));
handler.on_trade = Some(Box::new(move |offer_index| {
let self_weak = self_weak.clone();
handler.on_trade = Some(Box::new(move |offer_index| {
if let Some(villager) = self_weak.upgrade() {
let world = world.clone();
Box::pin(async move {
if let Some(villager) = self_weak.upgrade() {
villager
.complete_trade(offer_index, &world, player_uuid)
.await;
}
})
}));
tokio::spawn(async move {
villager
.complete_trade(offer_index, &world, player_uuid)
.await;
});
}
}));
Some(Arc::new(Mutex::new(handler)) as SharedScreenHandler)
})
Some(Arc::new(std::sync::Mutex::new(handler)) as SharedScreenHandler)
}
fn get_display_name(&self) -> TextComponent {

View File

@ -17,7 +17,7 @@ use pumpkin_data::villager::{
};
use pumpkin_inventory::merchant::merchant_screen_handler::MerchantScreenHandler;
use pumpkin_inventory::screen_handler::{
BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
};
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer;
@ -298,7 +298,7 @@ impl WanderingTraderEntity {
}
pub async fn open_trading_screen(&self, player: &Arc<Player>) {
if let Some(sync_id) = player.open_handled_screen(self, None).await {
if let Some(sync_id) = player.open_handled_screen(self, None) {
let offers = self
.offers
.lock()
@ -488,95 +488,87 @@ impl WanderingTraderEntity {
}
impl ScreenHandlerFactory for WanderingTraderEntity {
fn create_screen_handler<'a>(
&'a self,
fn create_screen_handler(
&self,
sync_id: u8,
player_inventory: &'a Arc<pumpkin_inventory::player::player_inventory::PlayerInventory>,
player: &'a dyn InventoryPlayer,
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
Box::pin(async move {
let self_weak = self
.self_weak
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()?;
let server_player = player.as_any().downcast_ref::<Player>();
let player_uuid =
server_player.map_or_else(uuid::Uuid::nil, |p| p.get_entity().entity_uuid);
player_inventory: &Arc<pumpkin_inventory::player::player_inventory::PlayerInventory>,
player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let self_weak = self
.self_weak
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()?;
let server_player = player.as_any().downcast_ref::<Player>();
let player_uuid =
server_player.map_or_else(uuid::Uuid::nil, |p| p.get_entity().entity_uuid);
let offers = self
.offers
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
let world = self.get_entity().world.load().clone();
let offers = self
.offers
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
let world = self.get_entity().world.load().clone();
let mut handler = MerchantScreenHandler::new(
sync_id,
player_inventory,
self.merchant_inventory.clone(),
offers,
)
.await;
let mut handler = MerchantScreenHandler::new(
sync_id,
player_inventory,
self.merchant_inventory.clone(),
offers,
);
self.is_trading.store(true, Ordering::Relaxed);
*self
.trading_player
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some((player_uuid, sync_id));
self.is_trading.store(true, Ordering::Relaxed);
*self
.trading_player
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some((player_uuid, sync_id));
let validity_weak = self_weak.clone();
handler.validity_check = Some(Box::new(move |inventory_player| {
validity_weak.upgrade().is_some_and(|trader| {
trader.can_continue_trading(inventory_player, player_uuid, sync_id)
})
}));
let validity_weak = self_weak.clone();
handler.validity_check = Some(Box::new(move |inventory_player| {
validity_weak.upgrade().is_some_and(|trader| {
trader.can_continue_trading(inventory_player, player_uuid, sync_id)
})
}));
let update_weak = self_weak.clone();
handler.on_trade_updated = Some(Box::new(move |has_result| {
let Some(trader) = update_weak.upgrade() else {
return;
};
if trader
.trade_sound_cooldown
.compare_exchange(0, 20, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
trader.get_entity().play_sound(if has_result {
Sound::EntityWanderingTraderYes
} else {
Sound::EntityWanderingTraderNo
});
}
}));
let update_weak = self_weak.clone();
handler.on_trade_updated = Some(Box::new(move |has_result| {
let Some(trader) = update_weak.upgrade() else {
return;
};
if trader
.trade_sound_cooldown
.compare_exchange(0, 20, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
trader.get_entity().play_sound(if has_result {
Sound::EntityWanderingTraderYes
} else {
Sound::EntityWanderingTraderNo
});
}
}));
let close_weak = self_weak.clone();
handler.on_close = Some(Box::new(move || {
let close_weak = close_weak.clone();
Box::pin(async move {
if let Some(trader) = close_weak.upgrade() {
trader.is_trading.store(false, Ordering::Relaxed);
*trader
.trading_player
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = None;
}
})
}));
let close_weak = self_weak.clone();
handler.on_close = Some(Box::new(move || {
if let Some(trader) = close_weak.upgrade() {
trader.is_trading.store(false, Ordering::Relaxed);
*trader
.trading_player
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = None;
}
}));
let trade_weak = self_weak.clone();
handler.on_trade = Some(Box::new(move |offer_index| {
let trade_weak = trade_weak.clone();
let world = world.clone();
Box::pin(async move {
if let Some(trader) = trade_weak.upgrade() {
trader.complete_trade(offer_index, &world, player_uuid);
}
})
}));
let trade_weak = self_weak;
handler.on_trade = Some(Box::new(move |offer_index| {
let trade_weak = trade_weak.clone();
let world = world.clone();
if let Some(trader) = trade_weak.upgrade() {
trader.complete_trade(offer_index, &world, player_uuid);
}
}));
Some(Arc::new(tokio::sync::Mutex::new(handler)) as SharedScreenHandler)
})
Some(Arc::new(std::sync::Mutex::new(handler)) as SharedScreenHandler)
}
fn get_display_name(&self) -> TextComponent {

File diff suppressed because it is too large Load Diff

View File

@ -732,10 +732,10 @@ impl EntityBase for MinecartEntity {
{
let container_clone = container.clone();
let world_clone = self.vehicle.entity.world.load_full();
container_clone.unpack_loot();
tokio::spawn(async move {
container_clone.unpack_loot().await;
let inventory: Arc<dyn Inventory> = container_clone;
world_clone.scatter_inventory(&position, &inventory).await;
world_clone.scatter_inventory(&position, &inventory);
});
}
if let Some(item) = self.drop_item() {
@ -750,24 +750,16 @@ impl EntityBase for MinecartEntity {
fn interact(&self, player: &Arc<Player>, item_stack: &mut ItemStack) -> bool {
match &self.kind {
MinecartKind::Chest(minecart) => {
let minecart = minecart.clone();
let custom_name = self.vehicle.entity.custom_name.load().as_ref().clone();
let player = player.clone();
tokio::spawn(async move {
minecart.interact(custom_name, &player).await;
});
minecart.interact(custom_name, player);
true
}
MinecartKind::Furnace(minecart) => {
minecart.interact(&self.vehicle.entity, player, item_stack)
}
MinecartKind::Hopper(minecart) => {
let minecart = minecart.clone();
let custom_name = self.vehicle.entity.custom_name.load().as_ref().clone();
let player = player.clone();
tokio::spawn(async move {
minecart.interact(custom_name, &player).await;
});
minecart.interact(custom_name, player);
true
}
MinecartKind::Rideable(_) => RideableMinecart::interact(&self.vehicle.entity, player),

View File

@ -24,7 +24,7 @@ impl ChestMinecart {
&self.inventory
}
pub(super) async fn interact(
pub(super) fn interact(
&self,
custom_name: Option<TextComponent>,
player: &Arc<Player>,
@ -40,7 +40,6 @@ impl ChestMinecart {
),
false,
)
.await
}
pub(super) fn write_nbt(&self, nbt: &mut NbtCompound) {

View File

@ -1,19 +1,18 @@
use std::any::Any;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Mutex, RwLock};
use pumpkin_data::item_stack::ItemStack;
use pumpkin_inventory::generic_container_screen_handler::{create_generic_9x3, create_hopper};
use pumpkin_inventory::player::player_inventory::PlayerInventory;
use pumpkin_inventory::screen_handler::{
BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
};
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::text::TextComponent;
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture};
use tokio::sync::{Mutex, RwLock};
use pumpkin_world::inventory::{Clearable, Inventory};
use crate::entity::{Entity, player::Player};
use crate::world::loot::fill_chest_inventory;
@ -83,22 +82,32 @@ impl MinecartInventory {
}
}
pub(super) async fn has_loot_table(&self) -> bool {
self.loot_table.lock().await.is_some()
pub(super) fn has_loot_table(&self) -> bool {
self.loot_table
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.is_some()
}
pub(super) async fn unpack_loot(self: &Arc<Self>) {
let loot_table = self.loot_table.lock().await.take();
pub(super) fn unpack_loot(self: &Arc<Self>) {
let loot_table = self
.loot_table
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.take();
let Some((loot_table, seed)) = loot_table else {
return;
};
let Some(table) = get_chest_loot_table(&loot_table) else {
*self.loot_table.lock().await = Some((loot_table, seed));
*self
.loot_table
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some((loot_table, seed));
return;
};
let inventory: Arc<dyn Inventory> = self.clone();
fill_chest_inventory(&inventory, table, seed).await;
fill_chest_inventory(&inventory, table, seed);
}
}
@ -107,39 +116,45 @@ impl Inventory for MinecartInventory {
self.size
}
fn is_empty(&self) -> InventoryFuture<'_, bool> {
Box::pin(async move {
let items = self.items.read().await;
items.iter().all(ItemStack::is_empty)
})
fn is_empty(&self) -> bool {
let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items.iter().all(ItemStack::is_empty)
}
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { self.items.read().await[slot].clone() })
fn get_stack(&self, slot: usize) -> ItemStack {
self.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner)[slot]
.clone()
}
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move {
let mut items = self.items.write().await;
std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone())
})
fn remove_stack(&self, slot: usize) -> ItemStack {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone())
}
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move {
let mut items = self.items.write().await;
if !items[slot].is_empty() && amount > 0 {
items[slot].split(amount)
} else {
ItemStack::EMPTY.clone()
}
})
fn remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack {
let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if !items[slot].is_empty() && amount > 0 {
items[slot].split(amount)
} else {
ItemStack::EMPTY.clone()
}
}
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
Box::pin(async move {
self.items.write().await[slot] = stack;
})
fn set_stack(&self, slot: usize, stack: ItemStack) {
self.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner)[slot] = stack;
}
fn as_any(&self) -> &dyn Any {
@ -148,13 +163,11 @@ impl Inventory for MinecartInventory {
}
impl Clearable for MinecartInventory {
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move {
self.items
.write()
.await
.fill_with(|| ItemStack::EMPTY.clone());
})
fn clear(&self) {
self.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.fill_with(|| ItemStack::EMPTY.clone());
}
}
@ -165,21 +178,19 @@ struct MinecartScreenFactory {
}
impl ScreenHandlerFactory for MinecartScreenFactory {
fn create_screen_handler<'a>(
&'a self,
fn create_screen_handler(
&self,
sync_id: u8,
player_inventory: &'a Arc<PlayerInventory>,
_player: &'a dyn InventoryPlayer,
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
Box::pin(async move {
let inventory: Arc<dyn Inventory> = self.inventory.clone();
let handler = if self.hopper {
create_hopper(sync_id, player_inventory, inventory).await
} else {
create_generic_9x3(sync_id, player_inventory, inventory).await
};
Some(Arc::new(Mutex::new(handler)) as SharedScreenHandler)
})
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let inventory: Arc<dyn Inventory> = self.inventory.clone();
let handler = if self.hopper {
create_hopper(sync_id, player_inventory, inventory)
} else {
create_generic_9x3(sync_id, player_inventory, inventory)
};
Some(Arc::new(Mutex::new(handler)) as SharedScreenHandler)
}
fn get_display_name(&self) -> TextComponent {
@ -187,18 +198,18 @@ impl ScreenHandlerFactory for MinecartScreenFactory {
}
}
pub(super) async fn open(
pub(super) fn open(
custom_name: Option<TextComponent>,
player: &Arc<Player>,
inventory: &Arc<MinecartInventory>,
title: TextComponent,
hopper: bool,
) -> bool {
if player.is_spectator() && inventory.has_loot_table().await {
if player.is_spectator() && inventory.has_loot_table() {
return false;
}
if !player.is_spectator() {
inventory.unpack_loot().await;
inventory.unpack_loot();
}
player
@ -210,7 +221,6 @@ pub(super) async fn open(
},
None,
)
.await
.is_some()
}
@ -266,8 +276,8 @@ mod tests {
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_world::inventory::Inventory;
#[tokio::test]
async fn deferred_mineshaft_loot_is_preserved_until_unpacked() {
#[test]
fn deferred_mineshaft_loot_is_preserved_until_unpacked() {
let inventory = std::sync::Arc::new(MinecartInventory::new(27));
let mut source = NbtCompound::new();
source.put_string(
@ -286,8 +296,8 @@ mod tests {
assert_eq!(deferred.get_long("LootTableSeed"), Some(1234));
assert!(deferred.get_list("Items").is_none());
inventory.unpack_loot().await;
assert!(!inventory.is_empty().await);
inventory.unpack_loot();
assert!(!inventory.is_empty());
let mut unpacked = NbtCompound::new();
inventory.write_nbt(&mut unpacked);
@ -295,19 +305,17 @@ mod tests {
assert!(unpacked.get_list("Items").is_some());
}
#[tokio::test]
async fn chest_minecart_items_round_trip_through_nbt() {
#[test]
fn chest_minecart_items_round_trip_through_nbt() {
let inventory = MinecartInventory::new(27);
inventory
.set_stack(8, ItemStack::new(3, &Item::POWERED_RAIL))
.await;
inventory.set_stack(8, ItemStack::new(3, &Item::POWERED_RAIL));
let mut nbt = NbtCompound::new();
inventory.write_nbt(&mut nbt);
let restored = MinecartInventory::new(27);
restored.read_nbt(&nbt);
let stack = restored.get_stack(8).await;
let stack = restored.get_stack(8);
assert_eq!(stack.get_item().id, Item::POWERED_RAIL.id);
assert_eq!(stack.item_count, 3);
}

View File

@ -51,19 +51,17 @@ impl HopperMinecart {
&& let Some(source) = block_entity.get_inventory()
{
for slot in 0..source.size() {
let stack = source.get_stack(slot).await;
let stack = source.get_stack(slot);
if stack.is_empty() || !source.can_transfer_to(inventory.as_ref(), slot, &stack)
{
continue;
}
let backup = stack.clone();
let one = source.remove_stack_specific(slot, 1).await;
if HopperBlockEntity::add_one_item(source.as_ref(), inventory.as_ref(), one)
.await
{
let one = source.remove_stack_specific(slot, 1);
if HopperBlockEntity::add_one_item(source.as_ref(), inventory.as_ref(), &one) {
return;
}
source.set_stack(slot, backup).await;
source.set_stack(slot, backup);
}
return;
}
@ -72,14 +70,14 @@ impl HopperMinecart {
Vector3::new(pos.x - 0.5, pos.y + 0.6875, pos.z - 0.5),
Vector3::new(pos.x + 0.5, pos.y + 2.0, pos.z + 0.5),
);
if Self::pick_up_item_internal(&world_clone, &inventory, &suction_box).await {
if Self::pick_up_item_internal(&world_clone, &inventory, &suction_box) {
return;
}
Self::pick_up_item_internal(&world_clone, &inventory, &cart_box).await;
Self::pick_up_item_internal(&world_clone, &inventory, &cart_box);
});
}
async fn pick_up_item_internal(
fn pick_up_item_internal(
world: &Arc<crate::world::World>,
inventory: &Arc<MinecartInventory>,
search_box: &BoundingBox,
@ -98,7 +96,7 @@ impl HopperMinecart {
}
(stack.clone(), stack.split(1))
};
if HopperBlockEntity::add_one_item(inventory.as_ref(), inventory.as_ref(), one).await {
if HopperBlockEntity::add_one_item(inventory.as_ref(), inventory.as_ref(), &one) {
let is_empty = {
let stack = item
.get_item_stack()
@ -120,7 +118,7 @@ impl HopperMinecart {
false
}
pub(super) async fn interact(
pub(super) fn interact(
&self,
custom_name: Option<TextComponent>,
player: &Arc<Player>,
@ -136,7 +134,6 @@ impl HopperMinecart {
),
true,
)
.await
}
pub(super) fn write_nbt(&self, nbt: &mut NbtCompound) {

View File

@ -32,31 +32,27 @@ impl BedrockClient {
slot_with_stack as usize,
) {
if slot_with_stack as usize != target_hotbar_slot {
let target_stack = player.inventory().get_stack(target_hotbar_slot).await;
let source_stack = player.inventory().get_stack(slot_with_stack as usize).await;
let target_stack = player.inventory().get_stack(target_hotbar_slot);
let source_stack = player.inventory().get_stack(slot_with_stack as usize);
player
.inventory()
.set_stack(target_hotbar_slot, source_stack)
.await;
.set_stack(target_hotbar_slot, source_stack);
player
.inventory()
.set_stack(slot_with_stack as usize, target_stack)
.await;
.set_stack(slot_with_stack as usize, target_stack);
}
} else {
let target_stack = player.inventory().get_stack(target_hotbar_slot).await;
let source_stack = player.inventory().get_stack(slot_with_stack as usize).await;
let target_stack = player.inventory().get_stack(target_hotbar_slot);
let source_stack = player.inventory().get_stack(slot_with_stack as usize);
player
.inventory()
.set_stack(target_hotbar_slot, source_stack)
.await;
.set_stack(target_hotbar_slot, source_stack);
player
.inventory
.set_stack(slot_with_stack as usize, target_stack)
.await;
.set_stack(slot_with_stack as usize, target_stack);
}
} else if player.gamemode.load() == GameMode::Creative {
player.inventory.set_stack(target_hotbar_slot, stack).await;
player.inventory.set_stack(target_hotbar_slot, stack);
} else {
return;
}
@ -80,9 +76,8 @@ impl BedrockClient {
player
.player_screen_handler
.lock()
.await
.send_content_updates()
.await;
.unwrap_or_else(std::sync::PoisonError::into_inner)
.send_content_updates();
// Sync main hand equipment to other players
let stack_in_hand = player.inventory().held_item();

View File

@ -6,7 +6,7 @@ impl BedrockClient {
if packet.container_id == 0 || packet.container_id == 0xff {
self.inventory_opened.store(false, Ordering::Relaxed);
}
player.on_handled_screen_closed().await;
player.on_handled_screen_closed();
self.enqueue_client_packet(&SContainerClose {
container_id: packet.container_id,

View File

@ -14,7 +14,10 @@ impl BedrockClient {
let result = 0u8;
if packet.actions.is_empty() && packet.legacy_request_id.0 != 0 {
let mut player_screen_handler = player.player_screen_handler.lock().await;
let mut player_screen_handler = player
.player_screen_handler
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
for legacy_slot in &packet.legacy_set_item_slots {
let mapped_window_id = match legacy_slot.container_id {
28 | 29 => 0, // HotBar or Inventory
@ -28,15 +31,13 @@ impl BedrockClient {
{
let current_stack = player_screen_handler
.get_slot(screen_slot)
.get_cloned_stack()
.await;
.get_cloned_stack();
if !current_stack.is_empty() {
player.drop_item(current_stack.clone());
player_screen_handler
.get_slot(screen_slot)
.set_stack(ItemStack::EMPTY.clone())
.await;
.set_stack(ItemStack::EMPTY.clone());
player_screen_handler
.set_received_stack(screen_slot, ItemStack::EMPTY.clone());
@ -57,7 +58,8 @@ impl BedrockClient {
}
}
}
player_screen_handler.send_content_updates().await;
player_screen_handler.send_content_updates();
drop(player_screen_handler);
}
for action in &packet.actions {
@ -75,28 +77,28 @@ impl BedrockClient {
{
let item_stack = descriptor_to_stack(&action.new_item);
let mut player_screen_handler = player.player_screen_handler.lock().await;
let mut player_screen_handler = player
.player_screen_handler
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let is_armor_equipped = player_screen_handler
.get_slot(screen_slot)
.get_stack()
.await
.are_equal(&item_stack);
if !is_armor_equipped {
if (5..9).contains(&screen_slot) {
player
.enqueue_equipment_change(
&match screen_slot {
5 => EquipmentSlot::HEAD,
6 => EquipmentSlot::CHEST,
7 => EquipmentSlot::LEGS,
8 => EquipmentSlot::FEET,
_ => unreachable!(),
},
&item_stack,
)
.await;
player.enqueue_equipment_change(
&match screen_slot {
5 => EquipmentSlot::HEAD,
6 => EquipmentSlot::CHEST,
7 => EquipmentSlot::LEGS,
8 => EquipmentSlot::FEET,
_ => unreachable!(),
},
&item_stack,
);
} else if (36..45).contains(&screen_slot) {
let hotbar_slot = screen_slot - 36;
if player.inventory().get_selected_slot() == hotbar_slot as u8 {
@ -108,10 +110,10 @@ impl BedrockClient {
player_screen_handler
.get_slot(screen_slot)
.set_stack(item_stack.clone())
.await;
.set_stack(item_stack.clone());
player_screen_handler.set_received_stack(screen_slot, item_stack);
player_screen_handler.send_content_updates().await;
player_screen_handler.send_content_updates();
drop(player_screen_handler);
inventory_updated = true;
}
@ -324,9 +326,7 @@ impl BedrockClient {
!current_equipped.are_items_and_components_equal(&held)
};
if should_change {
player
.enqueue_equipment_change(equippable.slot, &held)
.await;
player.enqueue_equipment_change(equippable.slot, &held);
let inventory = player.inventory();
let mut equipment_guard = inventory

File diff suppressed because it is too large Load Diff

View File

@ -958,9 +958,10 @@ impl JavaClient {
.await;
}
id if id == SContainerButtonClick::to_id(version) => {
player
.on_container_button_click(SContainerButtonClick::read(&mut payload, &version)?)
.await;
player.on_container_button_click(&SContainerButtonClick::read(
&mut payload,
&version,
)?);
}
id if id == SSetHeldItem::to_id(version) => {
self.handle_set_held_item(
@ -1012,8 +1013,7 @@ impl JavaClient {
player,
server,
SCloseContainer::read(&mut payload, &version)?,
)
.await;
);
}
id if id == SChunkBatch::to_id(version) => {
self.handle_chunk_batch(player, SChunkBatch::read(&mut payload, &version)?)

View File

@ -15,9 +15,14 @@ impl JavaClient {
player.world().clone().respawn_player(player, false).await;
{
let screen_handler = player.current_screen_handler.lock().await;
let mut screen_handler = screen_handler.lock().await;
screen_handler.sync_state().await;
let screen_handler = player
.current_screen_handler
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let mut screen_handler = screen_handler
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
screen_handler.sync_state();
};
// Restore abilities based on gamemode after respawn

View File

@ -2,12 +2,12 @@
use super::*;
impl JavaClient {
pub async fn handle_close_container(
pub fn handle_close_container(
&self,
player: &Arc<Player>,
_server: &Server,
_packet: SCloseContainer,
) {
player.on_handled_screen_closed().await;
player.on_handled_screen_closed();
}
}

View File

@ -46,9 +46,8 @@ impl JavaClient {
player
.player_screen_handler
.lock()
.await
.send_content_updates()
.await;
.unwrap_or_else(std::sync::PoisonError::into_inner)
.send_content_updates();
}
pub async fn handle_pick_item_from_entity(
@ -108,9 +107,8 @@ impl JavaClient {
player
.player_screen_handler
.lock()
.await
.send_content_updates()
.await;
.unwrap_or_else(std::sync::PoisonError::into_inner)
.send_content_updates();
}
}
}

View File

@ -42,11 +42,17 @@ impl JavaClient {
})
.count();
let cooking_display_count = RECIPES_COOKING.len();
let dynamic_recipes = server.recipe_manager.get_dynamic_recipes().await;
let dynamic_recipes = server.recipe_manager.get_dynamic_recipes();
let (grid_width, crafting_inv) = {
let screen_handler_arc = player.current_screen_handler.lock().await.clone();
let handler = screen_handler_arc.lock().await;
let screen_handler_arc = player
.current_screen_handler
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
let handler = screen_handler_arc
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let grid_width: usize = match handler.window_type() {
Some(WindowType::Crafting) => 3,
None => 2, // player inventory 2x2
@ -148,7 +154,7 @@ impl JavaClient {
let recipe_matches = {
let mut ok = true;
for (idx, ing) in ingredient_slots.iter().enumerate() {
let stack = crafting_inv.get_stack(idx).await;
let stack = crafting_inv.get_stack(idx);
match ing {
None => {
if !stack.is_empty() {
@ -172,7 +178,7 @@ impl JavaClient {
let mut min = u8::MAX;
for (idx, ing) in ingredient_slots.iter().enumerate() {
if ing.is_some() {
let stack = crafting_inv.get_stack(idx).await;
let stack = crafting_inv.get_stack(idx);
if !stack.is_empty() {
min = min.min(stack.item_count);
}
@ -185,9 +191,9 @@ impl JavaClient {
// Always clear the grid first, returning items to inventory.
for i in 0..grid_size {
let stack = crafting_inv.remove_stack(i).await;
let stack = crafting_inv.remove_stack(i);
if !stack.is_empty() {
player.inventory.offer(stack, false, player.as_ref()).await;
player.inventory.offer(stack, false, player.as_ref());
}
}
@ -195,7 +201,7 @@ impl JavaClient {
let active_ingredients: Vec<GenericIngredient<'_>> =
ingredient_slots.iter().flatten().copied().collect();
let amount_to_craft = if use_max {
compute_biggest_craftable(&active_ingredients, &player.inventory).await
compute_biggest_craftable(&active_ingredients, &player.inventory)
} else if recipe_matches {
current_min.saturating_add(1)
} else {
@ -203,21 +209,35 @@ impl JavaClient {
};
if amount_to_craft == 0 {
let screen_handler_arc = player.current_screen_handler.lock().await.clone();
screen_handler_arc.lock().await.send_content_updates().await;
let screen_handler_arc = player
.current_screen_handler
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
screen_handler_arc
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.send_content_updates();
return;
}
// Fill each grid slot with exactly `amount_to_craft` matching items.
for (idx, ing) in ingredient_slots.iter().enumerate() {
let Some(ingredient) = ing else { continue };
let taken = take_n_ingredient(&player.inventory, ingredient, amount_to_craft).await;
let taken = take_n_ingredient(&player.inventory, ingredient, amount_to_craft);
if !taken.is_empty() {
crafting_inv.set_stack(idx, taken).await;
crafting_inv.set_stack(idx, taken);
}
}
let screen_handler_arc = player.current_screen_handler.lock().await.clone();
screen_handler_arc.lock().await.send_content_updates().await;
let screen_handler_arc = player
.current_screen_handler
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
screen_handler_arc
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.send_content_updates();
}
}

View File

@ -14,8 +14,14 @@ impl JavaClient {
return;
}
let screen_handler = player.current_screen_handler.lock().await;
let mut screen_handler = screen_handler.lock().await;
let screen_handler = player
.current_screen_handler
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
let mut screen_handler = screen_handler
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if !screen_handler.can_use(player.as_ref()) {
return;
}
@ -23,9 +29,7 @@ impl JavaClient {
.as_any_mut()
.downcast_mut::<MerchantScreenHandler>()
{
merchant
.set_selected_offer(packet.selected_slot.0 as usize)
.await;
merchant.set_selected_offer(packet.selected_slot.0 as usize);
}
}
}

View File

@ -7,38 +7,76 @@ use pumpkin_world::inventory::Inventory;
impl JavaClient {
pub async fn handle_set_beacon(&self, player: &Arc<Player>, packet: &SSetBeacon) {
let screen_handler_lock = player.current_screen_handler.lock().await;
let mut screen_handler = screen_handler_lock.lock().await;
let is_valid = {
let screen_handler_lock = player
.current_screen_handler
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
let mut screen_handler = screen_handler_lock
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let Some(beacon_handler) = screen_handler
.as_any_mut()
.downcast_mut::<BeaconScreenHandler>()
else {
debug!(
"Player {} interacted with invalid menu (expected beacon)",
player.gameprofile.name
);
return;
let Some(beacon_handler) = screen_handler
.as_any_mut()
.downcast_mut::<BeaconScreenHandler>()
else {
debug!(
"Player {} interacted with invalid menu (expected beacon)",
player.gameprofile.name
);
return;
};
let Some(beacon_entity) = beacon_handler
.inventory
.as_any()
.downcast_ref::<BeaconBlockEntity>()
else {
return;
};
// Check if payment slot has an item
if beacon_entity
.payment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.is_empty()
{
return;
}
let levels = beacon_entity.levels.load(Ordering::Relaxed);
let primary_id = packet.primary_effect.map(|v| v.0);
let secondary_id = packet.secondary_effect.map(|v| v.0);
if BeaconBlockEntity::validate_effects(primary_id, secondary_id, levels) {
beacon_entity
.primary_effect
.store(primary_id.unwrap_or(-1), Ordering::Relaxed);
beacon_entity
.secondary_effect
.store(secondary_id.unwrap_or(-1), Ordering::Relaxed);
// Remove 1 item from payment slot
beacon_entity.remove_stack_specific(0, 1);
beacon_entity.mark_dirty();
screen_handler.sync_state();
info!(
"Player {} updated beacon effects: primary {:?}, secondary {:?}",
player.gameprofile.name, primary_id, secondary_id
);
true
} else {
false
}
};
let Some(beacon_entity) = beacon_handler
.inventory
.as_any()
.downcast_ref::<BeaconBlockEntity>()
else {
return;
};
// Check if payment slot has an item
if beacon_entity.payment.lock().await.is_empty() {
return;
}
let levels = beacon_entity.levels.load(Ordering::Relaxed);
let primary_id = packet.primary_effect.map(|v| v.0);
let secondary_id = packet.secondary_effect.map(|v| v.0);
if !BeaconBlockEntity::validate_effects(primary_id, secondary_id, levels) {
if !is_valid {
let primary_id = packet.primary_effect.map(|v| v.0);
let secondary_id = packet.secondary_effect.map(|v| v.0);
warn!(
"Player {} tried to set invalid beacon effects: primary {:?}, secondary {:?}",
player.gameprofile.name, primary_id, secondary_id
@ -48,25 +86,6 @@ impl JavaClient {
&[],
))
.await;
return;
}
beacon_entity
.primary_effect
.store(primary_id.unwrap_or(-1), Ordering::Relaxed);
beacon_entity
.secondary_effect
.store(secondary_id.unwrap_or(-1), Ordering::Relaxed);
// Remove 1 item from payment slot
beacon_entity.remove_stack_specific(0, 1).await;
beacon_entity.mark_dirty();
screen_handler.sync_state().await;
info!(
"Player {} updated beacon effects: primary {:?}, secondary {:?}",
player.gameprofile.name, primary_id, secondary_id
);
}
}

View File

@ -36,30 +36,30 @@ impl JavaClient {
item_stack.is_empty() || item_stack.item_count <= item_stack.get_max_stack_size();
if valid_slot && is_legal {
let mut player_screen_handler = player.player_screen_handler.lock().await;
let mut player_screen_handler = player
.player_screen_handler
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let is_armor_equipped = player_screen_handler
.get_slot(packet.slot as usize)
.get_stack()
.await
.are_equal(&item_stack);
if !is_armor_equipped {
if (5..9).contains(&packet.slot) {
player
.enqueue_equipment_change(
&match packet.slot {
5 => EquipmentSlot::HEAD,
6 => EquipmentSlot::CHEST,
7 => EquipmentSlot::LEGS,
8 => EquipmentSlot::FEET,
_ => {
tracing::error!("Invalid armor slot: {}", packet.slot);
EquipmentSlot::HEAD
}
},
&item_stack,
)
.await;
player.enqueue_equipment_change(
&match packet.slot {
5 => EquipmentSlot::HEAD,
6 => EquipmentSlot::CHEST,
7 => EquipmentSlot::LEGS,
8 => EquipmentSlot::FEET,
_ => {
tracing::error!("Invalid armor slot: {}", packet.slot);
EquipmentSlot::HEAD
}
},
&item_stack,
);
} else if (36..45).contains(&packet.slot) {
let slot = packet.slot - 36;
if player.inventory().get_selected_slot() == slot as u8 {
@ -71,10 +71,9 @@ impl JavaClient {
player_screen_handler
.get_slot(packet.slot as usize)
.set_stack(item_stack.clone())
.await;
.set_stack(item_stack.clone());
player_screen_handler.set_received_stack(packet.slot as usize, item_stack);
player_screen_handler.send_content_updates().await;
player_screen_handler.send_content_updates();
drop(player_screen_handler);
} else if is_negative && is_legal {
// Item drop

View File

@ -58,8 +58,7 @@ impl JavaClient {
PlayerInteractEvent::new(player, InteractAction::RightClickAir, &Block::AIR, None)
};
let (item_for_use, stack_for_use) = (item_in_hand.item, item_in_hand.clone());
self.prepare_hand_item_for_use(player, hand, &mut item_in_hand)
.await;
Self::prepare_hand_item_for_use(player, hand, &mut item_in_hand);
if !self
.should_continue_use_after_fish_event(server, player, hand, item_for_use)
@ -77,12 +76,7 @@ impl JavaClient {
}}
}
async fn prepare_hand_item_for_use(
&self,
player: &Arc<Player>,
hand: Hand,
held: &mut ItemStack,
) {
fn prepare_hand_item_for_use(player: &Arc<Player>, hand: Hand, held: &mut ItemStack) {
let inventory = player.inventory();
if let Some(cooldown) = held.get_use_cooldown() {
@ -135,7 +129,7 @@ impl JavaClient {
return;
}
player.enqueue_equipment_change(&slot, held).await;
player.enqueue_equipment_change(&slot, held);
let equipped = if current_equipped.is_empty() {
let equipped = held.clone();

View File

@ -20,7 +20,7 @@ impl GenericIngredient<'_> {
}
}
pub async fn take_n_ingredient(
pub fn take_n_ingredient(
inventory: &PlayerInventory,
ingredient: &GenericIngredient<'_>,
count: u8,
@ -51,7 +51,7 @@ pub async fn take_n_ingredient(
result.unwrap_or_else(|| ItemStack::EMPTY.clone())
}
pub async fn compute_biggest_craftable(
pub fn compute_biggest_craftable(
ingredients: &[GenericIngredient<'_>],
inventory: &PlayerInventory,
) -> u8 {

View File

@ -1,14 +1,12 @@
use std::any::Any;
use std::sync::Arc;
use tokio::sync::RwLock;
use std::sync::RwLock;
use pumpkin_data::{item_stack::ItemStack, screen::WindowType};
use pumpkin_inventory::screen_handler::{
InventoryPlayer, ItemStackFuture, ScreenHandler, ScreenHandlerBehaviour, ScreenHandlerFuture,
};
use pumpkin_inventory::screen_handler::{InventoryPlayer, ScreenHandler, ScreenHandlerBehaviour};
use pumpkin_inventory::slot::NormalSlot;
use pumpkin_util::text::TextComponent;
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture};
use pumpkin_world::inventory::{Clearable, Inventory};
pub struct PluginGui {
pub window_type: WindowType,
@ -32,74 +30,79 @@ impl PluginInventory {
}
impl Clearable for PluginInventory {
fn clear(&self) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + '_>> {
Box::pin(async move {
let mut slots = self.slots.write().await;
slots.fill_with(|| ItemStack::EMPTY.clone());
})
fn clear(&self) {
let mut slots = self
.slots
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
slots.fill_with(|| ItemStack::EMPTY.clone());
}
}
impl Inventory for PluginInventory {
fn size(&self) -> usize {
self.slots.blocking_read().len()
self.slots
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.len()
}
fn is_empty(&self) -> InventoryFuture<'_, bool> {
Box::pin(async move {
let slots = self.slots.read().await;
slots.iter().all(ItemStack::is_empty)
})
fn is_empty(&self) -> bool {
let slots = self
.slots
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
slots.iter().all(ItemStack::is_empty)
}
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move {
let slots = self.slots.read().await;
slots
.get(slot)
.cloned()
.unwrap_or_else(|| ItemStack::EMPTY.clone())
})
fn get_stack(&self, slot: usize) -> ItemStack {
let slots = self
.slots
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
slots
.get(slot)
.cloned()
.unwrap_or_else(|| ItemStack::EMPTY.clone())
}
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move {
let mut slots = self.slots.write().await;
if slot < slots.len() {
std::mem::replace(&mut slots[slot], ItemStack::EMPTY.clone())
} else {
ItemStack::EMPTY.clone()
}
})
fn remove_stack(&self, slot: usize) -> ItemStack {
let mut slots = self
.slots
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if slot < slots.len() {
std::mem::replace(&mut slots[slot], ItemStack::EMPTY.clone())
} else {
ItemStack::EMPTY.clone()
}
}
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move {
let mut slots = self.slots.write().await;
if slot < slots.len() && !slots[slot].is_empty() && amount > 0 {
slots[slot].split(amount)
} else {
ItemStack::EMPTY.clone()
}
})
fn remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack {
let mut slots = self
.slots
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if slot < slots.len() && !slots[slot].is_empty() && amount > 0 {
slots[slot].split(amount)
} else {
ItemStack::EMPTY.clone()
}
}
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
Box::pin(async move {
let mut slots = self.slots.write().await;
if slot < slots.len() {
slots[slot] = stack;
}
})
fn set_stack(&self, slot: usize, stack: ItemStack) {
let mut slots = self
.slots
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if slot < slots.len() {
slots[slot] = stack;
}
}
fn on_open(&self) -> InventoryFuture<'_, ()> {
Box::pin(async move {})
}
fn on_open(&self) {}
fn on_close(&self) -> InventoryFuture<'_, ()> {
Box::pin(async move {})
}
fn on_close(&self) {}
fn as_any(&self) -> &dyn Any {
self
@ -139,11 +142,9 @@ impl PluginScreenHandler {
}
impl ScreenHandler for PluginScreenHandler {
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;
})
fn on_closed(&mut self, player: &dyn InventoryPlayer) {
self.default_on_closed(player);
self.inventory.on_close();
}
fn as_any(&self) -> &dyn Any {
@ -162,11 +163,7 @@ impl ScreenHandler for PluginScreenHandler {
&mut self.behaviour
}
fn quick_move<'a>(
&'a mut self,
_player: &'a dyn InventoryPlayer,
_slot_index: i32,
) -> ItemStackFuture<'a> {
Box::pin(async move { ItemStack::EMPTY.clone() })
fn quick_move(&mut self, _player: &dyn InventoryPlayer, _slot_index: i32) -> ItemStack {
ItemStack::EMPTY.clone()
}
}

View File

@ -333,7 +333,7 @@ impl HostContainerBlockEntity for PluginHostState {
.get::<ContainerBlockEntityResource>(&Resource::new_own(res.rep()))
.map_err(|_| wasmtime::Error::msg("invalid container block entity resource handle"))?;
let inventory = container.provider.inventory.clone();
Ok(inventory.is_empty().await)
Ok(inventory.is_empty())
}
async fn get_stack(
@ -346,7 +346,7 @@ impl HostContainerBlockEntity for PluginHostState {
.get::<ContainerBlockEntityResource>(&Resource::new_own(res.rep()))
.map_err(|_| wasmtime::Error::msg("invalid container block entity resource handle"))?;
let inventory = container.provider.inventory.clone();
let stack = inventory.get_stack(slot as usize).await;
let stack = inventory.get_stack(slot as usize);
if stack.is_empty() {
Ok(None)
} else {
@ -376,7 +376,7 @@ impl HostContainerBlockEntity for PluginHostState {
None => pumpkin_data::item_stack::ItemStack::EMPTY.clone(),
};
inventory.set_stack(slot as usize, stack).await;
inventory.set_stack(slot as usize, stack);
Ok(())
}
@ -390,7 +390,7 @@ impl HostContainerBlockEntity for PluginHostState {
.get::<ContainerBlockEntityResource>(&Resource::new_own(res.rep()))
.map_err(|_| wasmtime::Error::msg("invalid container block entity resource handle"))?;
let inventory = container.provider.inventory.clone();
let removed = inventory.remove_stack(slot as usize).await;
let removed = inventory.remove_stack(slot as usize);
if removed.is_empty() {
Ok(None)
} else {
@ -406,7 +406,7 @@ impl HostContainerBlockEntity for PluginHostState {
.get::<ContainerBlockEntityResource>(&Resource::new_own(res.rep()))
.map_err(|_| wasmtime::Error::msg("invalid container block entity resource handle"))?;
let inventory = container.provider.inventory.clone();
inventory.clear().await;
inventory.clear();
Ok(())
}

View File

@ -124,11 +124,17 @@ impl gui::HostGui for PluginHostState {
slot: u32,
item: Resource<WitHostItemStack>,
) -> wasmtime::Result<()> {
let gui = self.get_gui_res(&res)?.provider.lock().await;
let mut slots = gui.inventory.slots.write().await;
if (slot as usize) < slots.len() {
let item_stack = {
let item_stack = self.get_item_stack(&item)?;
let item_stack = item_stack.lock().await.clone();
item_stack.lock().await.clone()
};
let gui = self.get_gui_res(&res)?.provider.lock().await;
let mut slots = gui
.inventory
.slots
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if (slot as usize) < slots.len() {
slots[slot as usize] = item_stack;
}
Ok(())
@ -141,7 +147,11 @@ impl gui::HostGui for PluginHostState {
) -> wasmtime::Result<Option<Resource<WitHostItemStack>>> {
let stack = {
let gui = self.get_gui_res(&res)?.provider.lock().await;
let slots = gui.inventory.slots.read().await;
let slots = gui
.inventory
.slots
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if (slot as usize) < slots.len() {
let stack = &slots[slot as usize];
if stack.is_empty() {
@ -191,7 +201,7 @@ impl gui::HostGui for PluginHostState {
async fn clear_items(&mut self, res: Resource<Gui>) -> wasmtime::Result<()> {
use pumpkin_world::inventory::Clearable;
let gui = self.get_gui_res(&res)?.provider.lock().await;
gui.inventory.clear().await;
gui.inventory.clear();
Ok(())
}

View File

@ -1,7 +1,6 @@
use pumpkin_protocol::bedrock::client::PackIdVersion;
use std::sync::Arc;
use std::sync::atomic::Ordering;
use tokio::sync::Mutex;
use wasmtime::component::Resource;
use crate::plugin::api::gui::PluginScreenHandler;
@ -1104,7 +1103,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
pumpkin_util::Hand::Left => PlayerInventory::OFF_HAND_SLOT,
};
player.inventory().set_stack(slot, stack.clone()).await;
player.inventory().set_stack(slot, stack.clone());
// Sync to client
let stack_serializer = ItemStackSerializer::from(stack);
@ -1127,10 +1126,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
pumpkin_data::item_stack::ItemStack::EMPTY.clone()
};
player
.inventory()
.set_stack(slot as usize, stack.clone())
.await;
player.inventory().set_stack(slot as usize, stack.clone());
// Sync to client
let stack_serializer = ItemStackSerializer::from(stack);
@ -1146,7 +1142,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
slot: u8,
) -> wasmtime::Result<Option<Resource<WitHostItemStack>>> {
let player = player_from_resource(self, &player)?;
let stack = player.inventory().get_stack(slot as usize).await;
let stack = player.inventory().get_stack(slot as usize);
if stack.is_empty() {
Ok(None)
} else {
@ -1163,7 +1159,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
) -> wasmtime::Result<Option<Resource<WitHostItemStack>>> {
let player = player_from_resource(self, &player)?;
let ec = player.ender_chest_inventory();
let stack = ec.get_stack(slot as usize).await;
let stack = ec.get_stack(slot as usize);
if stack.is_empty() {
Ok(None)
} else {
@ -1187,17 +1183,29 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
};
let ec = player.ender_chest_inventory();
ec.set_stack(slot as usize, stack.clone()).await;
ec.set_stack(slot as usize, stack.clone());
// If the player currently has their ender chest screen open, sync slot
let screen_handler_arc = player.current_screen_handler.lock().await.clone();
let handler = screen_handler_arc.lock().await;
if let Some(generic) = handler
.as_any()
.downcast_ref::<GenericContainerScreenHandler>()
&& generic.inventory.as_any().is::<EnderChestInventory>()
{
let sync_id = handler.sync_id();
let sync_id = {
let screen_handler_arc = player
.current_screen_handler
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
let handler = screen_handler_arc
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(generic) = handler
.as_any()
.downcast_ref::<GenericContainerScreenHandler>()
&& generic.inventory.as_any().is::<EnderChestInventory>()
{
Some(handler.sync_id())
} else {
None
}
};
if let Some(sync_id) = sync_id {
let stack_serializer = ItemStackSerializer::from(stack);
let packet = CSetContainerSlot::new(sync_id as i8, 0, slot as i16, &stack_serializer);
player.send_client_packet(&packet).await;
@ -1209,17 +1217,29 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
async fn clear_ender_chest(&mut self, player: Resource<Player>) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
let ec = player.ender_chest_inventory();
ec.clear().await;
ec.clear();
// If the player currently has their ender chest screen open, sync all slots
let screen_handler_arc = player.current_screen_handler.lock().await.clone();
let handler = screen_handler_arc.lock().await;
if let Some(generic) = handler
.as_any()
.downcast_ref::<GenericContainerScreenHandler>()
&& generic.inventory.as_any().is::<EnderChestInventory>()
{
let sync_id = handler.sync_id();
let sync_id = {
let screen_handler_arc = player
.current_screen_handler
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
let handler = screen_handler_arc
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(generic) = handler
.as_any()
.downcast_ref::<GenericContainerScreenHandler>()
&& generic.inventory.as_any().is::<EnderChestInventory>()
{
Some(handler.sync_id())
} else {
None
}
};
if let Some(sync_id) = sync_id {
let empty_serializer =
ItemStackSerializer::from(pumpkin_data::item_stack::ItemStack::EMPTY.clone());
for slot in 0..27 {
@ -1234,7 +1254,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
async fn open_ender_chest(&mut self, player: Resource<Player>) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.open_ender_chest().await;
player.open_ender_chest();
Ok(())
}
@ -2184,7 +2204,9 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
player.increment_screen_handler_sync_id();
let sync_id = player.screen_handler_sync_id.load(Ordering::Relaxed);
let screen_handler = Arc::new(Mutex::new(PluginScreenHandler::new(
let screen_handler: Arc<
std::sync::Mutex<dyn pumpkin_inventory::screen_handler::ScreenHandler>,
> = Arc::new(std::sync::Mutex::new(PluginScreenHandler::new(
sync_id,
gui.window_type,
&gui.inventory,
@ -2192,9 +2214,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
gui.allow_put_items,
)));
player
.open_handled_screen_direct(screen_handler, gui.title.clone())
.await;
player.open_handled_screen_direct(screen_handler, &gui.title);
Ok(())
}

View File

@ -716,7 +716,7 @@ impl Server {
self;
&mut PlayerLoginEvent::new(player.clone(), TextComponent::text("You have been kicked from the server"));
'after: {
player.screen_handler_sync_handler.store_player(player.clone()).await;
player.screen_handler_sync_handler.store_player(player.clone());
if world
.add_player(&player)
.is_ok() {

Some files were not shown because too many files have changed in this diff Show More