refactor(inventory): from async to sync Part 3
This commit is contained in:
parent
66a5f53408
commit
52038e3356
@ -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>()
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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());
|
||||
}
|
||||
|
||||
@ -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) {}
|
||||
|
||||
@ -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
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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]) {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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));
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
@ -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),
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
@ -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)?)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(())
|
||||
}
|
||||
|
||||
|
||||
@ -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(())
|
||||
}
|
||||
|
||||
|
||||
@ -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(())
|
||||
}
|
||||
|
||||
|
||||
@ -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
Loading…
Reference in New Issue
Block a user